Reputation: 9144
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).
Upvotes: 39
Views: 9085
Reputation: 101
Did some tests myself, and the following are the results:
template <class T>
T&& wrapper_without_forwarding(T&& t) {
return t;
}
This compiles for these scenarios:
int&& ri = 123;
int& li = ri;
wrapper_without_forwarding(li);
wrapper_without_forwarding(ri);
But if I directly pass in a number like this:
wrapper_without_forwarding(123);
Compilation fails with the following messages:
error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
28 | return t;
| ^
The error messages might be confusing, but personally, I think it is equivalent to this situation:
int&& test(){
int&& rv = 123;
return rv; // compilation error
return 123; // correct
}
Basically, the type of rv
is indeed a rvalue reference, but it itself is not a rvalue. Also, the return type of int&&
can only bind to rvalues so the compilation failed. I was initially also confused by the idea, but I found this video to be very helpful. A good rule of thumb to determine the lvalue-ness and rvalue-ness of a value is to see if a value has a name.
Back to the original wrapper_without_forwarding
function. Since we're passing in a rvalue of 123
, T&&
deduced to int&&
to bind the rvalue. At the same time, the return value of this function is also specified as int&&
type, which goes into the exact same situation as the test
function (return value have to be rvalue).
For these two lines wrapper_without_forwarding(li);
and wrapper_without_forwarding(ri);
Since li
and ri
are all lvalue. The forwarding reference T&&
eventually deduces to T&
, and you can always return the lvalue reference of a lvalue.
If we add std::forward
, we essentially apply std::move
to the cases where the forwarding reference is deduced to rvalue reference like int&&
(like the previous example of passing in the number 123). We then consider what std::move
is doing: converting anything (notice that a named variable of type 'rvalue reference' is still a lvalue) into rvalue.
int&& test(){
int&& rv = 123;
return std::move(rv);
}
And this function will eventually work if we apply that std::move
. However, std::move
will convert anything into rvalue. But we don't want this to happen for cases like T&&
become int&
, so we apply std::forward
.
In conclusion, if you do want to make the function wrapper
work regardless of the type passed in, then std::forward
should be applied.
Upvotes: 0
Reputation: 153792
Depending on what this function gets passed, it results in undefined behavior! More precisely, if you pass a non-lvalue, i.e. an rvalue, to this function, the value referenced by the returned reference will be stale.
Also T&&
isn't a "universal reference" although the effect is somewhat like a universal reference in that T
can be deduced as T&
or T const&
. The problematic case is when it gets deduced as T
: the arguments get passed in as temporary and die after the function returns but before anything can get hold of a reference to it.
The use of std::forward<T>(x)
is limited to, well, forwarding objects when calling another function: what came in as a temporary looks like an lvalue within the function. Using std::forward<T>(x)
lets x
look like a temporary if it came in as one - and, thus, allow moving from x
when creating the argument of the called function.
When you return an object from a function there are a few scenarios you might want to take care of but none of them involves std::forward()
:
const
or non-const
, you don't want to do anything with the object and just return the reference.return
statements use the same variable or all are using a temporary, copy/move elision can be used and will be used on decent compilers. Since the copy/move elision is an optimization, it doesn't necessarily happen, however.std::move()
to allow moving from the local object.In most of these cases the produced type is T
and you should return T
rather than T&&
. If T
is an lvalue type the result may not be an lvalue type, though, and it may be necessary to remove the reference qualification from the return type. In the scenario you specifically asked about the type T
works.
Upvotes: 8
Reputation: 35449
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.
Upvotes: 13
Reputation: 5546
No you dont need to use std::forward
better dont return r-value reference at all because it can prevent of NRVO optimization. You can read more about move semantics in this article: Article
Upvotes: 3