为什么 C++20 范围不只提供管道语法?

NoS*_*tAl 4 c++ range-v3 c++20

我知道这个问题听起来很奇怪,所以这里有一些上下文。

最近我很失望地得知 C++20 范围内的 map reduce 并不像人们期望的那样工作,即

const double val = data | transform(...) | accumulate (...);
Run Code Online (Sandbox Code Playgroud)

不起作用,您必须以这种不自然的方式编写它:

const double val = accumulate(data | transform(...));
Run Code Online (Sandbox Code Playgroud)

可以在此处此处找到详细信息,但归结为累积无法消除 2 个不同用例之间的歧义的事实。

所以这让我想到:

如果 C++20 要求你必须使用管道来使用范围,也就是你不能

vector<int> v;
sort(v);
Run Code Online (Sandbox Code Playgroud)

但你必须写

vector<int> v
v|sort();
Run Code Online (Sandbox Code Playgroud)

那会解决歧义问题吗?

如果是这样,尽管对使用std::sortSTL 算法和其他 STL 算法的人来说很不自然,但我想知道从长远来看这是否是更好的设计选择。

注意:如果这个问题太模糊,可以投票结束,但我觉得这是一个合理的设计问题,可以以相对公正的方式回答,特别是如果我对问题的理解是错误的。

Nic*_*las 6

您需要区分范围算法和范围适配器。算法是对一系列值执行通用操作的函数。适配器是创建范围视图的函数,这些视图修改范围的表示。适配器由|操作员链接;算法只是常规函数。

有时,相同的概念事物可以具有算法和适配器形式。transform既作为算法又作为适配器存在。前者将变换存储为输出范围;后者创建输入的视图范围,根据请求懒惰地计算转换。

这些是针对不同需求和用途的不同任务。

另请注意,C++20 中没有sort 适配器。排序适配器必须创建一个视图范围,该范围以某种方式混合在源范围中的元素周围。它必须为新的值序列分配存储空间(即使它只是对值的迭代器/指针/索引进行排序)。并且排序必须在构建时完成,因此不会发生懒惰的操作。

这也是为什么accumulate不能那样工作的原因。这不是“歧义”的问题;这是操作的基本性质的问题。累加从一个范围计算一个值;它不会从现有范围计算新范围。那是算法的工作,而不是适配器的工作。

有些任务在算法形式中很有用。某些任务在适配器形式中很有用(您会发现很少有zip类似算法)。有些任务对两者都有用。但是因为它们是用于不同目的的两个独立概念,所以它们具有不同的调用方式。

  • 我不明白你想在(过去的)最后一段中表达什么观点。range-v3 让我写 `get_data() | actions::sort` - 如果我选择的话,我没有看到任何关于 `|` 的内容会阻止我执行这样的操作。 (2认同)
  • 他们根本就没有分开?我实际上并不认为这些东西有任何有意义的区别——尤其是在语法方面。范围适配器是一种惰性算法,而操作是一种急切算法 - 适配器和操作采用并生成范围。积累“数据”的基本原理是什么?变换(f)| 过滤器(g) | accumulate` 无效?这是一件非常合理的事情,并且在其他语言/库中工作得很好。 (2认同)

Bar*_*rry 4

这能解决歧义问题吗?

是的。

如果只有一种方式来写某件事,那么这种方式必定是唯一可能的解释。如果算法“调用”只能是对必须|通过左侧范围的操作完成的算法的部分调用,那么您甚至不会有算法调用是部分还是全部的问题。它总是片面的。

从这个意义上来说,没有任何歧义。

但如果你走了那条路,你最终会得到这样的结果:

auto sum = accumulate("hello"s);
Run Code Online (Sandbox Code Playgroud)

它实际上并没有char对该字符串中的 s 求和,实际上是一个占位符,正在等待一个范围与初始值一起累积"hello"s