I've understood how std::move
works and implemented my own version for practice only. Now I'm trying to understand how std::forward
works:
I've implemented this so far:
#include <iostream>
template <typename T>
T&& forward_(T&& x)
{
return static_cast<T&&>(x);
}
/*template <typename T>
T&& forward_(T& x)
{
return static_cast<T&&>(x);
}*/
void incr(int& i)
{
++i;
}
void incr2(int x)
{
++x;
}
void incr3(int&& x)
{
++x;
}
template <typename T, typename F>
void call(T&& a, F func)
{
func(forward_<T>(a));
}
int main()
{
int i = 10;
std::cout << i << '\n';
call(i, incr);
std::cout << i << '\n';
call(i, incr2);
std::cout << i << '\n';
call(0, incr3); // Error: cannot bind rvalue reference of type int&& to lvalue of type int.
std::cout << "\ndone!\n";
}
Why must I provide the overloaded forward(T&)
version taking an lvalue reference? As I understand it a forwarding reference can yield an lvalue or an rvalue depending on the type of its argument. So passing the prvalue literal 0
to call
along with the incr3
function that takes an rvalue reference of type int&&
normally doesn't need forward<T>(T&)
?!
If I un-comment the forward_(T&)
version it works fine!?
I'm still confused about: why if I only use the forward_(T&)
version does it work for any value category? Then what is the point in having the one taking a forwarding reference forward_(T&&)
?
If I un-comment the version taking lvalue reference to T&
and the one taking forwarding reference T&&
then the code works fine and I've added some messages inside both to check which one called. the result is the the one with T&&
never called!
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
I mean running the same code in the driver program I've shown above.
A T&&
reference stops being a forwarding reference if you manually specify T
(instead of letting the compiler deduce it). If the T
is not an lvalue reference, then T&&
is an rvalue reference and won't accept lvalues.
For example, if you do forward_<int>(...)
, then the parameter is an rvalue reference and ...
can only be an rvalue.
But if you do forward_(...)
, then the parameter is a forwarding reference and ...
can have any value category. (Calling it like this makes no sense though, since forward_(x)
will have the same value category as x
itself.)