3
votes

I was playing with std::is_same utility function in combination with rvalue and lvalue reference and came across a weird behavior.

Consider this function template which checks the type of variable t.

I am using VS 2013 :

struct Test {};

template < class T> void h(T && t)
{ 
    cout << " Is type &&: " << std::is_same<decltype(t), T &&>::value << endl;
    cout << " Is type &: " << std::is_same<decltype(t), T &>::value << endl;
}

I observe the following outputs :

h(Test()); // is type && : 1  is type & : 0

which is normal for now as Test() is a temporary object, and the universal reference in parameter of h resolves to a r value reference (&& && = &&)

but consider this :

Test myTest;
h(myTest); // is type && : 1  is type & : 1 !!!

same result if i write :

Test &myTest = Test():
h(myTest); // is type && : 1  is type & : 1 !!!

and same with :

Test &&myTest = Test():
h(myTest); // is type && : 1  is type & : 1 !!!

AM I missing something ? It looks like a complete mess to me :) Are the features rvalue reference / decltype not fully supported in VS 2013?

thanks for your help

Romain

2
Looks completely correct to me. - T.C.
How can you be both an lvalue and rvalue reference? - Al227
Hint: in the latter two cases, T is Test&. - T.C.
Look for reference collapsing rules. - 101010
In all cases, you call h<Test&>, and so T&& is Test&. - Jarod42

2 Answers

4
votes

Romain, it is easy enough. Let's condider following case:

Test myTest;
h(myTest); // is type && : 1  is type & : 1 !!!

Inside h, T is Test&, and t is of type Test& &&, that is, Test&. When you do the test, std::is_same<decltype(t), T &&> is true, since Test& && is Test&, and t type is Test&.

std::is_same<decltype(t), T &> is also true, since t type is Test&, and T& is Test&&, which is Test&.

Careful application of reference collapsing rules helps here.

A bit on why inside h() T is of type Test&. The reason is that it is the only type of T which will match the actual argument. T can not be simply Test, since Test&& will not bind(match) lvalue of type Test (as this is the argument type). However, Test& && will, because of reference collapsing rules.

3
votes

I will start here:

template < class T> void h(T && t)
{ 
  std::cout << " Is type &&: " << std::is_same<decltype(t), T &&>::value << '\n';
  std::cout << " Is type &: " << std::is_same<decltype(t), T &>::value << '\n';
}

In the first test, you ask if decltype(t) is the same a T&&.

For a variable, decltype(variable) is the declaration type of the variable. Look at how t is declared -- it is T&&.

Not surpisingly, decltype(t) is the same as T&&, because T&& t is declared as a T&&.

This is true regardless of what T is. So no matter what type T you pass, you'll get true on that line.

Grab this. Understand this. No type you can match for T could make the first line ever not print 1. If you get confused about the first line, come back to the fact that it is doing std::is_same< T&&, T&& >::value. Whatever the rules for what T&& is, both sides will be the same.

But wait, you say, isn't T&& an rvalue reference? Well, usually. If type type T is a reference type, then reference collapsing rules apply. T&& when T=X& becomes X&, not X&&. lvalue reference wins out over rvalue reference.

This, with special deduction rules, is what powers forwarding references (aka universal references).

When you pass an lvalue of type X to h, it deduces T=X&.

When you pass an rvalue of type X to h, it deduces T=X.

In the first case, T&& becomes X&, and in the second it becomes X&&.

In your concrete case, Test() is an rvalue of type Test, which means h(Test()) deduces T=Test, and we get T&&=Test&& while T&=Test&. The code prints 1 then 0, as T&& (aka decltype(t), aka Test&&) is the same as T&&, but not T&.

In the second concrete case, h(myTest) has myTest as an lvalue (even if it was declared as Test&&, it is an lvalue, as it has a name). Then T is Test&, and T&& is Test& and T& is Test&. The function prints out 1 then 1, as T&& (aka decltype(t), aka Test&) is the same as T&& and T&.

To fix your problem, do this:

template < class T> void h(T && t)
{ 
  using rawT = std::remove_reference_t<T>;
  std::cout << " Is type &&: " << std::is_same<decltype(t), rawT &&>::value << '\n';
  std::cout << " Is type &: " << std::is_same<decltype(t), rawT &>::value << '\n';
}

and you get the output you expect.