gedamial gedamial - 3 months ago 12
C++ Question

Why does std::forward return static_cast<T&&> and not static_cast<T>?

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&&
?

Answer

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&&.

Comments