你在这里反驳了自己的结论,证据如下:
template <typename Container>
auto transform(Container&& container, auto&&... args)
requires ( requires {container.begin(); container.end(); }) {
所以...这是什么?这是一个接受满足约束条件的模板参数的函数。我们先不考虑这个约束要求成员
begin/end
而不是更合理的
std::ranges::begin/end
要求。
你打算将这个要求应用到多少个函数上?可能很多。每个算法都会有一个满足这个要求的版本。所以这看起来不像是一个一次性的要求,更像是应该被命名为
concept
的东西。
特别是因为这个概念可能需要指定算法所需的迭代器的类型。你不仅仅需要成员
begin/end
;你需要它们返回一个
input_or_output_iterator
和一个作为该迭代器的
sentinel_for
:
requires ( requires(Container c)
{
{c.begin()} -> input_or_output_iterator;
{c.end()} -> sentinel_for<decltype(c.begin())>;
})
你真的想每次询问一个“容器”时都要输入这个吗?当然不是,这就是命名概念存在的意义。
那么这个概念是什么呢?它是一个可以迭代的东西,是通过特定的迭代器接口访问的一系列值。
而且这个概念的名称应该选择得不暗示对元素序列的所有权。无论给定的是序列的所有者还是其他,"container"都绝对是错误的名称。
所以让我们把这个概念称为“range”(范围)值序列。值序列可以通过迭代器/终止器接口进行迭代。而且你可能需要有不同类别的值序列。输入序列、前向序列、连续序列等等。你可能想要检测序列是否能在常数时间内计算大小,或者序列是否有界限或从其所有者借用。
如果你能编写操作符来创建这些值序列的视图,那不是很棒吗?
不管怎么说,一个范围换个名字也同样香甜。一旦你开始将迭代器与终止器配对,它将永远主宰你的命运。
处理迭代器时,范围是一个自然的概念。而所有基于范围概念构建的内容都是其发展的产物。
it | transform(f)
可以给你一个适配后的迭代器,但你还需要适配结束迭代器才能做任何事情。仅仅能做一些事情是不够的。想象一下使用filter
会是什么样子 - undefined