c++std-ranges

C++ vector constructed directly as range argument not working as expected


This is different when I construct a vector directly as an argument vs. using one defined as a variable, can someone explain why?

#include <cstdlib>
#include <iostream>
#include <ostream>
#include <ranges>
#include <vector>

using namespace std;

template <std::ranges::range R>
std::ranges::borrowed_iterator_t<R> random_elt(R&& r) {
    auto it = r.begin();
    for (size_t i = 0; i < ::rand() % std::ranges::size(r); i++) {
        it++;
    }
    return it;
}

int main() {
    std::vector<int> v{1, 2, 3};

    // This is fine.
    cout << *random_elt(v) << endl;

    // Surely this should be the same?
    cout << *random_elt(std::vector<int>({1, 2, 3})) << endl;
}

Error:

$ g++ -o main main.cpp -std=gnu++23
main.cpp: In function ‘int main()’:
main.cpp:24:13: error: no match for ‘operator*’ (operand type is ‘std::ranges::borrowed_iterator_t<std::vector<int> >’)
   24 |     cout << *random_elt(std::vector<int>({1, 2, 3})) << endl;
      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Solution

  • Your function template random_elt accepts the range by reference and returns an iterator into it.

    The first case is OK because the range is based on the std::vector v whose lifetime exceeds the usage of the output from random_elt.

    But in the second case the argument std::vector<int>({1, 2, 3}) is a temporary. The iterator into the range based on it that you return from random_elt is potentially dangling. You attempt to dereference it with operator* when the range's lifetime has potentially ended.

    The error from gcc that you posted is a bit unintuitive, but it mentions std::ranges::borrowed_iterator_t which is related to potentially dangling iterators.

    MSVC in this case issues a clearer error message and mentions std::ranges::dangling:

    error C2100: you cannot dereference an operand of type 'std::ranges::dangling'
    

    Live demo with MSVC error