Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

move

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;
}
like image 629
Steve Lorimer Avatar asked Nov 23 '16 18:11

Steve Lorimer


People also ask

Is return value a rvalue?

Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue. Once you call return on an object, the name of the object does not exist anymore (it goes out of scope), so it becomes an rvalue.

What is the purpose of an rvalue reference?

Rvalue references is a small technical extension to the C++ language. Rvalue references allow programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.

Can you pass an lvalue to an rvalue reference?

In the example, the main function passes an rvalue to f . The body of f treats its named parameter as an lvalue. The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g ). You can cast an lvalue to an rvalue reference.


2 Answers

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.

like image 83
Nicol Bolas Avatar answered Sep 28 '22 15:09

Nicol Bolas


Foo&&

this creates a reference to a temporary value (or stores a rvalue reference assigned to it). If it stores a reference to a temporary value, its lifetime extends the value.

Foo

This stores a copy of the value, regardless of what was returned.

In C++11 and 14, if Foo cannot be moved, assigning Foo make_foo() to a variable of type Foo is illegal. There is a move there, even if the move is elided (and the return value and the value in the outer scope have merged lifetimes).

In C++17, guaranteed elision means that no move constructor need exist.

Foo x = make_foo(); // Foo make_foo()

the above in C++17 guarantees that the return value of make_foo() is just named x. In fact, a temporary within make_foo may also be x; the same object, with different names. No move need occur.

There are a few other subtle differences. decltype(x) will return the declared type of x; so Foo or Foo&& depending.

Another important difference is its use with auto.

auto&& x = some_function();

this creates a reference to anything. If some_function returns a temporary, it binds an rvalue reference to it and extends its lifetime. If it returns a reference, x matches the type of the reference.

auto x = some_function();

this creates a value, which may be copied from what some_function returns, or may be elided with the return value of some_function if it returns a temporary.

auto&& means, in a sense, "just make it work, and don't do extra work", and it could deduce to a Foo&&. auto means "store a copy".

In the "almost always auto" style, these will be more common than an explicit Foo or Foo&&.

auto&& will never deduce to Foo, but can deduce to Foo&&.

The most common uses of auto&&, even outside of almost always auto, would be:

for(auto&& x : range)

where x becomes an efficient way to iterate over the range, where we don't care what type range has much. Another common use is:

[](auto&& x){ /* some code */ }

lambdas are often used in contexts where the type is obvious and not worth typing again, like being passed to algorithms or the like. By using auto&& for parameter types we make the code less verbose.

like image 27
Yakk - Adam Nevraumont Avatar answered Sep 28 '22 16:09

Yakk - Adam Nevraumont