Is There Any Case Where a Return of a Rvalue Reference (&&) Is Useful

Is there any case where a return of a RValue Reference (&&) is useful?

There are a few occasions when it is appropriate, but they are relatively rare. The case comes up in one example when you want to allow the client to move from a data member. For example:

template <class Iter>
class move_iterator
{
private:
Iter i_;
public:
...
value_type&& operator*() const {return std::move(*i_);}
...
};

Should I return an rvalue reference parameter by rvalue reference?

There's no right answer, but returning by value is safer.

I have read several questions on SO relating to returning rvalue references, and have come to the conclusion that this is bad practice.

Returning a reference to a parameter foists a contract upon the caller that either

  1. The parameter cannot be a temporary (which is just what rvalue references represent), or
  2. The return value won't be retained past the the next semicolon in the caller's context (when temporaries get destroyed).

If the caller passes a temporary and tries to save the result, they get a dangling reference.

From what I have read, it seems the consensus is that since return values are rvalues, plus taking into account the RVO, just returning by value would be as efficient:

Returning by value adds a move-construction operation. The cost of this is usually proportional to the size of the object. Whereas returning by reference only requires the machine to ensure that one address is in a register, returning by value requires zeroing a couple pointers in the parameter std::string and putting their values in a new std::string to be returned.

It's cheap, but nonzero.

The direction currently taken by the standard library is, somewhat surprisingly, to be fast and unsafe and return the reference. (The only function I know that actually does this is std::get from <tuple>.) As it happens, I've presented a proposal to the C++ core language committee toward the resolution of this issue, a revision is in the works, and just today I've started investigating implementation. But it's complicated, and not a sure thing.

std::string transform(std::string&& input)
{
return transform(input); // calls the lvalue reference version
}

The compiler won't generate a move here. If input weren't a reference at all, and you did return input; it would, but it has no reason to believe that transform will return input just because it was a parameter, and it won't deduce ownership from rvalue reference type anyway. (See C++14 §12.8/31-32.)

You need to do:

return std::move( transform( input ) );

or equivalently

transform( input );
return std::move( input );

Difference between returning an rvalue reference and a value in && qualified functions

When you use this reference qualifiers in this way, you have to ask yourself two questions:

  1. What does std::move(object).funcname() mean?
  2. What does Typename().funcname() mean?

If you return a value from the function, then both of those will mean the same thing. Regardless of what you do to capture the value, the value will be a whole and distinct object, move-constructed from some internal data stored in the object.

In the first case, object now potentially no longer owns the data. In the second case, it doesn't matter because the object was a temporary and has since been destroyed.

If you return a && from the function, then those will mean different things. Namely, #2 will mean "your code is broken".

As to why you might still want to do it, even if it allows broken code. Well, that has to do with the actual answers to that question: what do those things mean?

Here's what I am referring to. std::get is basically a member function of tuple. And yet, if you pass it a tuple&&, you will get a T&& returned and not a T. Why?

Because you're accessing a member of the tuple.

If you had a struct, then std::move(struct_object).x would be an rvalue reference as well. So std::get is simply behaving in the same way for a tuple as member access would for a struct. That's kinda the whole point of tuple, after all: to behave like a struct as much as possible.

This allows you to do things like std::get<0>(std::move(tpl)).member, such that member will still be an rvalue reference. So you can move from a subobject without disturbing the rest of the object, exactly as you could for any other rvalue reference member accesses.

If get returned a value, then this would do something very different. Regardless of what we do with the return value, it will be moved out of the object, no questions asked. So if we only wanted to move a subobject of that member... too bad. The original object lost the entire member, not just a subobject of that member.

Of course, that doesn't change the fact that:

auto &&x = SomeStruct().x; //This extends the temporary's lifetime
auto &&x = std::get<0>(SomeTuple(...)); //This gets a dangling reference.

That is an unfortunate limitation of the language. But if the function is logically a member accessor, then returning a && from a && qualified this function instead of a value is a legitimate choice.

It all depends on what matters more to you: safety or orthogonality with member accessors.

C++ Why does returning rvalue reference change caller's behavior when function signature does not return rvalue reference?

What's happening is you are seeing elision there. You are move-constructing on return std::move(x) with a simple type of Bar; then the compiler is eliding the copy.

You can see the non-optimized assembly of GetBarRValue here. The call to the move constructor is actually happening in the GetBarRValue function, not upon returning. Back in main, it's just doing a simple lea, it's not at all calling any constructor.

Is returning by rvalue reference more efficient?

Beta_ab&&
Beta::toAB() const {
return move(Beta_ab(1, 1));
}

This returns a dangling reference, just like with the lvalue reference case. After the function returns, the temporary object will get destructed. You should return Beta_ab by value, like the following

Beta_ab
Beta::toAB() const {
return Beta_ab(1, 1);
}

Now, it's properly moving a temporary Beta_ab object into the return value of the function. If the compiler can, it will avoid the move altogether, by using RVO (return value optimization). Now, you can do the following

Beta_ab ab = others.toAB();

And it will move construct the temporary into ab, or do RVO to omit doing a move or copy altogether. I recommend you to read BoostCon09 Rvalue References 101 which explains the matter, and how (N)RVO happens to interact with this.


Your case of returning an rvalue reference would be a good idea in other occasions. Imagine you have a getAB() function which you often invoke on a temporary. It's not optimal to make it return a const lvalue reference for rvalue temporaries. You may implement it like this

struct Beta {
Beta_ab ab;
Beta_ab const& getAB() const& { return ab; }
Beta_ab && getAB() && { return move(ab); }
};

Note that move in this case is not optional, because ab is neither a local automatic nor a temporary rvalue. Now, the ref-qualifier && says that the second function is invoked on rvalue temporaries, making the following move, instead of copy

Beta_ab ab = Beta().getAB();

Returning by rvalue reference vs returning by value

Why not return by value and internally move the stored object?

Because that could be less efficient than returning a reference. Consider a case where you use the returned reference to fetch another reference from within that object. Like for example (*get_vector())[3]. With your proposed change, that is a reference to a copy of the original; the way it is currently, it is a reference to a value in the original temporary.

C++ as a language doesn't deal well effectively with the lifetime of references to temporaries. The solutions currently consist of either being careful about lifetimes, or not using references and having potentially slower/less efficient code. The standard library, in general, prefers to err on the side of performance.



Related Topics



Leave a reply



Submit