Consider the following code, which uses the Ranges library from C++20:
#include <vector>
#include <ranges>
#include <iostream>
int main()
{
std::vector<int> v{0,1,2,3,4,5,6,7};
auto transformed = std::ranges::views::transform(v, [](int i){ return i * i; });
std::cout << *std::prev(std::end(transformed));
}
I was quite surprised to learn that (at least under GCC-10.3.0 and GCC-12.0.0) this code gets stuck in std::prev
.
What happens is that since the lambda doesn't return an lvalue reference, the transformed
range iterators are classified as input iterators (see the rules for iterator_category
selection for views::transform
). However, std::prev
requires the iterator to be at least a bidirectional iterator, so I guess this code is actually UB. In libstdc++ applying std::prev
to an input iterator leads to this function
template<typename _InputIterator, typename _Distance>
__advance(_InputIterator& __i, _Distance __n, input_iterator_tag)
{
// concept requirements
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
__glibcxx_assert(__n >= 0);
while (__n--)
++__i;
}
being called with __n == -1
, which explains the observed behavior.
If we replace std::prev
with manual iterator decrement, everything works fine. Switching to std::ranges::prev
works, too.
Now, it is clearly nonsensical that I can't do std::prev
on what is just a view over an std::vector
. While a simple solution exists, I feel extremely worried about this unexpected interplay between old and new range manipulation parts of the standard library. So, my question is: is this a known problem, and should I really forget everything not in the std::ranges
namespace when working with the new ranges, and rewrite all the existing code to make sure they work with the new ranges?