【问题标题】:C++20 ranges too many | operators?C++20 范围太多 |运营商?
【发布时间】:2021-05-13 18:34:31
【问题描述】:

我为此代码使用 g++ 10.2。有人知道为什么我在results3 上的最后一个std::views::reverse 出现编译器错误吗?

#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.
}

错误sn-p:

...
<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 或概念约束要求发生冲突。我很想知道这是否是标准/正确的行为,或者这是否是一个实现问题

标签: c++ compiler-errors c++20 std-ranges


【解决方案1】:

TL;DR:在这种情况下,std::views::take 的结果的迭代器类型是 std::counted_iterator,它有时无法对预期不会失败的迭代器概念进行建模。这是LWG 3408,由P2259 解决。


这涉及到一些非常复杂的机制。

  • Tvalues | std::views::filter(even) | std::views::transform(square) 的迭代器类型,
  • Rstd::reverse_iterator&lt;T&gt;

result3的初始化器中:

  1. ... | take(4) 的迭代器类型为std::counted_iterator&lt;R&gt;
  2. std::counted_iterator&lt;R&gt;iterator_traits 与部分特化 std::iterator_traits&lt;std::counted_iterator&lt;I&gt;&gt; 匹配。
  3. 所述部分特化源自std::iterator_traits&lt;I&gt;
  4. std::iterator_traits&lt;R&gt; 是从主模板生成的:它提供了一个名为 iterator_category 的成员,但没有提供名为 iterator_concept 的成员。
  5. Riterator_categoryT 相同。
  6. Titerator_categoryinput_iterator_tag,因为它的解引用运算符不返回引用,这是 C++17 ForwardIterator 要求所不允许的。 (Titerator_conceptbidirectional_iterator_tag。)

所以最后std::iterator_traits&lt;std::counted_iterator&lt;R&gt;&gt;没有提供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

【讨论】:

  • ...这将由P2259修复
  • 所以这是现代 c++ 吧?
  • @qwr 如果你还能理解它的工作原理,那它还不够现代 :)
  • @JeremyFriesner 我不知道。我以为我理解迭代器,但现在它们已被弃用?
  • P2259 被选入 C++23。 Herb Sutter 有一个关于最新 C++ ISO 标准会议的trip report
【解决方案2】:

编辑似乎 MSVC 表现出相同的行为,所以我的答案中的结论可能不正确。

我认为这是take_view 实现中的一个错误。

引用take_view 上的cppreference 页面(强调我的):

take_view 对 contiguous_range、random_access_range、 bidirectional_range、forward_range、input_range 和 sized_range 时 底层视图 V 对各自的概念进行建模。

考虑以下代码,我们看到输入范围是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 之后,它不再是 bidierctional_range

auto t = take_view(input_to_take, 4);
static_assert(std::ranges::bidirectional_range<decltype(t)>); // Error (constraints not satisfied)

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

既然镜头视图不是双向视图,您示例中的以下反向视图无法编译,因为它需要 bidirectional_range 作为输入。

现场示例here

【讨论】:

  • 如果是这样的话,前两个例子会不会也编译失败,因为它们在使用reverse之前都使用了take
  • 是的,这就是为什么我认为这是实现中的一个错误。在某些情况下,输入范围的双向性会丢失,而在另一些情况下则不会。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-27
  • 2012-03-21
相关资源
最近更新 更多