Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning by rvalue reference vs returning by value

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

like image 286
Curious Avatar asked Sep 24 '17 16:09

Curious


1 Answers

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.

like image 125
Nicol Bolas Avatar answered Nov 15 '22 03:11

Nicol Bolas