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;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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'