El problema aquí es que, puesto que la clase está en templated T
, en el constructor Foo(T&&)
que estamos no realizando el tipo de deducción; Siempre tenemos una referencia de valor r. Es decir, el constructor para Foo
realmente se ve así:
Foo(int&&)
Foo(2)
funciona porque 2
es un prvalue.
Foo(x)
no lo hace porque x
es un valor que no se puede unir int&&
. Podrías hacer std::move(x)
para lanzarlo al tipo apropiado ( demo )
Foo<int&>(x)
funciona bien porque el constructor se Foo(int&)
debe a reglas de colapso de referencia; inicialmente es lo Foo((int&)&&)
que colapsa Foo(int&)
según el estándar.
Con respecto a su guía de deducción "redundante": Inicialmente hay una guía de deducción de plantilla predeterminada para el código que básicamente actúa como una función auxiliar de esta manera:
template<typename T>
struct Foo {
Foo(T&&) {}
};
template<typename T>
Foo<T> MakeFoo(std::add_rvalue_reference_t<T> value)
{
return Foo<T>(std::move(value));
}
//...
auto f = MakeFoo(x);
Esto se debe a que el estándar dicta que este método de plantilla (ficticio) tiene los mismos parámetros de plantilla que la clase (Just T
) seguido de cualquier parámetro de plantilla como el constructor (ninguno en este caso; el constructor no tiene plantilla). Entonces, los tipos de los parámetros de la función son los mismos que los del constructor. En nuestro caso, después de crear instancias Foo<int>
, el constructor se ve como Foo(int&&)
una referencia de valor en otras palabras. De ahí el uso de lo add_rvalue_reference_t
anterior.
Obviamente esto no funciona.
Cuando agregó su guía de deducción "redundante":
template<typename T>
Foo(T&&) -> Foo<T>;
Permitieron que el compilador distinguir que, a pesar de cualquier tipo de referencia correspondiente a T
en el constructor ( int&
, const int&
o int&&
etc.), que pretendía el tipo inferido para la clase de ser sin referencia (justo T
). Esto se debe a que de repente estamos realizando inferencia de tipos.
Ahora generamos otra función auxiliar (ficticia) que se ve así:
template<class U>
Foo<U> MakeFoo(U&& u)
{
return Foo<U>(std::forward<U>(u));
}
// ...
auto f = MakeFoo(x);
(Nuestras llamadas al constructor se redirigen a la función auxiliar con el propósito de deducir el argumento de la plantilla de clase, por lo que se Foo(x)
convierte en MakeFoo(x)
).
Esto permite U&&
volverse int&
y T
volverse simplementeint