2009-10-21 13 views
79

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 compilación muy extraños que desaparecieron mágicamente prefijando el "nombre de tipo" palabra clave al comienzo de la declaración ... (Por ejemplo, la semana pasada, declaraba dos iteradores como miembros de otra clase con plantilla y tuve que hacer esto) ...Oficialmente, ¿para qué sirve typename?

¿Cuál es la historia de typename?

+3

http://stackoverflow.com/questions/1600464/ – sbi

Respuesta

124

A continuación se presenta la cita del libro Josuttis :

el nombre de tipo de palabras clave se introdujo a especifican que el identificador que sigue es un tipo. Considere el siguiente ejemplo:

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

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

T::SubType * ptr 

habría una multiplicación de valor subtipo de tipo T con ptr.

+1

Gran libro. Léelo una vez y consérvelo como referencia si lo desea. –

+0

buena respuesta !!! – Gob00st

20

Stan Lippman's BLog post sugiere: -

BS reutilizar la clase palabra clave existente para especificar un parámetro de tipo en lugar de introducir una nueva palabra clave que podrían romper por supuesto programas existentes. No era que no se considerara una nueva palabra clave , solo que no se consideró necesario debido a su posible interrupción . Y hasta el estándar ISO-C++ , esta fue la única forma de declarar un parámetro de tipo.

Así que, básicamente BS reutilizados clase de palabras clave sin introducir una nueva palabra clave que se cambia después en el estándar para las siguientes razones

como el ejemplo dado

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

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

typename T::A* a6; 

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

Dado que la palabra clave estaba en la nómina, diablos, ¿por qué no se soluciona la confusión causada por el decisión original volver a utilizar la palabra clave clase.

Es por eso que tenemos tanto

Puede echar un vistazo a this post, que sin duda le ayudará, sólo extraen de él tanto como pude

+0

Sí, pero entonces ¿por qué era una nueva palabra clave 'typename' necesario, si se puede utilizar la palabra clave existente' class' para el mismo propósito? – Jesper

+4

@Jesper: Creo que la respuesta de Xenus es confusa aquí. 'typename' se hizo necesario para corregir el problema de análisis como se describe en la respuesta de Naveen al citar a Josuttis. (No creo que insertar una 'clase' en este lugar hubiera funcionado). Solo después de que se aceptara la nueva palabra clave para este caso, también se permitió en declaraciones de argumento de plantilla (_o ¿es eso definiciones? _), Porque ese ' clase' siempre ha sido algo engañoso. – sbi

11

considerar el Código

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

Por desgracia, el compilador no es necesario que sea psíquica, y no sabe si T :: sometype terminará en referencia a un nombre de tipo o de un miembro estático de T. Por lo tanto, uno utiliza typename contarla:

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

dos usos:

  1. Como una palabra clave argumento de plantilla (en lugar de 'clase')
  2. Una palabra clave nombretipo 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] 
} 
3

El 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, puede escribir:

template<typename T> 
struct test { 
    typedef T* ptr; 
}; 

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

Uno puede 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 tipo reference tiene un aspecto completamente diferente al de 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 test. Algo como esto

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

parece estar bien para usted porque espera quetest<T>::ptr es un tipo. Pero el compilador no sabe y, de hecho, hasta el estándar le aconseja que espere lo contrario, test<T>::ptr no es un tipo. Para decirle al compilador lo que espera, debe agregar un typename antes. La plantilla correcta se parece a esto

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

En pocas palabras: Usted tiene que agregar typename antes de cada vez que utilice 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)

5

En algunas situaciones en las que se refiere a un miembro del tipo dependiente (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 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 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 en typename necesaria para el código para compilar.

Lo mismo sucede cuando desea hacer referencia a un miembro de plantilla de tipo dependiente, es decir, a un nombre que designa una plantilla. Usted también tiene que ayudar al compilador mediante el uso de la palabra clave template, a pesar de que se coloca de manera diferente

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

En algunos casos puede ser necesario utilizar ambos

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

(si tengo la sintaxis correcta) .

Por supuesto, se debe utilizar otro rol de la palabra clave typename en las declaraciones de parámetros de la plantilla.

+0

Consulte también [Una descripción de la palabra clave typename de C++] (http://pages.cs.wisc.edu/~driscoll/typename.html) para obtener más información (de fondo). – Atafar

1
#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; 
}