El secreto radica en el hecho de que una plantilla puede especializarse para algunos tipos. Esto significa que también puede definir la interfaz completamente diferente para varios tipos. Por ejemplo, puedes escribir:
template<typename T>
struct test {
typedef T* ptr;
};
template<> // complete specialization
struct test<int> { // for the case T is int
T* ptr;
};
Uno podría preguntarse por qué es esto útil y de hecho: eso realmente parece inútil. Pero tenga en cuenta que, por ejemplo, std::vector<bool>el referencetipo se ve completamente diferente al de otros Ts. Es cierto que no cambia el tipo de referenceun tipo a algo diferente pero, sin embargo, podría suceder.
Ahora, ¿qué sucede si escribe sus propias plantillas con esta testplantilla? Algo como esto
template<typename T>
void print(T& x) {
test<T>::ptr p = &x;
std::cout << *p << std::endl;
}
parece estar bien para ti porque esperas que test<T>::ptrsea un tipo. Pero el compilador no lo sabe y, de hecho, incluso el estándar le aconseja que espere lo contrario, test<T>::ptrno es un tipo. Para decirle al compilador qué espera, debe agregar un typenameantes. La plantilla correcta se ve así
template<typename T>
void print(T& x) {
typename test<T>::ptr p = &x;
std::cout << *p << std::endl;
}
En pocas palabras: debe agregar typenameantes cada vez que use un tipo anidado de una plantilla en sus plantillas. (Por supuesto, solo si se usa un parámetro de plantilla de su plantilla para esa plantilla interna).