mi pregunta de hoy es bastante simple: ¿por qué el compilador no puede inferir parámetros de plantilla de constructores de clases, tanto como puede hacerlo a partir de parámetros de función? Por ejemplo, ¿por qué no puede ser válido el siguiente código?
template<typename obj>
class Variable {
obj data;
public: Variable(obj d)
{
data = d;
}
};
int main()
{
int num = 2;
Variable var(num); //would be equivalent to Variable<int> var(num),
return 0; //but actually a compile error
}
Como digo, entiendo que esto no es válido, entonces mi pregunta es ¿por qué no lo es? ¿Permitir esto crearía grandes agujeros sintácticos? ¿Hay alguna instancia en la que uno no quiera esta funcionalidad (donde inferir un tipo causaría problemas)? Solo estoy tratando de comprender la lógica detrás de permitir la inferencia de plantillas para funciones, pero no para clases construidas adecuadamente.
template<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
Respuestas:
Creo que no es válido porque el constructor no siempre es el único punto de entrada de la clase (estoy hablando de constructor de copia y operador =). Entonces, suponga que está usando su clase así:
No estoy seguro de si sería tan obvio para el analizador saber qué tipo de plantilla es MyClass pm;
No estoy seguro de si lo que dije tiene sentido, pero siéntase libre de agregar algún comentario, esa es una pregunta interesante.
C ++ 17
Se acepta que C ++ 17 tendrá deducción de tipo de los argumentos del constructor.
Ejemplos:
Papel aceptado .
fuente
MyClass *pm
aquí no sería válido por la misma razón que una función declaradatemplate <typename T> void foo();
no se puede llamar sin una especialización explícita.No puede hacer lo que pide por razones que otras personas han abordado, pero puede hacer esto:
que para todos los propósitos y propósitos es lo mismo que pides. Si te encanta la encapsulación, puedes hacer que make_variable sea una función miembro estática. Eso es lo que la gente llama constructor con nombre. Entonces, no solo hace lo que quieres, sino que casi se llama lo que quieres: el compilador está infiriendo el parámetro de plantilla del constructor (nombrado).
NB: cualquier compilador razonable optimizará el objeto temporal cuando escriba algo como
fuente
auto v = make_variable(instance)
para que no tenga que especificar el tipostatic
miembro ... piensa en eso por solo un segundo. Aparte de eso: las funciones de creación gratuitas fueron de hecho la solución, pero es un montón de repetición redundante, que mientras lo escribe, sabe que no debería tener que hacerlo porque el compilador tiene acceso a toda la información que está repitiendo. .. y afortunadamente C ++ 17 lo canoniza.En la era ilustrada de 2016, con dos nuevos estándares en nuestro haber desde que se hizo esta pregunta y uno nuevo a la vuelta de la esquina, lo crucial que debe saber es que los compiladores que admiten el estándar C ++ 17 compilarán su código tal como está. .
Deducción de argumento de plantilla para plantillas de clase en C ++ 17
Aquí (cortesía de una edición de Olzhas Zhumabek de la respuesta aceptada) está el documento que detalla los cambios relevantes al estándar.
Abordar preocupaciones de otras respuestas
La respuesta mejor calificada actual
Esta respuesta señala que el "constructor de copias y
operator=
" no conocerían las especializaciones de plantilla correctas.Esto es una tontería, porque el constructor de copia estándar y
operator=
solo existe para un tipo de plantilla conocido :Aquí, como señalé en los comentarios, no hay razón para
MyClass *pm
ser una declaración legal con o sin la nueva forma de inferencia:MyClass
no es un tipo (es una plantilla), por lo que no tiene sentido declarar un puntero de tipoMyClass
. Aquí hay una forma posible de corregir el ejemplo:Aquí, ya
pm
es del tipo correcto, por lo que la inferencia es trivial. Además, es imposible mezclar tipos accidentalmente al llamar al constructor de copias:Aquí,
pm
habrá un puntero a una copia dem
. Aquí,MyClass
se está construyendo una copia a partir de lom
que es de tipoMyClass<string>
(y no del tipo inexistenteMyClass
). Por lo tanto, en el punto dondepm
se infiere 's tipo, no es información suficiente para saber que el tipo de plantilla dem
, y por lo tanto el tipo de plantilla depm
, esstring
.Además, lo siguiente siempre generará un error de compilación :
Esto se debe a que la declaración del constructor de copia no tiene plantilla:
Aquí, el tipo de plantilla del argumento del constructor de copia coincide con el tipo de plantilla de la clase en general; es decir, cuando
MyClass<string>
se instancia,MyClass<string>::MyClass(const MyClass<string>&);
se instancia con él, y cuandoMyClass<int>
se instancia,MyClass<int>::MyClass(const MyClass<int>&);
se instancia. A menos que se especifique explícitamente o se declare un constructor con plantilla, no hay razón para que el compilador cree una instanciaMyClass<int>::MyClass(const MyClass<string>&);
, lo que obviamente sería inapropiado.La respuesta de Cătălin Pitiș
Pitiș da un ejemplo deduciendo
Variable<int>
yVariable<double>
luego dice:Como se señaló en el ejemplo anterior, en
Variable
sí mismo no es un nombre de tipo, aunque la nueva característica hace que parezca uno sintácticamente.Pitiș luego pregunta qué pasaría si no se proporciona un constructor que permita la inferencia adecuada. La respuesta es que no se permite ninguna inferencia, porque la inferencia se activa mediante la llamada al constructor . Sin una llamada al constructor, no hay inferencia .
Esto es similar a preguntar qué versión de
foo
se deduce aquí:La respuesta es que este código es ilegal, por la razón indicada.
La respuesta de MSalter
Esta es, por lo que puedo decir, la única respuesta que plantea una preocupación legítima sobre la función propuesta.
El ejemplo es:
La pregunta clave es, ¿el compilador selecciona aquí el constructor de tipo inferido o el constructor de copia ?
Al probar el código, podemos ver que el constructor de copia está seleccionado. Para ampliar el ejemplo :
No estoy seguro de cómo la propuesta y la nueva versión del estándar especifican esto; parece estar determinado por "guías de deducción", que son un nuevo estándar que todavía no entiendo.
Tampoco estoy seguro de por qué la
var4
deducción es ilegal; el error del compilador de g ++ parece indicar que la declaración se está analizando como una declaración de función.fuente
var4
es solo un caso del "análisis más molesto" (no relacionado con la deducción de arg de la plantilla). Solíamos usar paréntesis adicionales para esto, pero en estos días creo que usar tirantes para denotar de manera inequívoca la construcción es el consejo habitual.Variable var4(Variable(num));
se trata como una declaración de función? Si es así, ¿por qué esVariable(num)
una especificación de parámetro válida?Aún falta: hace que el siguiente código sea bastante ambiguo:
fuente
Suponiendo que el compilador sea compatible con lo que solicitó. Entonces este código es válido:
Ahora, tengo el mismo nombre de tipo (Variable) en el código para dos tipos diferentes (Variable y Variable). Desde mi punto de vista subjetivo, afecta bastante la legibilidad del código. Tener el mismo nombre de tipo para dos tipos diferentes en el mismo espacio de nombres me parece engañoso.
Actualización posterior: Otra cosa a considerar: especialización de plantilla parcial (o completa).
¿Qué pasa si me especializo en Variables y no proporciono un constructor como usted espera?
Entonces yo tendría:
Entonces tengo el código:
¿Qué debe hacer el compilador? ¿Usar la definición de clase de Variable genérica para deducir que es Variable, luego descubrir que Variable no proporciona un constructor de parámetros?
fuente
Los estándares C ++ 03 y C ++ 11 no permiten la deducción de argumentos de plantilla de los parámetros pasados al constructor.
Pero hay una propuesta para la "Deducción de parámetros de plantilla para constructores" para que pueda obtener lo que está pidiendo pronto. Editar: de hecho, esta característica ha sido confirmada para C ++ 17.
Ver: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html y http://www.open-std.org/jtc1/sc22/wg21/docs/ papers / 2015 / p0091r0.html
fuente
Muchas clases no dependen de los parámetros del constructor. Solo hay unas pocas clases que tienen un solo constructor y se parametrizan en función de los tipos de este constructor.
Si realmente necesita una inferencia de plantilla, use una función auxiliar:
fuente
La deducción de tipos se limita a las funciones de plantilla en C ++ actual, pero desde hace mucho tiempo se sabe que la deducción de tipos en otros contextos sería muy útil. Por lo tanto, C ++ 0x's
auto
.Si bien exactamente lo que sugieres no será posible en C ++ 0x, lo siguiente muestra que puedes acercarte bastante:
fuente
Tiene razón, el compilador podría adivinar fácilmente, pero no está en el estándar o en C ++ 0x que yo sepa, por lo que tendrá que esperar al menos 10 años más (tasa de respuesta fija de los estándares ISO) antes de que los proveedores del compilador agreguen esta función
fuente
Veamos el problema con referencia a una clase con la que todos deberían estar familiarizados - std :: vector.
En primer lugar, un uso muy común de vector es usar el constructor que no toma parámetros:
En este caso, obviamente no se puede realizar ninguna inferencia.
Un segundo uso común es crear un vector de tamaño predeterminado:
Aquí, si se usara inferencia:
obtenemos un vector de ints, no de cadenas, ¡y presumiblemente no tiene el tamaño!
Por último, considere los constructores que toman varios parámetros, con "inferencia":
¿Qué parámetro debería usarse para la inferencia? Necesitaríamos alguna forma de decirle al compilador que debería ser el segundo.
Con todos estos problemas para una clase tan simple como un vector, es fácil ver por qué no se usa la inferencia.
fuente
Haciendo del ctor una plantilla, la Variable puede tener solo una forma pero varios ctors:
¿Ver? No podemos tener varios miembros de Variable :: data.
fuente
Consulte La deducción del argumento de la plantilla de C ++ para obtener más información al respecto.
fuente