template y typename en C++: dónde colocarlos y por qué son imprescindibles
\n\nSi alguna vez has escrito o leído código en C++ con plantillas, seguramente te has encontrado con una situación en la que el compilador de repente da un error sin motivo aparente. Estás seguro de que el código es correcto, pero el compilador insiste en que pongas template o typename. ¿Te suena? Bienvenido al club.
Esta pregunta es una de las más populares en Stack Overflow (miles de visitas y decenas de respuestas). Y no es de extrañar: incluso los desarrolladores experimentados a veces se confunden sobre dónde usar estas palabras clave. En este artículo, aclararemos de una vez por todas: dónde, cuándo y por qué se deben escribir template y typename en C++. Y lo más importante, entenderemos cómo no caer en la trampa.
¿Por qué surgió este problema?
\n\nLa raíz del mal está en los nombres dependientes (dependent names). Cuando escribes código con plantillas, el compilador no puede saber de inmediato con qué está tratando: si es un tipo o algo más. Esto se debe a que los parámetros de la plantilla aún no se conocen. El compilador pospone el análisis hasta el momento de la instanciación, pero necesita conocer el rol sintáctico del nombre ya en la fase de análisis inicial.
\n\nImagina que escribes:
\n\ntemplate<typename T>\nvoid foo() {\n T::something * p;\n}\n\n\n¿Qué es esto? ¿Una declaración del puntero p a T::something? ¿O una multiplicación de T::something por p? Sin información adicional, el compilador no lo sabe. Y precisamente para resolver esta ambigüedad se introdujeron las palabras clave typename y template.
La palabra clave typename: cuando el tipo no es obvio
\n\nRegla principal
\n\nLa palabra clave typename se usa para decirle al compilador: "Oye, lo que sigue es un tipo, no otra cosa". Es necesaria en los casos en que el nombre depende de un parámetro de plantilla y el compilador no puede determinar por sí mismo que se trata de un tipo.
Consideremos el ejemplo clásico:
\n\ntemplate<typename T>\nclass MyClass {\npublic:\n // Sin typename — error de compilación\n typename T::value_type getValue() const {\n return data;\n }\nprivate:\n T data;\n};\n\n\nAquí, T::value_type es un nombre dependiente. Sin typename, el compilador pensaría que value_type es un miembro de datos estático o una clase anidada, pero no un tipo. Y nosotros sabemos que es un tipo (por ejemplo, int, si T es std::vector<int>).
¿Dónde más se necesita typename?
\n\nLa regla es simple: typename es obligatorio antes de nombres de tipos dependientes en los siguientes contextos:
- \n
- En declaraciones de variables y miembros de clase \n
- En parámetros de funciones \n
- En tipos de retorno \n
- En declaraciones using (alias de tipos) \n
- En clases base (si la clase base es un tipo dependiente) \n
Ejemplo con using:
\n\ntemplate<typename T>\nclass Container {\n using Iterator = typename T::iterator; // ¡typename obligatorio!\n // ...\n};\n\n\n¿Cuándo NO se necesita typename?
\n\nHay excepciones importantes en las que typename no es necesario:
- \n
- En la lista de parámetros de la plantilla:
template<typename T>— aquítypenamees parte de la sintaxis \n - En clases base:
class Derived : public Base<T>::Nested— aquí no se necesitatypename\n - En la lista de inicialización del constructor \n
- En contextos donde el nombre es explícitamente un tipo (por ejemplo, después de
new) \n
La palabra clave template: cuando una plantilla está dentro de otra plantilla
\n\nLa esencia del problema
\n\nCon template la situación es simétrica. Se necesita cuando accedes a un miembro que es una plantilla den