Should I return an rvalue reference (by std::move'ing)?

c++, return, rvalue-reference, stdmove

Solution

So, lets say you have:

A compute()
{
  A v;
  …
  return v;
}

And you're doing:

A a = compute();

There are two transfers (copy or move) that are involved in this expression. First the object denoted by `v` in the function must be transferred to the result of the function, i.e. the value donated by the `compute()` expression. Let's call that Transfer 1. Then, this temporary object is transferred to create the object denoted by `a` - Transfer 2.

In many cases, both Transfer 1 and 2 can be elided by the compiler - the object `v` is constructed directly in the location of `a` and no transferring is necessary. The compiler has to make use of Named Return Value Optimization for Transfer 1 in this example, because the object being returned is named. If we disable copy/move elision, however, each transfer involves a call to either A's copy constructor or its move constructor. In most modern compilers, the compiler will see that `v` is about to be destroyed and it will first move it into the return value. Then this temporary return value will be moved into `a`. If `A` does not have a move constructor, it will be copied for both transfers instead.

Now lets look at:

A compute(A&& v)
{
  return v;
}

The value we're returning comes from the reference being passed into the function. The compiler doesn't just assume that `v` is a temporary and that it's okay to move from it1. In this case, Transfer 1 will be a copy. Then Transfer 2 will be a move - that's okay because the returned value is still a temporary (we didn't return a reference). But since we know that we've taken an object that we can move from, because our parameter is an rvalue reference, we can explicitly tell the compiler to treat `v` as a temporary with `std::move`:

A compute(A&& v)
{
  return std::move(v);
}

Now both Transfer 1 and Transfer 2 will be moves.

1 The reason why the compiler doesn't automatically treat `v`, defined as `A&&`, as an rvalue is one of safety. It's not just too stupid to figure it out. Once an object has a name, it can be referred to multiple times throughout your code. Consider:

A compute(A&& a)
{
  doSomething(a);
  doSomethingElse(a);
}

If `a` was automatically treated as an rvalue, `doSomething` would be free to rip its guts out, meaning that the `a` being passed to `doSomethingElse` may be invalid. Even if `doSomething` took its argument by value, the object would be moved from and therefore invalid in the next line. To avoid this problem, named rvalue references are lvalues. That means when `doSomething` is called, `a` will at worst be copied from, if not just taken by lvalue reference - it will still be valid in the next line.

It is up to the author of `compute` to say, "okay, now I allow this value to be moved from, because I know for certain that it's a temporary object". You do this by saying `std::move(a)`. For example, you could give `doSomething` a copy and then allow `doSomethingElse` to move from it:

A compute(A&& a)
{
  doSomething(a);
  doSomethingElse(std::move(a));
}

Problem

A C++Next blog post said that ``` A compute(…) { A v; … return v; } ``` If `A` has an accessible copy or move constructor, the compiler may choose to elide the copy. Otherwise, if `A` has a move constructor, `v` is moved. Otherwise, if `A` has a copy constructor, `v` is copied. Otherwise, a compile time error is emitted. I thought I should always return the value without `std::move` because the compiler would be able to figure out the best choice for users. But in another example from the blog post ``` Matrix operator+(Matrix&& temp, Matrix&& y) { temp += y; return std::move(temp); } ``` Here the `std::move` is necessary because `y` must be treated as an lvalue inside the function. Ah, my head almost blow up after studying this blog post. I tried my best to understand the reasoning but the more I studied, the more confused I became. Why should we return the value with the help of `std::move`?

Original source

Related problems