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 originaldata
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?
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.
}