C++20:为什么范围适配器和范围不能组合在一个表达式中?

C++20: Why can't range adaptors and ranges be combined in one expression?

我正在查看以下代码段:

std::vector<int> elements{ 1,2,3 };
// this won't compile:
elements
    | std::views::filter([](auto i) { return i % 2 == 0; })
    | std::ranges::for_each([](auto e) {std::cout << e << std::endl; });
// but this compiles:
auto view  = 
elements
    | std::views::filter([](auto i) { return i % 2 == 0; });
std::ranges::for_each(view, [](auto e) {std::cout << e << std::endl; });
 

而且我看不出范围适配器(视图)和范围算法通过管道运算符的组合无法编译的原因。我想这与 for_each 似乎在我的编译器 (msvc 19.29) 报告中返回的类型 std::ranges::dangling 有关,但我不确定 ...

Why can't range adaptors and ranges be combined in one expression?

他们可以。你只是把它们结合起来了。您使用 elements 作为范围,并将其与范围适配器组合 std::views::filter.

std::ranges::for_each 然而既不是范围,也不是范围适配器。它是一个接受范围作为参数的函数(模板)。如果你真的愿意,你可以将它保留在一个表达式中,尽管在我看来这是以可读性为代价的:

std::ranges::for_each(
    elements
      | std::views::filter([](auto i) { return i % 2 == 0; }),
    [](auto e) {std::cout << e << std::endl; }
);

filter 是一个范围适配器:它接受一系列值并将其转换为不同的值范围。

for_each 是一个 操作 。它采用一系列值并对其进行处理。那个“东西”并没有导致一系列价值的产生。所以它没有适应任何东西;它正在消耗范围。

范围适配器组合是关于构建范围,而不是消耗它们。范围组合的乘积始终是范围。

你问的问题无异于想知道为什么可以将一个 std::string 添加到另一个,但是你不能将一个 std::string 添加到一个 std::ifstream 来打开一个文件。打开文件不会产生字符串;它会生成一个 文件 。此处相同:在范围内执行任意操作不会产生范围。

这就是为什么 ranges::transform 有一个范围适配器 views::transform 版本:因为 transform 在概念上产生了一个新的值范围。 for_each 不产生任何东西;它只消耗一个范围。

为什么某些算法集确实具有管道支持(视图、操作和 to - 虽然 C++20 目前只有视图)而其他算法集却没有管道支持,但实际上并没有任何固有的设计原因t(<algorithm> 中的所有其他内容)。管道进入 for_each 与管道进入 filter 一样有意义,只是 range-v3(以及因此的 C++20 范围)只对某些事情这样做,而不是其他事情。

截至目前,没有选择加入机制来使某些东西在范围意义上“可管道化”(请参阅​​ P2387 来改变它)。但是一旦该论文或它的某些变体获得批准,就可以轻松编写一个算法适配器,使算法可通过管道传输。例如,您可以这样做:

elements
  | std::views::filter([](auto i) { return i % 2 == 0; })
  | adaptor(std::ranges::for_each)([](auto e) {std::cout << e << std::endl; });

Demo(注意这取决于 libstdc++ 内部)。

图书馆机制解决这个问题的一个问题是模棱两可——这取决于决定如果 f(x, y) 是可调用的,那么意图是让它成为一个完整的调用而不是部分调用。从这个意义上说,对于某些算法,您可能无法区分部分调用和完整调用(这本身可能是不让它们隐式管道化的动机之一)。对于某些视图来说,这当然是一个已知问题(zipconcatjoin 以定界符为例),这本身就是使用语言解决此问题的动机之一(例如 P2011)。

当然,这只会影响您是否可以写 | some_algo(args...),我在这里描述的选择加入机制可以很容易地拼写为 | partial(ranges::for_each, args...) 而不是 | adaptor(ranges::for_each)(args...) ,然后就没有歧义了。在我看来这只是一个更奇怪的拼写,这就是为什么我按照我的方式展示它的原因。