C++20 范围太多 | 运营商?

jcj*_*sen 38 c++ compiler-errors c++20 std-ranges

我正在为此代码使用 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.
}
Run Code Online (Sandbox Code Playgroud)

错误片段:

#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.
}
Run Code Online (Sandbox Code Playgroud)

完整的错误集可以在这里看到:https : //godbolt.org/z/Y7Gjqd

cpp*_*ner 33

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


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

  • T是 的迭代器类型values | std::views::filter(even) | std::views::transform(square)
  • Rstd::reverse_iterator<T>

在 的初始化程序中result3

  1. 的迭代器类型... | take(4)std::counted_iterator<R>
  2. iterator_traitsstd::counted_iterator<R>部分特匹配std::iterator_traits<std::counted_iterator<I>>
  3. 所述部分专业化源自std::iterator_traits<I>
  4. std::iterator_traits<R>从主模板生成:它提供名为 的成员iterator_category,但不提供名为的成员iterator_concept
  5. iterator_categoryR的相同T
  6. 所述iterator_categoryTinput_iterator_tag,因为它的引用操作不返回参考,这是不通过C ++ 17 ForwardIterator要求允许的。(iterator_conceptTbidirectional_iterator_tag。)

所以最后,std::iterator_traits<std::counted_iterator<R>>不提供iterator_concept,它iterator_categoryinput_iterator_tag

因此, 的结果... | take(4)无法建模bidirectional_range,因此被 拒绝views::reverse

(的迭代器类型... | take(4)不是counted_iteratorfilter从管道除去的iterator_categoryinput_iterator_tagtransform从管道除去左右。result1result2不触发该错误。)

这本质上是LWG 3408

  • ...这将由 [P2259](https://wg21.link/p2259) 修复 (14认同)
  • 这就是现代 C++ 吧? (10认同)
  • @qwr 如果你仍然可以理解它是如何工作的,那么它还不够现代:) (3认同)
  • @JeremyFriesner 我不知道。我以为我理解迭代器,但现在它们已被弃用? (2认同)

flo*_*tan 6

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

我认为这是take_view.

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

take_view模型的概念contiguous_range,random_access_range, bidirectional_range,forward_range,input_range和sized_range当底层视图V模型各自的概念。

考虑以下代码,我们看到 take range 的输入是 a 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.
Run Code Online (Sandbox Code Playgroud)

但是,在将该范围传递到 之后,take_view它不再是bidierctional_range.

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

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

既然 take view 不是双向视图,则示例中的以下反向视图无法编译,因为它期望 abidirectional_range作为输入。

现场示例在这里