Most C++ standard library utilities return by rvalue reference when overloaded on a rvalue qualifier for this. For example std::optional has the following overloads for the value()
function
constexpr T& value() &;
constexpr const T & value() const &;
constexpr T&& value() &&;
constexpr const T&& value() const &&;
This allows the returned value to be moved from when needed, good. This is a solid optimization.
But what about the uncertainty associated with the returning of an rvalue? For example (live example here https://wandbox.org/permlink/kUqjfOWWRP6N57eS)
auto get_vector() {
auto vector = std::vector<int>{1, 2, 3};
return std::optional{std::move(vector)};
}
int main() {
for (auto ele : *get_vector()) {
cout << ele << endl;
}
}
The code above causes undefined behavior because of how the range based for loop is expanded
{
auto && __range = range_expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
The forwarding reference range
when binding to the return value of *get_vector()
does not extend the lifetime of the xvalue. And results in binding to a destroyed value. And therefore results in UB.
Why not return by value and internally move the stored object? Especially because now C++17 has the prvalue optimization, for example
auto lck = std::lock_guard{mtx};
Note that this is not the same as this question here C++11 rvalues and move semantics confusion (return statement), this does not mention the lifetime extension problem with rvalue returns with container/holders and was asked way before C++17 had mandatory elision for prvalues
Why not return by value and internally move the stored object?
Because that could be less efficient than returning a reference. Consider a case where you use the returned reference to fetch another reference from within that object. Like for example (*get_vector())[3]
. With your proposed change, that is a reference to a copy of the original; the way it is currently, it is a reference to a value in the original temporary.
C++ as a language doesn't deal well effectively with the lifetime of references to temporaries. The solutions currently consist of either being careful about lifetimes, or not using references and having potentially slower/less efficient code. The standard library, in general, prefers to err on the side of performance.
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