C++23 引入了非常强大的
ranges::to
,用于从范围构造对象(通常是容器),具有以下定义 ([range.utility.conv.to]):
template<class C, input_range R, class... Args> requires (!view<C>)
constexpr C to(R&& r, Args&&... args);
注意,它只是约束模板参数
C
不是view
,也就是说,C
甚至可能不是range
。
但是,其实现使用
range_value_t<C>
来获取 C
的元素类型,这使得 C
至少是一个 range
,因为模板参数 range_value_t
必须对 R
进行建模,因此 range
约束。
那么,为什么
ranges::to
对模板参数 C
的约束如此松散?
我注意到论文的R3版本曾经将
C
约束为input_range
,这显然是合理的,因为input_range
保证了range_value_t
格式良好,但在R4中这个约束是已删除。而且我没有找到任何关于此更改的评论。
那么,去除
C
必须是input_range
的约束有哪些考虑?
有没有一个实际例子来说明这种约束放松的好处?
这是我们需要解决的措辞问题,我将在今天晚些时候提出一个问题。这是LWG 3785。
那么,去除
必须是C
的约束有哪些考虑?input_range
ranges::to
的目标是将范围收集到......某物中。但它不一定是实际范围。只是消耗所有元素的东西。当然,最常见的用法是实际容器类型,最常见的实际容器类型是std::vector
。
还有其他有趣的用例,确实没有太多理由拒绝。
假设我们有一个
std::expected<int, std::exception_ptr>
的范围,称之为 results
。也许我们进行了大量计算,但其中一些计算失败了。我可以将其收集到std::vector<std::expected<int, std::exception_ptr>>
中,这可能会有用。但还有另一种选择:我可以将其收集到std::expected<std::vector<int>, std::exception_ptr>
中。也就是说,如果所有计算都成功,我将得到所有结果作为值类型。但是,如果其中任何一个失败,我都会收到第一个错误。这是一件非常有用的事情,从概念上讲,这与 ranges::to
对其输入所做的事情非常一致 - 所以这可以支持:
auto processed = results | ranges::to<std::expected>();
if (not processed) {
std::rethrow_exception(processed.error());
}
std::vector<int> values = std::move(processed).value();
// go do more stuff
这对支持非常有用 - 特别是因为不支持它并不需要任何成本。我们只是不能过早地拒绝它。
(我是从关于标准提案邮件列表的讨论来到这里的。)
P1206 非常清楚
C
应该是 container 类型 - 例如vector
,但绝对不是optional
或variant
或string
或int
或...(因此,与上面的2022年的巴里相反,我断言ranges::to<std::expected>
是不是)正如您在 中指出的,使用它需要您自担风险。邮件列表线程,ranges::to<std::optional>
用于在 C++23 中“工作”,但 C++26 使其停止工作,请自行承担风险。)
好的,所以 ranges::to
适合与容器一起使用。但 C++ 至少提供了三种不是范围的容器类型:
stack<T>
、
queue<T>
和
priority_queue<T>
。
static_assert(!std::ranges::range<std::stack<int>>);
auto stk = std::views::iota(1, 10) | std::ranges::to<std::stack<int>>(); // OK
这会调用 stack<int>::stack(from_range_t, R&&)
从这 9 个整数创建一个堆栈。但
stack<int>
本身并不是范围类型;它是一个容器,但它不是一个范围。 因此,限制
ranges::to
仅适用于
range
类型显然是不正确的。我们缺乏“容器”的任何标准概念。因此,唯一安全的解决方案(不会无缘无故地禁止我们想要允许的任何事情的解决方案)是让
ranges::to
基本上不受约束。一个例外是:因为我们知道没有容器曾经是
view
,所以我们可以安全地禁止
view
类型。哦,没有容器类型永远是标量类型:我们可以安全地要求
C
是类类型。除此之外,我们无能为力。