15

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

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
gedamial
  • 1,498
  • 1
  • 15
  • 30

1 Answers1

23

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

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Will you explain why the copy occurs in a static_cast? – gedamial Jul 07 '16 at 17:33
  • The value category is not the same. The value category *group* is the same ("rvalue"), but the value categories differ (prvalue vs xvalue). – Kerrek SB Jul 07 '16 at 17:34
  • 2
    @gedamial: What else would it do? When you convert a `T1` to a `T2`, the data is copied (via transformation) to a new object. This can only be avoided with reference types.... ([or, for `typeid(T1) == typeid(T2)`, by using Visual Studio in its ridiculous default non-compliant mode](http://stackoverflow.com/a/37969300/560648)). – Lightness Races in Orbit Jul 07 '16 at 17:36
  • If I call `g(int{5})` then the template parameter T is equal to int&&, and when we do `static_cast(x)` it evaluates to `static_cast(x)` which is the same type as x – gedamial Jul 07 '16 at 17:38
  • 1
    @gedamial: _"If I call g(int{5}) then the template parameter T is equal to int&&"_ No, it is `int`. _"when we do static_cast(x) it evaluates to static_cast(x)"_ No it doesn't. And, even if it did, per my link above it wouldn't make a difference unless you were in MSVS non-compliant mode. – Lightness Races in Orbit Jul 07 '16 at 17:39
  • @LightnessRacesinOrbit So in g() `static_cast` becomes `static_cast` and... then? What type is `x` that we need to cast to `int`? – gedamial Jul 07 '16 at 17:49
  • 1
    @KerrekSB Is there a better word for that? I don't like value category group either since lvalue is one thing but rvalue is two things. Maybe just observable value category? – Barry Jul 07 '16 at 17:52
  • 2
    @gedamial `static_cast(e)` is the equivalent of `T t(e);` Now do you see why it's a copy? – Barry Jul 07 '16 at 17:54
  • @Barry Yes but I want to understand where the type-mismatch is. How are T and x different? – gedamial Jul 07 '16 at 17:59
  • 1
    @gedamial They're not. But `X x; X t(x);` makes a copy, whereas `X& t(x);` and `X&& t(std::move(x));` do not. – Barry Jul 07 '16 at 18:09
  • @gedamial: Why don't you click on the link I gave you. – Lightness Races in Orbit Jul 07 '16 at 18:15
  • @Barry About the types not to be different, I have another doubt. On this page https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers Scott Meyers says: "param is initialized with the literal 10, which is an rvalue (...) the universal reference param is initialized with an rvalue, so param becomes an rvalue reference – in particular, int&&" `param` in my case is `x` But, if `x` is of type T&& and `T` is of type `int`, then in `static_cast(x)` T and x are not the same!? If I call g(10), T is `int` and `x` is int&&, and are not the same – gedamial Jul 07 '16 at 18:37
  • @gedamial You're confusing types and values everywhere and I do not understand what you're saying. `static_cast(x)` would copy the `int`, yes. – Barry Jul 07 '16 at 18:55
  • @Barry If I call g(10), that literal is an rvalue so, inside the function `T` evaluates to `int` whereas the parameter `x` evaluates to `T&&` which is `int&&` So you see? `static_cast(x)` in terms of **types** should evaluate (as Scott Meyers says) to `static_cast(int&&)` – gedamial Jul 07 '16 at 18:59
  • @gedamial Huh? It's not a question of type. It's a question of efficiency. `static_cast(x)` will do an extra copy, `static_cast(x)` will not. Nowhere in that article does Meyers do this wrong. – Barry Jul 07 '16 at 19:17
  • @Barry I'm not talking about copy or not copy. I'm talking about what `T` and `x` really are! `T` is `int` and `x` is `int&&`. Is it true? If no: why? scott meyers says – gedamial Jul 07 '16 at 19:29
  • @gedamial Yes. If `x` is an rvalue of type `int`, then `T` will be `int` and `decltype(x)` will be `int&&`. – Barry Jul 07 '16 at 19:31
  • @Barry Thanks. So now that we have clarified this, we can say that in the function g() the copy occurs because `static_cast(int&&)` is like saying `X&& x;` `X t(x);` which is a valid context for a copy. **Right?** – gedamial Jul 07 '16 at 19:38
  • @gedamial I don't know what you mean by "valid context for a copy." It is a copy, period. – Barry Jul 07 '16 at 20:09
  • @Barry, OP: It's not just a question of efficiency, of course; if you replace `int` in the code in OP's original question with `unique_ptr`, it fails to compile. – Kyle Strand Jul 07 '16 at 21:02
  • 2
    @gedamial "...if `x` is of type `T&&` ... then ... `T` and `x` are not the same!?" Correct; `T&&` is not the same as `T`, because one is a reference and the other is a value. "`static_cast(int&&)` is like saying `X&& x; X t(x);` ... " Yes, because `static_cast` creates a value of type `T`, as per Lightness's first comment, and here `T` is not a reference-type so an actual `int` is created (copied). – Kyle Strand Jul 07 '16 at 21:07
  • Thank you all very much, you were brilliant guys =) – gedamial Jul 07 '16 at 21:15