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 reference
tipo se ve completamente diferente al de otros T
s. Es cierto que no cambia el tipo de reference
un tipo a algo diferente pero, sin embargo, podría suceder.
Ahora, ¿qué sucede si escribe sus propias plantillas con esta test
plantilla? 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>::ptr
sea un tipo. Pero el compilador no lo sabe y, de hecho, incluso el estándar le aconseja que espere lo contrario, test<T>::ptr
no es un tipo. Para decirle al compilador qué espera, debe agregar un typename
antes. 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 typename
antes 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).