Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 459
gedamial Avatar asked Jul 07 '16 17:07

gedamial


People also ask

What does std :: forward do?

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 .

What is Static_cast INT?

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.

Does Static_cast take time?

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.

Is Static_cast a template?

No, these are called cast operators.


1 Answers

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

like image 198
Barry Avatar answered Oct 16 '22 04:10

Barry