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
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
:
... | take(4)
is std::counted_iterator<R>
.iterator_traits
for std::counted_iterator<R>
matches the partial specialization std::iterator_traits<std::counted_iterator<I>>
.std::iterator_traits<I>
.std::iterator_traits<R>
is generated from the primary template: it provides a member named iterator_category
, but not a member named iterator_concept
.iterator_category
of R
is the same as that of T
.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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With