15

In Scott Meyer's new book, he proposes an example usage for rvalue reference qualifiers that looks something like this:

class Widget {
private:
    DataType values;

public:
    DataType& data() &  { return values; } 
    DataType  data() && { return std::move(values); } // why DataType?
};

So that:

auto values = makeWidget().data();

move-constructs values instead of copy-constructing it.

Why does the rvalue-ref-qualified data() return DataType instead of DataType&&? auto would still deduce DataType in that case (although decltype(auto) wouldn't - but that can't be the only reason to prefer to return a value instead of an ravlue ref). This highly voted answer returns an rvalue ref, which makes more sense conceptually to me.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    "the highly voted answer" correctly suggests to return by value, have a read once again – bobah Dec 08 '14 at 22:47
  • 1
    @bobah It says "Your case of returning an rvalue reference would be a good idea in other occasions." before proposing `Beta_ab && getAB() && { return move(ab); }`... – Barry Dec 08 '14 at 22:48

3 Answers3

11
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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • So the only reason is because of the dangling with `auto&&`? In either case, we'd only get one move done... the one from the member `values` into the object `values` (I should've named them differently in the question). – Barry Dec 08 '14 at 22:52
  • 3
    @LightnessRacesinOrbit references are not temporaries. – R. Martinho Fernandes Dec 08 '14 at 22:53
  • @R.MartinhoFernandes: The object referred to by `makeWidget()` probably is. But it doesn't matter; that's not bound to anything. – Lightness Races in Orbit Dec 08 '14 at 23:03
  • @Barry In my opinion, `auto&&` as a variable type is not the primary concern when returning rvalue refs that alias their complete object. Though this leads to issues with range-based for loops (where the `auto&&` is hidden), also consider function parameters (forwarding refs, rvalue refs and lref-to-const) – dyp Dec 09 '14 at 00:35
  • @dyp Can you provide an example for those other cases? I'm not sure I see the difference. – Barry Dec 09 '14 at 03:47
  • 2
    @Barry [Who is to blame for this range based for over a reference to temporary?](http://programmers.stackexchange.com/q/262215) ; for function parameters, I might have been wrong, since constructing an example is not straight-forward. In any case, the temporary lives at least until the end of the full-expression, so problems only arise when defining variables in the scope of the temporary's creation, and maybe for braced-init-lists (IIRC each initializer is a full-expression). – dyp Dec 09 '14 at 12:44
  • @dyp Thank you for that. The examples you have at the end are exactly what I was looking for. Do you mind copying those over to here as an answer? – Barry Dec 09 '14 at 14:01
-2

finally get this clear by researching StackOverflow and tech blogs, here contribute back a grain of my thoughts


    class Widget { 
        private: DataType values; 
        public: DataType& data() & { return values; } 
        // return rvalue, okay, ref-qualifier necessary, since values not usable after move
        DataType data() && { return std::move(values); }
        // return rvalue reference, okay
        DataType&& data_ref() && { return std::move(values); }
    };

    // note that auto is deduced to be Widget type
    // okay, rvalue returned by function call moves construct w1
    // if compiler implemented RVO copy elision, the move in move construct likely be elided
    auto w1 = Widget().data(); 
    // okay, rvalue reference returned by function call moves construct w2
    auto w2 = Widget().data_ref(); 

    // auto&& is a universal reference, deduced to be Widget&& rvalue reference
    // dangling reference, bad. temporary copy is destroyed, so w3 is a dangling rvalue reference to values in the destroyed copy
    auto&& w3 = Widget().data_ref();

-4

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.

Puppy
  • 144,682
  • 38
  • 256
  • 465