Oficialmente, ¿para qué sirve typename?

En ocasiones, he visto algunos mensajes de error realmente indescifrables escupidos por gcc al usar plantillas … Específicamente, he tenido problemas donde las declaraciones aparentemente correctas estaban causando errores de comstackción muy extraños que mágicamente desaparecieron al agregar como prefijo la palabra clave “typename” a el comienzo de la statement … (Por ejemplo, la semana pasada, declaraba dos iteradores como miembros de otra clase con plantillas y tenía que hacer esto) …

¿Cuál es la historia sobre typename?

La siguiente es la cita del libro de Josuttis:

La palabra clave typename se introdujo para especificar que el identificador que sigue es un tipo. Considere el siguiente ejemplo:

template  Class MyClass { typename T::SubType * ptr; ... }; 

Aquí, typename se usa para aclarar que SubType es un tipo de clase T. Por lo tanto, ptr es un puntero al tipo T :: SubType. Sin typename, SubType se consideraría un miembro estático. Así

 T::SubType * ptr 

sería una multiplicación de valor Subtipo de tipo T con ptr.

La publicación BLog de Stan Lippman sugiere:

Stroustrup reutilizó la palabra clave de clase existente para especificar un parámetro de tipo en lugar de introducir una nueva palabra clave que podría romper los progtwigs existentes. No se trató de considerar una nueva palabra clave, sino que no se consideró necesaria dada su posible interrupción. Y hasta el estándar ISO-C ++, esta era la única forma de declarar un parámetro de tipo.

Así que, básicamente, Stroustrup reutilizó la palabra clave class sin introducir una nueva palabra clave que luego se modifique en la norma por los siguientes motivos

Como el ejemplo dado

 template  class Demonstration { public: void method() { T::A *aObj; // oops … // … }; 

la gramática del lenguaje malinterpreta T::A *aObj; como una expresión aritmética por lo que se introduce una nueva palabra clave llamada typename

 typename T::A* a6; 

instruye al comstackdor a tratar el enunciado siguiente como una statement.

Dado que la palabra clave estaba en la nómina, diablos, ¿por qué no solucionar la confusión causada por la decisión original de reutilizar la palabra clave class?

Por eso tenemos ambos

Puedes echarle un vistazo a esta publicación , definitivamente te será útil, simplemente extraje todo lo que pude

Considera el código

 template somefunction( T * arg ) { T::sometype x; // broken . . 

Desafortunadamente, no se requiere que el comstackdor sea psíquico, y no sabe si T :: tipo terminará refiriéndose a un nombre de tipo o un miembro estático de T. Entonces, uno usa typename para contarlo:

 template somefunction( T * arg ) { typename T::sometype x; // works! . . 

En algunas situaciones en las que se refiere a un miembro del denominado tipo dependiente (que significa “dependiente del parámetro de plantilla”), el comstackdor no siempre puede deducir inequívocamente el significado semántico de la construcción resultante, porque no sabe qué tipo de nombre es (es decir, si es un nombre de un tipo, un nombre de un miembro de datos o el nombre de otra cosa). En casos como ese, debe eliminar la ambigüedad de la situación al decirle explícitamente al comstackdor que el nombre pertenece a un nombre de tipo definido como miembro de ese tipo dependiente.

Por ejemplo

 template  struct S { typename T::type i; }; 

En este ejemplo, la palabra clave typename necesaria para que el código se compile.

Lo mismo sucede cuando desea hacer referencia a un miembro de plantilla de tipo dependiente, es decir, a un nombre que designa una plantilla. También debe ayudar al comstackdor utilizando la template palabra clave, aunque se coloca de forma diferente

 template  struct S { T::template ptr p; }; 

En algunos casos, podría ser necesario usar ambos

 template  struct S { typename T::template ptr::type i; }; 

(si obtuve la syntax correctamente).

Por supuesto, otra función de la palabra clave typename se usará en las declaraciones de parámetros de la plantilla.

Dos usos:

  1. Como una palabra clave de argumento de plantilla (en lugar de ‘clase’)
  2. Una palabra clave typename le dice al comstackdor que un identificador es un tipo (en lugar de una variable miembro estática)
 template  class X // [1] { typename T::Y _member; // [2] } 

El secreto reside en el hecho de que una plantilla puede ser especializada para algunos tipos. Esto significa que también puede definir la interfaz completamente diferente para varios tipos. Por ejemplo, puedes escribir:

 template struct test { typedef T* ptr; }; template<> // complete specialization struct test { // for the case T is int T* ptr; }; 

Uno podría preguntarse por qué es útil y de hecho: Eso realmente parece inútil. Pero tenga en cuenta que, por ejemplo, std::vector el tipo de reference ve completamente diferente que para otras T s. Es cierto que no cambia el tipo de reference de un tipo a algo diferente, pero sin embargo podría suceder.

Ahora qué sucede si escribe sus propias plantillas usando esta plantilla de test . Algo como esto

 template void print(T& x) { test::ptr p = &x; std::cout << *p << std::endl; } 

parece estar bien para ti porque esperas que la test::ptr sea ​​un tipo. Pero el comstackdor no sabe y, de hecho, incluso el estándar le aconseja que espere lo contrario, test::ptr no es un tipo. Para decirle al comstackdor lo que espera, debe agregar un typename antes. La plantilla correcta se ve así

 template void print(T& x) { typename test::ptr p = &x; std::cout << *p << std::endl; } 

En typename : debe agregar typename antes siempre que use un tipo nested de una plantilla en sus plantillas. (Por supuesto, solo si se usa un parámetro de plantilla de su plantilla para esa plantilla interna).

 #include  class A { public: typedef int my_t; }; template  class B { public: // T::my_t *ptr; // It will produce comstacktion error typename T::my_t *ptr; // It will output 5 }; int main() { B b; int my_int = 5; b.ptr = &my_int; std::cout << *b.ptr; std::cin.ignore(); return 0; }