T&& in templated function and class

603 Views Asked by At

I've just stumbled upon that T&& in class and function means different things.
In function:

template<class T> void f(T&& t){};   // t is R or L-value
...
int i=0; 
f(i);   // t in f is lvalue
f(42);  // t in f is rvalue  

In class:

template<class T> 
struct S { 
       S(T&& t){}  // t is only R-value? 
};
...
int i;
S<int> ss(i);   // compile error - cannot bind lvalue to ‘int&&’

Does this means that if we haveT&& t in class, than t will be only rvalue?
Can some one point me where I can get more info about this?
Does it means that I need to write two method overloads for L and R-values?

ANSWER
As Alf's example shows, t in function and class can be Lvalue or Rvalue.

2

There are 2 best solutions below

0
On BEST ANSWER

In your function T is deduced from the actual argument. The main use for that particular combination is perfect forwarding. In the class template T is not deduced, it must be specified.

E.g., this compiles nicely with both g++ and msvc:

template<class T> 
struct S { 
       S(T&& t){}
};

int main()
{
    int i;
    S< int& > ss(i);
}
0
On

You're dealing with template argument deduction here.

By using f without explicitly defining the template argument, C++ compilers now must decide what the template argument type T is from the parameters you pass it.

The rules for template argument deduction with && types are special to allow for perfect forwarding. When you use f(i), the T is deduced as T&. Thus, the parameter t is of type T& &&, which collapses to T&. However, when you use f(42), the type T is deduced as T&&, and thus t is T&& &&, which collapses to T&&.

Once you force T to be a specific type, all that effectively goes away. The collapsing can still happen, but because you used S<int>, then t will be of type int&&. S<int> ss(i) is effectively the equivalent of f<int>(i), which is also not legal. And since template argument deduction only works with functions and not types, you have to do something like this for S if you want perfect forwarding:

template<class T> 
struct S { 
    template<class U>
    S(U&& t){}
};

Of course, you can use SFINAE methods and template metaprogramming to ensure that the constructor template can only be instantiated if the basic type of T and U are the same.