Search code examples
c++c++14rvalue-referencelvaluetype-deduction

Type deduction while using universal references


Can someone help me to understand why the output of the following code

template< typename T >
void check()
{
  std::cout << "unknow type" << std::endl;
}

template<>
void check<int>()
{
  std::cout << "int" << std::endl;
}

template<>
void check<int&>()
{
  std::cout << "int&" << std::endl;
}

template<>
void check<int&&>()
{
  std::cout << "int&&" << std::endl;
}

template< typename T >
void bar( T&& a )
{
  check<T>();
}

int main()
{
  bar(0);

  int a = 0;
  bar( a );
}

is

int
int&

and not

int&&
int&

From my point of view, it seems more intuitive that an r-value reference remains as an r-value reference and an l-value reference an l-value reference, However, it seems that only l-value references remains as l-value references and r-values become non-reference values. What is the motivation/idea behind this?


Solution

  • bar(0); calls the specialization bar<int>(int&&) i.e. T is deduced as int, so check<T>() is check<int>(). The parameter type is T&& which is int&&, but that's the type of the parameter, not the type T.

    This is entirely consistent with non-forwarding references. If you define:

    template<typename T> void baz(T&);
    

    and you call it with an lvalue of type int then T is deduced as int, not int&

    The only thing that's special about forwarding references like your example uses is that for T&& the type can be deduced as an lvalue reference, call it R, in which case the parameter type is R&& which is the same as add_rvalue_reference_t<R> which is just R. So for the call bar(a) you call the specialization bar<int&>(int&) i.e. T is deduced as int&

    When you call bar<int&&>(0) with an explicit template argument list there is no argument deduction, and so T is substituted by int&&, so the parameter type T&& is add_rvalue_reference_t<int&&> which is just int&&.