In Scott Meyer\'s new book, he proposes an example usage for rvalue reference qualifiers that looks something like this:
class Widget {
private:
DataType
DataType data() && { return std::move(values); } // why DataType?
auto values = makeWidget().data();
The temporary that holds the return value will be initialized through the move-constructor, copy-initialized from move(values)
.
Then that temporary initializes values
, but since makeWidget().data()
is an rvalue (prvalue to be precise) the move-constructor is called again - with the temporary as its argument.
Now consider copy-elision:
When a nameless temporary, not bound to any references, would be moved or copied into an object of the same cv-unqualified type, the copy/move is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be moved or copied to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization".
So the second move will (presumably) be completely elided, and only one is left - the one we would have had anyway, had the return type been the rvalue reference.
The problem with returning an rvalue reference is that if we write
auto&& values = makeWidget().data();
values
will be dangling as binding an xvalue to a reference doesn't extend anythings lifetime. When we return the object type, the temporaries lifetime is extended.
auto&&
would be broken if it returned an rvalue reference. The linked answer is broken in this particular case.
The core problem here is that you can overload on lvalue or rvalue, but there are really three value categories you might want to know about- value, lvalue, and rvalue. C++ does not distinguish between value and rvalue when calling member functions, so you can't know if it's correct to return an rvalue reference or not. No matter which decision you make, it's easy to construct examples where it doesn't work.