Oficialmente, ¿para qué sirve typename?

131

En ocasiones he visto algunos mensajes de error realmente indescifrables escupidos gccal usar plantillas ... Específicamente, he tenido problemas en los que las declaraciones aparentemente correctas estaban causando errores de compilación muy extraños que desaparecieron mágicamente al prefijar la typenamepalabra clave al comienzo del declaración ... (Por ejemplo, la semana pasada, estaba declarando dos iteradores como miembros de otra clase con plantilla y tuve que hacer esto) ...

¿De qué trata la historia typename?

dicroce
fuente

Respuestas:

207

La siguiente es la cita del libro de Josuttis:

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

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

Aquí, typenamese usa para aclarar que SubTypees un tipo de class T. Por lo tanto, ptres 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 SubTypede tipo Tcon ptr.

Naveen
fuente
2
Gran libro Léalo una vez y guárdelo como referencia si lo desea.
deft_code el
1
El lector astuto se dará cuenta de que la gramática no permite una expresión de multiplicación para una declaración de miembro. Como tal, C ++ 20 prescinde de la necesidad de esto typename(¡aunque no todos!).
Davis Herring
No me convenció Una vez que la plantilla se está instanciando, está muy bien definido qué es el T :: Subtipo
kovarex
36

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, por supuesto, podría romper los programas existentes. No era que no se considerara una nueva palabra clave, solo que no se consideraba 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.

Entonces, básicamente, Stroustrup reutilizó la palabra clave de clase sin introducir una nueva palabra clave que luego se cambia en el estándar por las siguientes razones

Como el ejemplo dado

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

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

typename T::A* a6;

le indica al compilador que trate la declaración posterior como una declaración.

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 de clase?

Por eso tenemos ambos

Puedes echar un vistazo a esta publicación , definitivamente te ayudará, solo la extraje todo lo que pude

Xinus
fuente
Sí, pero ¿por qué era typenamenecesaria una nueva palabra clave , si podía usar la palabra clave existente classpara el mismo propósito?
Jesper
55
@Jesper: Creo que la respuesta de Xenus es confusa aquí. typenamese hizo necesario solucionar el problema de análisis como se describe en la respuesta de Naveen citando a Josuttis. (No creo que insertar una classen este lugar hubiera funcionado). Solo después de que se aceptara la nueva palabra clave para este caso, también se permitía en las declaraciones de argumentos de plantilla (¿ o son definiciones? ), Porque classsiempre ha habido algo engañoso.
sbi
13

Considera el código

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

Desafortunadamente, no se requiere que el compilador sea psíquico, y no sabe si T :: sometype terminará refiriéndose a un nombre de tipo o un miembro estático de T. Por lo tanto, uno usa typenamepara decirlo:

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .
sombra de Luna
fuente
6

En algunas situaciones donde se refiere a un miembro de los llamados dependientes tipo (que significa "dependiente del parámetro de plantilla"), el compilador 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 se trata de un nombre de un tipo, un nombre de un miembro de datos o un nombre de otra cosa). En casos como ese, debe desambiguar la situación diciéndole explícitamente al compilador que el nombre pertenece a un nombre de tipo definido como miembro de ese tipo dependiente.

Por ejemplo

template <class T> struct S {
  typename T::type i;
};

En este ejemplo, la palabra clave es typenamenecesaria 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 compilador utilizando la palabra clave template, aunque se coloca de manera diferente

template <class T> struct S {
  T::template ptr<int> p;
};

En algunos casos puede ser necesario usar ambos

template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(si obtuve la sintaxis correctamente).

Por supuesto, otra función de la palabra clave typenamese utilizará en las declaraciones de parámetros de plantilla.

Hormiga
fuente
Consulte también una descripción de la palabra clave typename de C ++ para obtener más información (de fondo).
Atafar
5

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).

phlipsy
fuente
5

Dos usos:

  1. Como una templatepalabra clave de argumento (en lugar de class)
  2. Una typenamepalabra clave le dice al compilador que un identificador es un tipo (en lugar de una variable miembro estática)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}
Phillip Ngan
fuente
4

Creo que todas las respuestas han mencionado que la typenamepalabra clave se usa en dos casos diferentes:

a) Al declarar un parámetro de tipo de plantilla. p.ej

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

Que no hay diferencia entre ellos y son EXACTAMENTE iguales.

b) Antes de usar un nombre de tipo dependiente anidado para una plantilla.

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

El hecho de no usar typenameconduce a errores de análisis / compilación.

Lo que quiero agregar al segundo caso, como se menciona en el libro de Scot Meyers Effective C ++ , es que hay una excepción de usar typenameantes de un nombre de tipo dependiente anidado . La excepción es que si usa el nombre de tipo dependiente anidado como una clase base o en una lista de inicialización de miembros , no debe usar typenameallí:

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

Nota: El uso typenamepara el segundo caso (es decir, antes del nombre de tipo dependiente anidado) no es necesario desde C ++ 20.

Gupta
fuente
2
#include <iostream>

class A {
public:
    typedef int my_t;
};

template <class T>
class B {
public:
    // T::my_t *ptr; // It will produce compilation error
    typename T::my_t *ptr; // It will output 5
};

int main() {
    B<A> b;
    int my_int = 5;
    b.ptr = &my_int;
    std::cout << *b.ptr;
    std::cin.ignore();
    return 0;
}
Jobin
fuente