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.
Related
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.
An example here for std::forward,
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
Output:
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
mentions that
the fact that all named values (such as function parameters) always
evaluate as lvalues (even those declared as rvalue references)
Whereas, the typical move constructor looks like
ClassName(ClassName&& other)
: _data(other._data)
{
}
which looks like _data(other._data) should invoke the move constructor of _data's class. But, how is it possible without using std::forward? In other words, shouldn't it be
ClassName(ClassName&& other)
: _data(std::forward(other._data))
{
}
?
Because, as pointed out in std:forward case,
all then named values should evaluate as lvalue
I more and more like C++ because of the depth of issue like this and the fact that the language is bold enough to provide such features :) Thank you!
A typical move constructor looks like this (assuming it is explicitly implemented: you might want to prefer = default):
ClassName::ClassName(ClassName&& other)
: _data(std::move(other._data)) {
}
Without the std::move() the member is copied: since it has a name other is an lvalue. The object the reference is bound to is an rvalue or an object considered as such, however.
std::forward<T>(obj) is always used with an explicit template argument. In practice the type is that deduced for a forwarding reference. These look remarkably like rvalue references but are something entirely different! In particular, a forwarding reference may refer to an lvalue.
You may be interested in my Two Daemons article which describes the difference in detail.
std::forward should be used with a forwarding reference.
std::move should be used with an rvalue reference.
There is nothing particular about constructors. The rules apply the same to any function, member function or constructor.
The most important thing is to realize when you have a forwarding reference and when you have an rvalue reference. They look similar but are not.
A forwarding reference is always in the form:
T&& ref
for T some deduced type.
For instance, this is a forwarding reference:
template <class T>
auto foo(T&& ref) -> void;
All these are rvalue references:
auto foo(int&& ref) -> void; // int not deduced
template <class T>
auto foo(const T&& ref); // not in form `T&&` (note the const)
template <class T>
auto foo(std::vector<T>&& ref) -> void; // not in form `T&&`
template <class T>
struct X {
auto foo(T&& ref) -> T; // T not deduced. (It was deduced at class level)
};
For more please check this excellent in-depth article by Scott Meyers with the note that when the article was written the term "universal reference" was used (actually introduced by Scott himself). Now it is agreed that "forwarding reference" better describes it's purpose and usage.
So your example should be:
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
}
as other is an rvalue reference because ClassName is not a deduced type.
This Ideone example should make things pretty clear for you. If not, keep reading.
The following constructor accepts Rvalues only. However, since the argument "other" got a name it lost its "rvalueness" and now is a Lvalue. To cast it back to Rvalue, you have to use std::move. There's no reason to use std::forward here because this constructor does not accept Lvalues. If you try to call it with a Lvalue, you will get compile error.
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
// If you don't use move, you could have:
// cout << other._data;
// And you will notice "other" has not been moved.
}
The following constructor accepts both Lvalues and Rvalues. Scott Meyers called it "Universal Rerefences", but now it's called "Forwarding References". That's why, here, it's a must to use std::forward so that if other was an Rvalue, _data constructor will get called with an Rvalue. If other was an Lvalue, _data will be constructed with an Lvalue. That's why it's called perfect-forwarding.
template<typename T>
ClassName(T&& other)
: _data(std::forward<decltype(_data)>(other._data))
{
}
I've tried to use your constructors as an example so you could understand, but this is not specific to constructors. This applies to functions as well.
With the first example tho, since your first constructor only accepts Rvalues, you could perfectly use std::forward instead, and both would do the same thing. But it's best not to do it, because people may think that your constructor accepts a forwarding reference, when it actually doesn't.
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.