Search code examples
c++c++11move-semanticsrvalue-referenceref-qualifier

Returning by value or by rvalue reference from rvalue reference qualified member function?


In Effective Modern C++, Item 12, Scott Meyers writes the following class to show how useful overloading member functions on the reference qualifiers can be:

class Widget {
public:
    using DataType = std::vector<double>;
    …
    DataType& data() &            // for lvalue Widgets
    { return values; }            // return lvalue

    DataType data() &&            // for rvalue Widgets
    { return std::move(values); } // return rvalue
    …
private:
    DataType values;
};

This seems clear: now non_temp_obj.data() will call the first overload and return a reference to a member of an object which is still alive afterwards, whereas make_temp_obj().data() returns by value a member of an object which dies as soon as that expression is done.

Here's my first question: as regards the && overload, why return std::move(values); and not just return values;, considering we are returning by value?

In the errata, however, Meyers writes

A better way to have the rvalue reference overload of the data member function return an rvalue is to have it return an rvalue reference. That would avoid the creation of a temporary object for the return value, and it would be consistent with the by-reference return of the original data interface near the top of page 84.

which I interpret as suggesting to change

    DataType data() &&
    { return std::move(values); }

to

    DataType&& data() &&
    { return std::move(values); }

but I don't understand the reason, especially in light of this answer which pretty much convinces me that the book version is correct and the errata is wrong.

So my second question is: who's right?


Solution

  • values is an object member and an lvalue, so if you just return values directly, it will be copied to the return value, not moved. The point of the && ref-qualified overload is to avoid making an unnecessary copy. return std::move(values) accomplishes this by casting values to an rvalue, so that it gets moved from instead of copied.

    For the second part of your question: both have their advantages and disadvantages. As the answer you linked notes, returning by value from the && overload avoids lifetime issues, since the returned object will have its lifetime extended if a reference is immediately bound to it. On the other hand, returning by value could destroy the value of values unexpectedly. For instance:

    DataType Widget::data() &&
    { return std::move(values); }
    
    void func() {
        Widget foo;
        std::move(foo).data(); // Moves-constructs a temporary from
                               // foo::value, which is then immediately
                               // destroyed.
        auto bar = foo.data(); // Oops, foo::value was already moved from
                               // above and its value is likely gone.
    }