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