c++c++20std-rangesiotaborrowed-range

Why cannot I generate pairs of numbers from a std::views::iota in c++ 20


I wrote the following function to generate pairs from input ranges.

template <typename Range>
requires std::ranges::borrowed_range<Range>
auto pair(Range && r)
{
    // Creating a range of iterators to the original range
    auto iterator_range = std::views::iota(
      std::ranges::begin(r), 
      std::ranges::end(r));
    return iterator_range
        | std::views::drop(1)
        | std::views::transform([](auto const & it){
            using T = std::ranges::range_reference_t<Range>;
            return std::pair<T,T>(*std::prev(it), *it);
        });
}

It specifically has the constraint borrowed_range against the input so as not to accidentally take non borrowed ranges and get dangling iterators. This works perfectly for an input of std::vector.


auto number_range = std::vector{0,1,2,3,4,5,6,7,8,9};
        
for(auto [a, b] : pair(number_range))
   std::cout << a << ", " << b << std::endl;          

A reference to a vector is a borrowed range. However I thought I'd test it with an iota range as an input. It too is a borrowed range and because the types stored in the pair are set using using T = std::ranges::range_reference_t<Range>; I figured it would just work. However I was surprised when the below is compiled into an infinite loop.

auto number_range = std::views::iota(0, 10);
        
for(auto [a, b] : pair(number_range))
   std::cout << a << ", " << b << std::endl;
          

infinite loop

What's going on here? The gcc version is linked below and crashes

https://godbolt.org/z/q1EEaj6bW

Is there some undefined behaviour in my code? The clang version is linked below and runs as expected.

https://godbolt.org/z/c3fMqej31

Expected Results

std::vector
0, 1
1, 2
2, 3
3, 4
4, 5
5, 6
6, 7
7, 8
8, 9
std::views::iota
0, 1
1, 2
2, 3
3, 4
4, 5
5, 6
6, 7
7, 8
8, 9

Solution

  • Your code is UB.

    std::prev only works with Cpp17BidirectionalIterator, and although the iterator satisfies bidirectional_iterator in the case of iota_view, it does not meet the requirements of Cpp17BidirectionalIterator as its reference is pr-value, so it is just a C++17 input iterator. Applying std::prev to a C++17 input iterator is undefined behavior.

    Using ranges::prev works as expected for both compiler.

    Demo