abraham_hilbert abraham_hilbert - 28 days ago 8
C++ Question

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 when an r-value reference remains as an r-value reference and an l-value reference as 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?

Answer

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