Returning by value vs rvalue reference

340 views Asked by At

After replying on question about returning temporaries I've noticed there was a second reply which is slightly different.

Instead of returning by value, it returned by rvalue reference. Can you explain what the difference is between these approaches and where the risks are for them?

struct Bar
    {
    Bar& doThings() & {return *this;}

     // Returning rvalue reference
    Bar&& doThings() &&    {return std::move(*this);}

     // Alternative: Returning by value
    //Bar doThings() && {return std::move(*this);}

    std::unique_ptr<int> m_content; // A non-copyable type
    };
2

There are 2 answers

7
xskxzr On

One major difference is that, if rvalue reference is returned, the lifetime of the temporary will not be extended when the return value is bound to a reference.

For example,

Bar&& rb = Bar{}.doThings();
use(rb); // not safe if returning rvalue reference, while safe if returning by value
0
Paul Sanders On

I was interested in this, so I knocked up a little test program:

#include <memory>
#include <iostream>

struct Bar
{
    // Return by reference
    Bar& doThings() & { return *this; }

    // Return by rvalue reference
    Bar&& doThings() && { std::cout << "in Bar&& doThings, this=" << (void *) this << "\n"; return std::move (*this); }

    ~Bar () { std::cout << "Bar destroyed at " << (void *) this << "\n"; }

    int first;
    std::unique_ptr<int> m_content; // Make Bar a non-copyable type
};

int main ()
{
    std::cout << std::hex;
    Bar bar;
    std::cout << "bar is at " << &bar << "\n";
    Bar& bar_ref = bar.doThings ();
    std::cout << "bar_ref refers to " << &bar_ref.first << "\n\n";
    Bar&& bar_rvalue_ref = Bar ().doThings ();
    std::cout << "bar_rvalue_ref refers to " << &bar_rvalue_ref.first << "\n\n";
}

Output:

bar is at 0x7ffdc10846f0
bar_ref refers to 0x7ffdc10846f0

in Bar&& doThings, this=0x7ffdc1084700
Bar destroyed at 0x7ffdc1084700
bar_rvalue_ref refers to 0x7ffdc1084700

Bar destroyed at 0x7ffdc10846f0

So this tell us that neither form of doThings() creates a temporary. You can also verify this at Godbolt.

So, looking at the implementation of Bar as written, where does this leave us? Well, I personally can't see any purpose in Bar&& doThings() at all because:

  1. AFAICT, you can only call it on a newly constructed object so there seems little point in it anyway.
  2. Because it moves from *this, it eats, in principle, that newly constructed object so the reference returned (which still refers to it) is not to be relied on.
  3. The temporary created in step 1 has an infinitessimal lifetime, as @xskxzr has already pointed out.

Whereas, Bar& doThings() works just fine (and, returns, in effect, a pointer to the object itself, so no copying or temporaries will ever be involved).

I'm sure I've missed something important so I'd love to hear people's reactions to this. Surely there is some purpose in returning by rvalue reference, perhaps in more complicated scenarios. Thanks.

Live demo