Steve Lorimer Steve Lorimer - 11 days ago 5
C++ Question

Is there any reason to capture a return-value as an rvalue-reference?

I have a move-only struct

Foo
, and a function
Foo get();


If I want to capture the return value of
get
in a mutable manner, I have two options:


  • by value (
    Foo
    )

  • by rvalue-reference (
    Foo&&
    )



When I capture by rvalue-reference I create an lvalue, much like capturing by value.

I'm struggling to see the point between the different options?


  • Is there any difference between these two?



Working example:

#include <iostream>

struct Foo
{
Foo(std::string s) : s(std::move(s)) {}
Foo(Foo&& f) : s(std::move(f.s)) {}

Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;

std::string s;
};

Foo get()
{
return Foo { "hello" };
}

int main()
{
// capture return value as l-value
Foo lv1 = get();

// move into another lvalue
Foo lv2 = std::move(lv1);
std::cout << lv2.s << '\n';

// capture return value as r-value reference
Foo&& rv1 = get();

// move into another lvalue
Foo lv3 = std::move(rv1);
std::cout << lv3.s << '\n';

return 0;
}

Answer
Foo lv1 = get();

This requires that Foo is copy/moveable.

Foo&& rv1 = get();

This does not (at least, not as far as this line of code is concerned; the implementation of get may still require one).

Even though compilers are permitted to elide the copy of the return value into the variable, copy initialization of this form still requires the existence of an accessible copy or move constructor.

So if you want to impose as few limitations on the type Foo as possible, you can store a && of the returned value.

Of course, C++17 changes this rule so that the first one doesn't need a copy/move constructor.

Comments