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.)
It is clear that you wander why having two versions of std::forward
; one takes an l-value reference to the type parameter T&
and the other takes a universal reference (forwarding) to the type parameter. T&&
.
In your case you are using forward_
from inside the function template call
which has forwarding reference too. The problem is that even that function call
called with an rvalue it always uses forward_
for an lvalue because there's no way that call
can pass its arguments without an object (parameter). Remember that a name of an object is an lvlaue even if it's initialized from an r-value. That is why always in your example forward_(T&)
is called.
Here is an example:
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);
}
int main()
{
int i = 10;
forward_(i); // forward(T&) (1)
forward_(5); // forward(T&&) (2)
forward_("Hi"); // forward(T&) (3)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With