Dada la siguiente estructura de plantilla:
template<typename T>
struct Foo {
Foo(T&&) {}
};
Esto compila, y T
se deduce que es int
:
auto f = Foo(2);
Pero esto no se compila: https://godbolt.org/z/hAA9TE
int x = 2;
auto f = Foo(x);
/*
<source>:12:15: error: no viable constructor or deduction guide for deduction of template arguments of 'Foo'
auto f = Foo(x);
^
<source>:7:5: note: candidate function [with T = int] not viable: no known conversion from 'int' to 'int &&' for 1st argument
Foo(T&&) {}
^
*/
Sin embargo, Foo<int&>(x)
es aceptado.
Pero cuando agrego una guía de deducción definida por el usuario aparentemente redundante, funciona:
template<typename T>
Foo(T&&) -> Foo<T>;
¿Por qué no se T
puede deducir int&
sin una guía de deducción definida por el usuario?
c++
templates
language-lawyer
jtbandes
fuente
fuente
Foo<T<A>>
Respuestas:
Creo que la confusión aquí surge porque hay una excepción específica para las guías de deducción sintetizadas con respecto al reenvío de referencias.
Es cierto que la función candidata para el propósito de la deducción de argumentos de plantilla generada a partir del constructor y la generada a partir de la guía de deducción definida por el usuario se ven exactamente iguales, es decir:
pero para el generado por el constructor,
T&&
es una referencia de valor simple, mientras que es una referencia de reenvío en el caso definido por el usuario. Esto se especifica en [temp.deduct.call] / 3 del estándar C ++ 17 (borrador N4659, enfatice el mío):Por lo tanto, el candidato sintetizado a partir del constructor de la clase no deducirá
T
como si fuera una referencia de reenvío (que podría deducirseT
como una referencia de valor de l, por lo queT&&
también es una referencia de valor de l), sino que solo deduciráT
como no referencia, por lo queT&&
siempre es Una referencia de valor.fuente
Foo<int>(...)
simplementeFoo(...)
, lo que no es el caso con el reenvío de referencias (que podría deducirse en suFoo<int&>
lugar.El problema aquí es que, puesto que la clase está en templated
T
, en el constructorFoo(T&&)
que estamos no realizando el tipo de deducción; Siempre tenemos una referencia de valor r. Es decir, el constructor paraFoo
realmente se ve así:Foo(2)
funciona porque2
es un prvalue.Foo(x)
no lo hace porquex
es un valor que no se puede unirint&&
. Podrías hacerstd::move(x)
para lanzarlo al tipo apropiado ( demo )Foo<int&>(x)
funciona bien porque el constructor seFoo(int&)
debe a reglas de colapso de referencia; inicialmente es loFoo((int&)&&)
que colapsaFoo(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:
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 instanciasFoo<int>
, el constructor se ve comoFoo(int&&)
una referencia de valor en otras palabras. De ahí el uso de loadd_rvalue_reference_t
anterior.Obviamente esto no funciona.
Cuando agregó su guía de deducción "redundante":
Permitieron que el compilador distinguir que, a pesar de cualquier tipo de referencia correspondiente a
T
en el constructor (int&
,const int&
oint&&
etc.), que pretendía el tipo inferido para la clase de ser sin referencia (justoT
). Esto se debe a que de repente estamos realizando inferencia de tipos.Ahora generamos otra función auxiliar (ficticia) que se ve así:
(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 enMakeFoo(x)
).Esto permite
U&&
volverseint&
yT
volverse simplementeint
fuente
x
es un valor al que no se puede unirint&&
", pero alguien que no entienda quedará perplejo yFoo<int&>(x)
podría funcionar, pero no se descubrió automáticamente. Creo que todos queremos una comprensión más profunda de por qué.