Search code examples
c++c++11returnrvalue-referenceperfect-forwarding

Forwarding of return values. Is std::forward is needed?


I am writing library which wraps a lot of functions and methods from other library. To avoid coping of return values I am applying std::forward like so:

template<class T>
T&& wrapper(T&& t) { 
   f(t);  // t passed as lvalue  
   return std::forward<T>(t);
}

f returns void and takes T&& (or overloaded on valueness). Wrapper always returns wrappers's param and on returned value should preserve valuness of argument. Do I actually need to use std::forward in return? Does RVO makes it superfluous? Does the fact that it is a reference (R or L) makes it superfluous? Is it needed if return is not last function statement (inside some if)?

It is debatable if wrapper() should return void or T&&, because caller have access to evaluated value via arg (which is reference, R or L). But in my case I need to return value so that wrapper() can be used in expressions.

It might be irrelevant to the question, but it is known that functions f does not steal from t, so 1st use of std::forward in f(std::forward<T>(t)) is superfluous and it was removed by me.

I've wrote small test: https://gist.github.com/3910503

Test shows, that returning unforwarded T- does creates extra copy in gcc48 and clang32 with -O3 (RVO does not kicks in).

Also, I was not able to get bad behavior from UB in:

auto&& tmp = wrapper(42); 

It does not prove anything of cause because it is undefined behavior (if it is UB).


Solution

  • In the case that you do know that t will not be in a moved-from state after the call to f, your two somewhat sensible options are:

    • return std::forward<T>(t) with type T&&, which avoids any construction but allows for writing e.g. auto&& ref = wrapper(42);, which leaves ref a dangling reference

    • return std::forward<T>(t) with type T, which at worst requests a move construction when the parameter is an rvalue -- this avoids the above problem for prvalues but potentially steals from xvalues

    In all cases you need std::forward. Copy elision is not considered because t is always a reference.