Referring to std::forward
For all the overloads, the return type has been specified as T&& (ignoring constexpr).
But in the description attached to the following example:
template<class T>
void wrapper(T&& arg)
{
// arg is always lvalue
foo(std::forward<T>(arg)); // Forward as lvalue or as rvalue, depending on T
}
If a call to wrapper() passes an rvalue std::string, then T is
deduced to std::string (not std::string&, const std::string&, or
std::string&&), and std::forward ensures that an rvalue reference is
passed to foo.
If a call to wrapper() passes a const lvalue
std::string, then T is deduced to const std::string&, and
std::forward ensures that a const lvalue reference is passed to foo.
If a call to wrapper() passes a non-const lvalue std::string, then T
is deduced to std::string&, and std::forward ensures that a non-const
lvalue reference is passed to foo.
In the two instances above after the first, an lvalue reference and not an rvalue reference (as implied by T&&, is this understanding correct?) has been documented as being passed to foo.
If the above understanding is correct, how come the return value has been specified as T&&?
There is a distinction between
void f1(int&& a) {}
template<class T>
void f2(T&& a) {}
The first version is defining f1 to work on rvalues. The second version, on the other hand, is a template function accepting a universal (or in some references, forwarding) reference as its argument.
To understand the mechanics of std::forward, you should call f2 with different arguments, as in:
#include <iostream>
template <class T> void f2(T &&a) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
int main() {
int a{5};
f2(5);
f2(a);
return 0;
}
When you compile the code, say with g++, you get the following output from your program:
./a.out
void f2(T &&) [T = int]
void f2(T &&) [T = int &]
As you can see, in the first call T is deduced to be int, whereas in the second call it is deduced to be int &. Due to the reference collapsing rules, as already mentioned in the comments to your question, T && will give you T whereas T& && will give you T&. In short, you observing T&& as the return type does not mean that the functions return an rvalue reference. In fact, in the template functions, && is like an identity operator. When combined with T&, it will give you T&; otherwise, it will give you T.
There is a really nice talk at CppCon2016 by Arthur O'Dwyer on this topic. Maybe you can have a look at it to understand the template type deduction rules, which will help you in clarifying the std::forward behaviour.
I thought I'd try to explain the general case in as simple a way as I can (without omitting any details) as I was confused by std::forward for the longest time.
Let [base-type] refer to the unreferenced part of the parameter type (which can have other type descriptors like const but no &'s). These are all the official reference collapsing rules for free (i.e. undetermined) template parameters. The far left column represents the type of T. The far right column represents the corresponding type of T& or T&& (i.e. type of arg in the template).
Type of T Type of arg
. | |
. v v
1 [base-type] + & => [base-type]&
2 [base-type] + && => [base-type]&&
3 [base-type]& + & => [base-type]&
4 [base-type]& + && => [base-type]&
5 [base-type]&& + & => [base-type]&
6 [base-type]&& + && => [base-type]&&
When arg is specified as type T&&, only rules 2, 4, or 6 apply for type deduction.
If the passed arg has the form [base-type]&&, only rules 2 and 6 apply. If rule 2 is used, [base-type] will be substituted for T. If rule 6 is used [base-type]&& will be substituted for T. These cases are practically identical as the return type, T&&, will be [base-type]&& in both cases.
Rule 2: T + && = [base-type] + && = [base-type]&&
Rule 6: T + && = [base-type]&& + && = [base-type]&&
From tests I've done, the compiler always choses T = [base-type] over [base-type]& or [base-type]&&, so long as the former is a valid substitution. Therefore T is always deduced as [base-type] when an argument (arg) of the form [base-type]&& is passed to the template function.
If the passed arg has the form [base-type]&, only rule 4 applies and [base-type]& must be substituted for T. There is only one possibility this time. The return type is thus...
Rule 4: T + && = [base-type]& + && = [base-type]&
The bottom line is this:
In every case the type returned from std::forward<T>(arg) is the
collapsed version of T&&, which is exactly the same as the type of
arg.
Note that this only works as long as arg is specified as being of type T&&. Specifying arg as T& or T doesn't produce the desired universal forwarding, so you shouldn't ever do it. arg T&& is the only universal forwarding argument specification.
I wrote a small tutorial program that can be run online here. It demonstrates that T&& can represent a reference to either an l-value and an r-value. It also shows that T& and T do not work.
passing l-values and r-values with universal forwarding.
Related
This question already has answers here:
Closed 11 years ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
Possible Duplicate:
Advantages of using forward
I know what it does and when to use it but I still can't wrap my head around how it works. Please be as detailed as possible and explain when std::forward would be incorrect if it was allowed to use template argument deduction.
Part of my confusion is this:
"If it has a name, it's an lvalue" - if that's the case why does std::forward behave differently when I pass thing&& x vs thing& x?
I think the explanation of std::forward as static_cast<T&&> is confusing. Our intuition for a cast is that it converts a type to some other type -- in this case it would be a conversion to an rvalue reference. It's not! So we are explaining one mysterious thing using another mysterious thing. This particular cast is defined by a table in Xeo's answer. But the question is: Why? So here's my understanding:
Suppose I want to pass you an std::vector<T> v that you're supposed to store in your data structure as data member _v. The naive (and safe) solution would be to always copy the vector into its final destination. So if you are doing this through an intermediary function (method), that function should be declared as taking a reference. (If you declare it as taking a vector by value, you'll be performing an additional totally unnecessary copy.)
void set(const std::vector<T> & v) { _v = v; }
This is all fine if you have an lvalue in your hand, but what about an rvalue? Suppose that the vector is the result of calling a function makeAndFillVector(). If you performed a direct assignment:
_v = makeAndFillVector();
the compiler would move the vector rather than copy it. But if you introduce an intermediary, set(), the information about the rvalue nature of your argument would be lost and a copy would be made.
set(makeAndFillVector()); // set will still make a copy
In order to avoid this copy, you need "perfect forwarding", which would result in optimal code every time. If you're given an lvalue, you want your function to treat it as an lvalue and make a copy. If you're given an rvalue, you want your function to treat it as an rvalue and move it.
Normally you would do it by overloading the function set() separately for lvalues and rvalues:
set(const std::vector<T> & lv) { _v = v; }
set(std::vector<T> && rv) { _v = std::move(rv); }
But now imagine that you're writing a template function that accepts T and calls set() with that T (don't worry about the fact that our set() is only defined for vectors). The trick is that you want this template to call the first version of set() when the template function is instantiated with an lvalue, and the second when it's initialized with an rvalue.
First of all, what should the signature of this function be? The answer is this:
template<class T>
void perfectSet(T && t);
Depending on how you call this template function, the type T will be somewhat magically deduced differently. If you call it with an lvalue:
std::vector<T> v;
perfectSet(v);
the vector v will be passed by reference. But if you call it with an rvalue:
perfectSet(makeAndFillVector());
the (anonymous) vector will be passed by rvalue reference. So the C++11 magic is purposefully set up in such a way as to preserve the rvalue nature of arguments if possible.
Now, inside perfectSet, you want to perfectly pass the argument to the correct overload of set(). This is where std::forward is necessary:
template<class T>
void perfectSet(T && t) {
set(std::forward<T>(t));
}
Without std::forward the compiler would have to assume that we want to pass t by reference. To convince yourself that this is true, compare this code:
void perfectSet(T && t) {
set(t);
set(t); // t still unchanged
}
to this:
void perfectSet(T && t) {
set(std::forward<T>(t));
set(t); // t is now empty
}
If you don't explicitly forward t, the compiler has to defensively assume that you might be accessing t again and chose the lvalue reference version of set. But if you forward t, the compiler will preserve the rvalue-ness of it and the rvalue reference version of set() will be called. This version moves the contents of t, which means that the original becomes empty.
This answer turned out much longer than what I initially assumed ;-)
First, let's take a look at what std::forward does according to the standard:
§20.2.3 [forward] p2
Returns: static_cast<T&&>(t)
(Where T is the explicitly specified template parameter and t is the passed argument.)
Now remember the reference collapsing rules:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
(Shamelessly stolen from this answer.)
And then let's take a look at a class that wants to employ perfect forwarding:
template<class T>
struct some_struct{
T _v;
template<class U>
some_struct(U&& v)
: _v(static_cast<U&&>(v)) {} // perfect forwarding here
// std::forward is just syntactic sugar for this
};
And now an example invocation:
int main(){
some_struct<int> s1(5);
// in ctor: '5' is rvalue (int&&), so 'U' is deduced as 'int', giving 'int&&'
// ctor after deduction: 'some_struct(int&& v)' ('U' == 'int')
// with rvalue reference 'v' bound to rvalue '5'
// now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int&&>(v)'
// this just turns 'v' back into an rvalue
// (named rvalue references, 'v' in this case, are lvalues)
// huzzah, we forwarded an rvalue to the constructor of '_v'!
// attention, real magic happens here
int i = 5;
some_struct<int> s2(i);
// in ctor: 'i' is an lvalue ('int&'), so 'U' is deduced as 'int&', giving 'int& &&'
// applying the reference collapsing rules yields 'int&' (& + && -> &)
// ctor after deduction and collapsing: 'some_struct(int& v)' ('U' == 'int&')
// with lvalue reference 'v' bound to lvalue 'i'
// now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int& &&>(v)'
// after collapsing rules: 'static_cast<int&>(v)'
// this is a no-op, 'v' is already 'int&'
// huzzah, we forwarded an lvalue to the constructor of '_v'!
}
I hope this step-by-step answer helps you and others understand just how std::forward works.
It works because when perfect forwarding is invoked, the type T is not the value type, it may also be a reference type.
For example:
template<typename T> void f(T&&);
int main() {
std::string s;
f(s); // T is std::string&
const std::string s2;
f(s2); // T is a const std::string&
}
As such, forward can simply look at the explicit type T to see what you really passed it. Of course, the exact implementation of doing this is non-trival, if I recall, but that's where the information is.
When you refer to a named rvalue reference, then that is indeed an lvalue. However, forward detects through the means above that it is actually an rvalue, and correctly returns an rvalue to be forwarded.
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
If std::forward used template argument deduction:
Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.
Example:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.
So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.
I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.
In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.
Short answer:
Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:
If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
If an rvalue of type _T && is passed to someFunc,T will be deduced as _T &&
Now, you can replace T with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.
If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).
Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
If std::forward used template argument deduction:
Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.
Example:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.
So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.
I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.
In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.
Short answer:
Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:
If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
If an rvalue of type _T && is passed to someFunc,T will be deduced as _T &&
Now, you can replace T with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.
If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).
Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.
In VS2010 std::forward is defined as such:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity appears to be used solely to disable template argument deduction. What's the point of purposefully disabling it in this case?
If you pass an rvalue reference to an object of type X to a template function that takes type T&& as its parameter, template argument deduction deduces T to be X. Therefore, the parameter has type X&&. If the function argument is an lvalue or const lvalue, the compiler deduces its type to be an lvalue reference or const lvalue reference of that type.
If std::forward used template argument deduction:
Since objects with names are lvalues the only time std::forward would correctly cast to T&& would be when the input argument was an unnamed rvalue (like 7 or func()). In the case of perfect forwarding the arg you pass to std::forward is an lvalue because it has a name. std::forward's type would be deduced as an lvalue reference or const lvalue reference. Reference collapsing rules would cause the T&& in static_cast<T&&>(arg) in std::forward to always resolve as an lvalue reference or const lvalue reference.
Example:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
Because std::forward(expr) is not useful. The only thing it can do is a no-op, i.e. perfectly-forward its argument and act like an identity function. The alternative would be that it's the same as std::move, but we already have that. In other words, assuming it were possible, in
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg) is semantically equivalent to arg. On the other hand, std::forward<Arg>(arg) is not a no-op in the general case.
So by forbidding std::forward(arg) it helps catch programmer errors and we lose nothing since any possible use of std::forward(arg) are trivially replaced by arg.
I think you'd understand things better if we focus on what exactly std::forward<Arg>(arg) does, rather than what std::forward(arg) would do (since it's an uninteresting no-op). Let's try to write a no-op function template that perfectly forwards its argument.
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
This naive first attempt isn't quite valid. If we call noop(0) then NoopArg is deduced as int. This means that the return type is int&& and we can't bind such an rvalue reference from the expression arg, which is an lvalue (it's the name of a parameter). If we then attempt:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
then int i = 0; noop(i); fails. This time, NoopArg is deduced as int& (reference collapsing rules guarantees that int& && collapses to int&), hence the return type is int&, and this time we can't bind such an lvalue reference from the expression std::move(arg) which is an xvalue.
In the context of a perfect-forwarding function like noop, sometimes we want to move, but other times we don't. The rule to know whether we should move depends on Arg: if it's not an lvalue reference type, it means noop was passed an rvalue. If it is an lvalue reference type, it means noop was passed an lvalue. So in std::forward<NoopArg>(arg), NoopArg is a necessary argument to std::forward in order for the function template to do the right thing. Without it, there's not enough information. This NoopArg is not the same type as what the T parameter of std::forward would be deduced in the general case.
Short answer:
Because for std::forward to work as intended(, i.e. to faitfully pass the original type info), it is meant to be used INSIDE TEMPLATE CONTEXT, and it must use the deduced type param from the enclosing template context, instead of deducing the type param by itself(, since only the enclosing templates have the chance to deduce the true type info, this will be explained in the details), hence the type param must be provided.
Though using std::forward inside non-template context is possible, it is pointless(, will be explained in the details).
And if anyone dares to try implementing std::forward to allow type deducing, he/she is doomed to fail painfully.
Details:
Example:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
Observer that arg is declared as T&&,( it is the key to deduce the true type passed, and) it is not a rvalue reference, though it has the same syntax, it is called an universal reference (Terminology coined by Scott Meyers), because T is a generic type, (likewise, in string s; auto && ss = s; ss is not a rvalue reference).
Thanks to universal reference, some type deduce magic happens when someFunc is being instantiated, specifically as following:
If an rvalue object, which has the type _T or _T &, is passed to someFunc, T will be deduced as _T &(, yeah, even if the type of X is just _T, please read Meyers' artical);
If an rvalue of type _T && is passed to someFunc,T will be deduced as _T &&
Now, you can replace T with the true type in above code:
When lvalue obj is passed:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
When rvalue obj is passed:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
And after applying reference collapse rule(, pls read Meyers' artical), we get:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
Now, you can guess what std::forwrd does eseentially is just static_cast<T>(para)(, in fact, in clang 11's implementation it is static_cast<T &&>(para), which is the same after applying reference collapsing rule). Everything works out fine.
But if you think about let std::fowrd deducing the type param by itself, you'll quickly find out that inside someFunc, std::forward literally IS NOT ABLE TO deduce the original type of arg.
If you try to make the compiler do it, it will never be deduced as _T &&(, yeah, even when arg is bind to an _T &&, it is still an lvaule obj inside someFunc, hence can only be deduceed as _T or _T &.... you really should read Meyers' artical).
Last, why should you only use std::forward inside templates? Because in non-templates context, you know exactly what type of obj you have. So, if you have an lvalue bind to an rvalue reference, and you need to pass it as an lvaule to another function, just pass it, or if you need to pass it as rvalue, just do std::move. You simply DON'T NEED std::forward inside non-template context.
This question already has answers here:
Closed 11 years ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
Possible Duplicate:
Advantages of using forward
I know what it does and when to use it but I still can't wrap my head around how it works. Please be as detailed as possible and explain when std::forward would be incorrect if it was allowed to use template argument deduction.
Part of my confusion is this:
"If it has a name, it's an lvalue" - if that's the case why does std::forward behave differently when I pass thing&& x vs thing& x?
I think the explanation of std::forward as static_cast<T&&> is confusing. Our intuition for a cast is that it converts a type to some other type -- in this case it would be a conversion to an rvalue reference. It's not! So we are explaining one mysterious thing using another mysterious thing. This particular cast is defined by a table in Xeo's answer. But the question is: Why? So here's my understanding:
Suppose I want to pass you an std::vector<T> v that you're supposed to store in your data structure as data member _v. The naive (and safe) solution would be to always copy the vector into its final destination. So if you are doing this through an intermediary function (method), that function should be declared as taking a reference. (If you declare it as taking a vector by value, you'll be performing an additional totally unnecessary copy.)
void set(const std::vector<T> & v) { _v = v; }
This is all fine if you have an lvalue in your hand, but what about an rvalue? Suppose that the vector is the result of calling a function makeAndFillVector(). If you performed a direct assignment:
_v = makeAndFillVector();
the compiler would move the vector rather than copy it. But if you introduce an intermediary, set(), the information about the rvalue nature of your argument would be lost and a copy would be made.
set(makeAndFillVector()); // set will still make a copy
In order to avoid this copy, you need "perfect forwarding", which would result in optimal code every time. If you're given an lvalue, you want your function to treat it as an lvalue and make a copy. If you're given an rvalue, you want your function to treat it as an rvalue and move it.
Normally you would do it by overloading the function set() separately for lvalues and rvalues:
set(const std::vector<T> & lv) { _v = v; }
set(std::vector<T> && rv) { _v = std::move(rv); }
But now imagine that you're writing a template function that accepts T and calls set() with that T (don't worry about the fact that our set() is only defined for vectors). The trick is that you want this template to call the first version of set() when the template function is instantiated with an lvalue, and the second when it's initialized with an rvalue.
First of all, what should the signature of this function be? The answer is this:
template<class T>
void perfectSet(T && t);
Depending on how you call this template function, the type T will be somewhat magically deduced differently. If you call it with an lvalue:
std::vector<T> v;
perfectSet(v);
the vector v will be passed by reference. But if you call it with an rvalue:
perfectSet(makeAndFillVector());
the (anonymous) vector will be passed by rvalue reference. So the C++11 magic is purposefully set up in such a way as to preserve the rvalue nature of arguments if possible.
Now, inside perfectSet, you want to perfectly pass the argument to the correct overload of set(). This is where std::forward is necessary:
template<class T>
void perfectSet(T && t) {
set(std::forward<T>(t));
}
Without std::forward the compiler would have to assume that we want to pass t by reference. To convince yourself that this is true, compare this code:
void perfectSet(T && t) {
set(t);
set(t); // t still unchanged
}
to this:
void perfectSet(T && t) {
set(std::forward<T>(t));
set(t); // t is now empty
}
If you don't explicitly forward t, the compiler has to defensively assume that you might be accessing t again and chose the lvalue reference version of set. But if you forward t, the compiler will preserve the rvalue-ness of it and the rvalue reference version of set() will be called. This version moves the contents of t, which means that the original becomes empty.
This answer turned out much longer than what I initially assumed ;-)
First, let's take a look at what std::forward does according to the standard:
§20.2.3 [forward] p2
Returns: static_cast<T&&>(t)
(Where T is the explicitly specified template parameter and t is the passed argument.)
Now remember the reference collapsing rules:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
(Shamelessly stolen from this answer.)
And then let's take a look at a class that wants to employ perfect forwarding:
template<class T>
struct some_struct{
T _v;
template<class U>
some_struct(U&& v)
: _v(static_cast<U&&>(v)) {} // perfect forwarding here
// std::forward is just syntactic sugar for this
};
And now an example invocation:
int main(){
some_struct<int> s1(5);
// in ctor: '5' is rvalue (int&&), so 'U' is deduced as 'int', giving 'int&&'
// ctor after deduction: 'some_struct(int&& v)' ('U' == 'int')
// with rvalue reference 'v' bound to rvalue '5'
// now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int&&>(v)'
// this just turns 'v' back into an rvalue
// (named rvalue references, 'v' in this case, are lvalues)
// huzzah, we forwarded an rvalue to the constructor of '_v'!
// attention, real magic happens here
int i = 5;
some_struct<int> s2(i);
// in ctor: 'i' is an lvalue ('int&'), so 'U' is deduced as 'int&', giving 'int& &&'
// applying the reference collapsing rules yields 'int&' (& + && -> &)
// ctor after deduction and collapsing: 'some_struct(int& v)' ('U' == 'int&')
// with lvalue reference 'v' bound to lvalue 'i'
// now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int& &&>(v)'
// after collapsing rules: 'static_cast<int&>(v)'
// this is a no-op, 'v' is already 'int&'
// huzzah, we forwarded an lvalue to the constructor of '_v'!
}
I hope this step-by-step answer helps you and others understand just how std::forward works.
It works because when perfect forwarding is invoked, the type T is not the value type, it may also be a reference type.
For example:
template<typename T> void f(T&&);
int main() {
std::string s;
f(s); // T is std::string&
const std::string s2;
f(s2); // T is a const std::string&
}
As such, forward can simply look at the explicit type T to see what you really passed it. Of course, the exact implementation of doing this is non-trival, if I recall, but that's where the information is.
When you refer to a named rvalue reference, then that is indeed an lvalue. However, forward detects through the means above that it is actually an rvalue, and correctly returns an rvalue to be forwarded.