Let's have a function called Y that overloads:
void Y(int& lvalue)
{ cout << "lvalue!" << endl; }
void Y(int&& rvalue)
{ cout << "rvalue!" << endl; }
Now, let's define a template function that acts like std::forward
template<class T>
void f(T&& x)
{
Y( static_cast<T&&>(x) ); // Using static_cast<T&&>(x) like in std::forward
}
Now look at the main()
int main()
{
int i = 10;
f(i); // lvalue >> T = int&
f(10); // rvalue >> T = int&&
}
As expected, the output is
lvalue!
rvalue!
Now come back to the template function f()
and replace static_cast<T&&>(x)
with static_cast<T>(x)
. Let's see the output:
lvalue!
rvalue!
It's the same! Why? If they are the same, then why std::forward<>
returns a cast from x
to T&&
?
std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues , and lvalues to be passed on as lvalues .
The static_cast operator converts variable j to type float . This allows the compiler to generate a division with an answer of type float . All static_cast operators resolve at compile time and do not remove any const or volatile modifiers.
static_cast MAY take time at run-time. For example, if you convert int to float then work is required. Usually casting pointers does not require any run-time cost.
No, these are called cast operators.
The lvalue vs rvalue classification remains the same, but the effect is quite different (and the value category does change - although not in an observable way in your example). Let's go over the four cases:
template<class T>
void f(T&& x)
{
Y(static_cast<T&&>(x));
}
template<class T>
void g(T&& x)
{
Y(static_cast<T>(x));
}
If we call f
with an lvalue, T
will deduce as some X&
, so the cast reference collapses X& && ==> X&
, so we end up with the same lvalue and nothing changes.
If we call f
with an rvalue, T
will deduce as some X
so the cast just converts x
to an rvalue reference to x
, so it becomes an rvalue (specifically, an xvalue).
If we call g
with an lvalue, all the same things happen. There's no reference collapsing necessary, since we're just using T == X&
, but the cast is still a no-op and we still end up with the same lvalue.
But if we call g
with an rvalue, we have static_cast<T>(x)
which will copy x
. That copy is an rvalue (as your test verifies - except now it's a prvalue instead of an xvalue), but it's an extra, unnecessary copy at best and would be a compilation failure (if T
is movable but noncopyable) at worst. With static_cast<T&&>(x)
, we were casting to a reference, which doesn't invoke a copy.
So that's why we do T&&
.
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