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

C++C++11Move SemanticsRvalue Reference

C++ Problem Overview


Is there a reason when a function should return a RValue Reference? A technique, or trick, or an idiom or pattern?

MyClass&& func( ... );

I am aware of the danger of returning references in general, but sometimes we do it anyway, don't we? T& T::operator=(T) is just one idiomatic example. But how about T&& func(...)? Is there any general place where we would gain from doing that? Probably different when one writes library or API code, compared to just client code?

C++ Solutions


Solution 1 - C++

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_);}
    ...
};

Solution 2 - C++

This follows up on towi's comment. You never want to return references to local variables. But you might have this:

vector<N> operator+(const vector<N>& x1, const vector<N>& x2) { vector<N> x3 = x1; x3 += x2; return x3; }
vector<N>&& operator+(const vector<N>& x1, vector<N>&& x2)    { x2 += x1; return std::move(x2); }
vector<N>&& operator+(vector<N>&& x1, const vector<N>& x2)    { x1 += x2; return std::move(x1); }
vector<N>&& operator+(vector<N>&& x1, vector<N>&& x2)         { x1 += x2; return std::move(x1); }

This should prevent any copies (and possible allocations) in all cases except where both parameters are lvalues.

Solution 3 - C++

No. Just return the value. Returning references in general is not at all dangerous- it's returning references to local variables which is dangerous. Returning an rvalue reference, however, is pretty worthless in almost all situations (I guess if you were writing std::move or something).

Solution 4 - C++

You can return by reference if you are sure the referenced object will not go out of scope after the function exits, e.g. it's a global object's reference, or member function returning reference to class fields, etc.

This returning reference rule is just same to both lvalue and rvalue reference. The difference is how you want to use the returned reference. As I can see, returning by rvalue reference is rare. If you have function:

Type&& func();

You won't like such code:

Type&& ref_a = func();

because it effectively defines ref_a as Type& since named rvalue reference is an lvalue, and no actual move will be performed here. It's quite like:

const Type& ref_a = func();

except that the actual ref_a is a non-const lvalue reference.

And it's also not very useful even you directly pass func() to another function which takes a Type&& argument because it's still a named reference inside that function.

void anotherFunc(Type&& t) {
  // t is a named reference
}
anotherFunc(func());

The relationship of func( ) and anotherFunc( ) is more like an "authorization" that func() agrees anotherFunc( ) might take ownership of (or you can say "steal") the returned object from func( ). But this agreement is very loose. A non-const lvalue reference can still be "stolen" by callers. Actually functions are rarely defined to take rvalue reference arguments. The most common case is that "anotherFunc" is a class name and anotherFunc( ) is actually a move constructor.

Solution 5 - C++

One more possible case: when you need to unpack a tuple and pass the values to a function.

It could be useful in this case, if you're not sure about copy-elision.

Such an example:

template<typename ... Args>
class store_args{
    public:
        std::tuple<Args...> args;

        template<typename Functor, size_t ... Indices>
        decltype(auto) apply_helper(Functor &&f, std::integer_sequence<size_t, Indices...>&&){
            return std::move(f(std::forward<Args>(std::get<Indices>(args))...));
        }

        template<typename Functor>
        auto apply(Functor &&f){
            return apply_helper(std::move(f), std::make_index_sequence<sizeof...(Args)>{});
        }
};

pretty rare case unless you're writing some form of std::bind or std::thread replacement though.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontowiView Question on Stackoverflow
Solution 1 - C++Howard HinnantView Answer on Stackoverflow
Solution 2 - C++ClintonView Answer on Stackoverflow
Solution 3 - C++PuppyView Answer on Stackoverflow
Solution 4 - C++hangyuanView Answer on Stackoverflow
Solution 5 - C++RamblingMadView Answer on Stackoverflow