Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++20 ranges too many | operators?

I am using g++ 10.2 for this code. Does anybody know why I get a compiler error for the last std::views::reverse on results3?

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

Error snippet:

...
<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&&)> >
...

The full set of errors can be seen here: https://godbolt.org/z/Y7Gjqd

like image 879
jcjustesen Avatar asked Feb 10 '21 03:02

jcjustesen


2 Answers

TL;DR: In this case, the iterator type of the result of std::views::take is std::counted_iterator, which sometimes fails to model an iterator concept when it's not expected to fail. This is LWG 3408 and is resolved by P2259.


This involves some really complicated mechanism.

Let

  • T be the iterator type of values | std::views::filter(even) | std::views::transform(square),
  • R be std::reverse_iterator<T>.

In the initializer of result3:

  1. The iterator type of ... | take(4) is std::counted_iterator<R>.
  2. The iterator_traits for std::counted_iterator<R> matches the partial specialization std::iterator_traits<std::counted_iterator<I>>.
  3. Said partial specialization derives from std::iterator_traits<I>.
  4. std::iterator_traits<R> is generated from the primary template: it provides a member named iterator_category, but not a member named iterator_concept.
  5. The iterator_category of R is the same as that of T.
  6. The iterator_category of T is input_iterator_tag, because its dereference operator does not return a reference, which is not allowed by C++17 ForwardIterator requirements. (The iterator_concept of T is bidirectional_iterator_tag.)

So in the end, std::iterator_traits<std::counted_iterator<R>> does not provide iterator_concept, and its iterator_category is input_iterator_tag.

Therefore, the result of ... | take(4) fails to model bidirectional_range, and thus it is rejected by views::reverse.

(The iterator type of ... | take(4) is not a counted_iterator when filter is removed from the pipeline; the iterator_category is not input_iterator_tag when transform is removed from the pipeline. So result1 and result2 do not trigger this error.)

This is essentially LWG 3408.

like image 127
cpplearner Avatar answered Oct 07 '22 16:10

cpplearner


EDIT It seems that MSVC shows the same behaviour, so perhaps the conclusion in my answer is not correct.

I'd argue this is a bug in the implementation of the take_view.

Quoting from the cppreference page on the take_view (emphasis mine):

take_view models the concepts contiguous_range, random_access_range, bidirectional_range, forward_range, input_range, and sized_range when the underlying view V models respective concepts.

Considering the following code we see the input to the take range is 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.

However after passing that range into the take_view it is no longer a bidierctional_range.

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

I'd argue this contradicts what is written on cppreference.

Now that the take view is not a bidirectional view, the following reverse view in your example can't compile as it expects a bidirectional_range as input.

Live example here.

like image 32
florestan Avatar answered Oct 07 '22 17:10

florestan