C++20的ranges库中,有太多的 | 运算符吗?

40

我在使用 g++ 10.2 编译这段代码。有人知道为什么最后一个 std::views::reverseresults3 上会导致编译错误吗?

#include <vector>
#include <ranges>

int main() {
    auto values = std::vector{1,2,3,4,5,6,7,8,9,10};
    auto even = [](const auto value) {
        return value % 2 == 0;
    };
    auto square = [](const auto value) {
        return value * value;
    };

    auto results1 = values
        | std::views::filter(even)
        | std::views::reverse
        | std::views::take(4)
        | std::views::reverse;

    auto results2 = values
        | std::views::transform(square)
        | std::views::reverse
        | std::views::take(4)
        | std::views::reverse;

    auto results3 = values
        | std::views::filter(even)
        | std::views::transform(square)
        | std::views::reverse
        | std::views::take(4)
        | std::views::reverse; // Error happens on this line.
}

错误片段:

...
<source>: In function 'int main()':
<source>:30:9: error: no match for 'operator|' (operand types are 'std::ranges::take_view<std::ranges::reverse_view<std::ranges::transform_view<std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, main()::<lambda(auto:13)> >, main()::<lambda(auto:14)> > > >' and 'const std::ranges::views::__adaptor::_RangeAdaptorClosure<std::ranges::views::<lambda(_Range&&)> >')
   25 |     auto results3 = values
      |                     ~~~~~~
   26 |         | std::views::filter(even)
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~
   27 |         | std::views::transform(square)
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   28 |         | std::views::reverse
      |         ~~~~~~~~~~~~~~~~~~~~~
   29 |         | std::views::take(4)
      |         ~~~~~~~~~~~~~~~~~~~~~
      |         |
      |         std::ranges::take_view<std::ranges::reverse_view<std::ranges::transform_view<std::ranges::filter_view<std::ranges::ref_view<std::vector<int, std::allocator<int> > >, main()::<lambda(auto:13)> >, main()::<lambda(auto:14)> > > >
   30 |         | std::views::reverse;
      |         ^ ~~~~~~~~~~~~~~~~~~~
      |                       |
      |                       const std::ranges::views::__adaptor::_RangeAdaptorClosure<std::ranges::views::<lambda(_Range&&)> >
...

完整的错误信息可以在此处查看:https://godbolt.org/z/Y7Gjqd


有趣的一点是:如果您将 transform 往下移动到最后一个 reverse 之后,它会成功编译。我想知道范围适配器的确切顺序是否可能与 CTAD 或概念约束要求发生冲突。我很想知道这是否是标准/正确的行为,还是这是一个实现问题。 - Human-Compiler
2个回答

35
在这种情况下,std::views::take结果的迭代器类型是std::counted_iterator,当它不应该失败时,有时会无法模拟迭代器概念。这是LWG 3408,通过P2259解决。
这涉及到一些非常复杂的机制。
设:
  • Tvalues | std::views::filter(even) | std::views::transform(square) 的迭代器类型。
  • Rstd::reverse_iterator<T>
result3 的初始化器中:
  1. ... | take(4) 的迭代器类型是 std::counted_iterator<R>
  2. std::counted_iterator<R>iterator_traits 匹配偏特化的 std::iterator_traits<std::counted_iterator<I>>
  3. 上述偏特化派生自 std::iterator_traits<I>
  4. std::iterator_traits<R> 是从主模板生成的:它提供了一个名为 iterator_category 的成员,但没有名为 iterator_concept 的成员。
  5. Riterator_categoryT 的相同。
  6. Titerator_categoryinput_iterator_tag,因为其解引用运算符不返回引用,这不符合 C++17 ForwardIterator 要求。 (Titerator_conceptbidirectional_iterator_tag。)
所以最终,std::counted_iterator<R> 不提供 iterator_concept,其 iterator_categoryinput_iterator_tag
因此,... | take(4) 的结果未能模拟 bidirectional_range,因此被 views::reverse 拒绝。
(当从管道中删除 filter 时,... | take(4) 的迭代器类型不是 counted_iterator;当从管道中删除 transform 时,iterator_category 不是 input_iterator_tag。 因此,result1result2 不会触发此错误。)
这本质上就是 LWG 3408

14
这将由P2259修复。 - Barry
@Barry 关于论文的问题:这里是否有其他论文资源描述了C++20和C++17迭代器之间的区别?我想应该进行了一些修改/设计来处理范围添加的问题吧?我找到的只有这个链接:https://en.cppreference.com/w/cpp/named_req/Iterator - NoSenseEtAl
13
这就是现代 C++ 吗? - qwr
5
如果你仍然可以理解它是如何工作的,那么它还不够现代。 :) - Jeremy Friesner
2
@JeremyFriesner 我不知道。我以为我理解迭代器了,但现在它们被弃用了? - qwr
1
P2259已被投票纳入C++23标准。Herb Sutter发布了一份旅行报告,介绍了最新的C++ ISO标准会议。 - bolov

6
编辑:看起来MSVC展现了相同的行为,因此我的答案结论可能不正确。
我认为这是take_view实现中的一个bug。
引用自take_viewcppreference页面(重点在于下划线):
take_view模拟概念连续范围、随机访问范围、双向范围、前向范围、输入范围和大小范围,当底层视图V模拟相应的概念时。
考虑以下代码,我们可以看到take范围的输入是bidirectional_range
 auto input_to_take = values
  | std::views::filter(even)
  | std::views::transform(square)
  | std::views::reverse;

static_assert(std::ranges::bidirectional_range<decltype(input_to_take)>); // No error here.

然而,在将该范围传递到take_view后,它不再是一个bidirectional_range
auto t = take_view(input_to_take, 4);
static_assert(std::ranges::bidirectional_range<decltype(t)>); // Error (constraints not satisfied)

我认为这与cppreference上的内容相矛盾。

现在,由于take视图不是双向视图,所以您示例中的以下反向视图无法编译,因为它期望双向范围作为输入。

可以查看运行示例here


如果是这样,那么前两个例子不也会无法编译吗?因为它们都在使用 reverse 之前使用了 take - Human-Compiler
是的,这就是我认为这是实现中的一个错误的原因。在某些情况下,输入范围的双向性会丢失,在其他情况下则不会。 - florestan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接