您反对自己的结论,如下所示:
template <typename Container>
auto transform(Container&& container, auto&&... args)
requires ( requires {container.begin(); container.end(); }) {
所以……这是什么?它是一个接受满足约束的模板参数的函数。让我们忽略这个约束需要成员begin/end
而不是更合理的std::ranges::begin/end
要求。
您打算将此要求应用于多少个函数?应该很多吧 每个算法都会有一个有这个要求的版本。所以这开始看起来不像一次性的要求,而更像是一个应该命名的东西concept
。
特别是因为该概念可能应该指定算法需要哪种迭代器。您不仅需要 member 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())>;
})
你真的想在每次请求“容器”时都输入这个吗?当然不是; 这就是命名概念的用途。
那么这个概念是什么?这是一个可以迭代的东西,一个可以通过特定迭代器接口访问的值序列。
并且应该选择该概念的名称,以免暗示元素序列的所有权。该transform
算法不关心给定的内容是否拥有该序列。所以“容器”绝对是错误的名称。
所以让我们称这个概念为范围值序列。值序列可以通过迭代器/哨兵接口进行迭代。而且您可能需要拥有不同类别的值序列。输入序列、前向序列、连续序列等。您可能想要检测序列是否可以在恒定时间内计算大小,或者序列是否有界或从其所有者借来的。
如果您可以编写操作符来创建这些值序列的视图,那不是很好吗?
任何其他名称的系列闻起来都一样甜美。一旦你开始了将迭代器与哨兵配对的黑暗道路,它将永远主宰你的命运。
在处理迭代器时,范围是一个自然的概念。一切都建立在范围概念之上