Saltar a contenido

Última actualización:2026-10-12

Análisis del código fuente de STL typename

En el código fuente subyacente de STL hay unas líneas en las que typedef se usa junto con typename; a primera vista parecen muy complejas, ¿qué significan exactamente? ¡Hoy las analizamos juntos!

template<typename _Iterator>
struct iterator_traits
{
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

Usos comunes de typename

Primero aprendamos los usos comunes de typename:

template <typename T>
int compare(const T &a, const T &b)
{
    return a>b?a:b;
}

Lo anterior es solo un programa de ejemplo; si se quisiera escribir de forma más completa una comparación de valores, habría que considerar además las versiones especializadas. Quizá pienses que sustituir typename por class en el código anterior funciona igual, ¡y en efecto! Entonces surge la duda: ¿hay alguna diferencia entre ambas formas? Tras consultar C++ Primer, se descubre que son exactamente iguales.

Ámbito de clase

Al acceder desde fuera de una clase a nombres definidos dentro de ella, se puede usar el operador de ámbito de clase; las llamadas con la forma MyClass::name suelen darse en tres casos: miembros de datos estáticos, funciones miembro estáticas y tipos anidados:

struct MyClass {
    static int A; //静态成员
    static int B(){cout<<"B()"<<endl; return 100;} //静态函数
    typedef int C;  //嵌套类型
    struct A1 { //嵌套类型
        static int s;
    };
};

Al invocarlas, se puede llamar directamente:

cout<<MyClass::A<<endl;
cout<<MyClass::B()<<endl;
MyClass:C c;
...

Prueba con un ejemplo completo

Volquemos a un ejemplo con typename:

template <class T>
void foo() {
    T::iterator * iter;
    // ...
}

¿Cuál es el propósito de este código? La primera reacción de la mayoría será: el autor quiere definir un puntero iter cuyo tipo apuntado es el iterator contenido en el ámbito de clase T. Podría existir una estructura así, que contenga un tipo iterator:

struct MyIterator {
    struct iterator {

    };
};

La llamada sería así:

foo<MyIterator>();

De este modo, la línea de iter queda clara: es un puntero al tipo MyIterator::iterator. Conjeturamos que es así, ¿pero lo es en realidad?

Sin embargo, ¿qué ocurre con algo como T::iterator? T es un parámetro de tipo de la plantilla y no se sabrá de qué tipo se trata hasta que la plantilla se instancie, y lo mismo cabe decir del iterator interno. Por la introducción anterior al ámbito de clase sabemos que T::iterator puede ser en realidad cualquiera de estos tres tipos:

  • miembro de datos estático
  • función miembro estática
  • tipo anidado

El ContainsAType::iterator del ejemplo anterior es un tipo anidado, así que no hay ningún problema. Pero ¿y si fuera un miembro de datos estático? ¿Qué pasa si el tipo con el que se instancia la plantilla foo es algo como esto?

struct MyIterator {
    static int iterator;
};

Entonces T::iterator * iter; es instanciado por el compilador como MyIterator::iterator * iter;. ¿Qué es esto? Lo primero es una variable miembro estática y no un tipo, de modo que la línea se convierte en una expresión de multiplicación; solo que aquí iter no está definido y el compilador dará un error:

error: no type named ‘iterator’ in ‘struct MyIterator’

typename

Para los nombres usados en una definición de plantilla que dependen de un parámetro de plantilla, el compilador solo considerará ese nombre como un tipo si el tipo existe en los argumentos de la instanciación, o si el nombre va precedido de la palabra clave typename. Fuera de estos dos casos, jamás se tratará como un tipo.

Por tanto, si quieres decirle directamente al compilador que T::iterator es un tipo y no una variable, basta con cualificarlo con typename:

template <class T>
void foo() {
    typename T::iterator * iter;
}

Así el compilador puede determinar que T::iterator es un tipo sin necesidad de esperar al momento de la instanciación, con lo que se elimina la ambigüedad mencionada antes.

Análisis del código fuente

Volvamos al código fuente de STL

template<typename _Iterator>
struct iterator_traits
{
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

Al ver lo anterior todo queda claro de inmediato: no es más que usar typename para decirle al compilador que _Iterator::iterator_category es un tipo y luego re nombrarlo con typedef; el resto es análogo.

Temas relacionados

Comentarios