¿Dónde y por qué tengo que poner las palabras clave" template "y" typename"?


En las plantillas, ¿dónde y por qué tengo que poner typename y template en nombres dependientes? ¿Qué son exactamente los nombres dependientes de todos modos? Tengo el siguiente código:

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

El problema que tengo está en la línea typedef Tail::inUnion<U> dummy. Estoy bastante seguro de que inUnion es un nombre dependiente, y VC++ tiene razón al ahogarse en él. También sé que debería poder agregar template en algún lugar para decirle al compilador que inUnion es un template-id. Pero ¿dónde exactamente? Y debe entonces asumir que inUnion es una plantilla de clase, es decir, inUnion<U> nombra un tipo y no una función?

Author: MSalters, 2009-03-04

6 answers

Para analizar un programa C++, el compilador necesita saber si ciertos nombres son tipos o no. El siguiente ejemplo demuestra que:

t * f;

¿Cómo se debe analizar esto? Para muchos lenguajes un compilador no necesita saber el significado de un nombre para analizar y básicamente saber qué acción hace una línea de código. En C++, lo anterior sin embargo puede producir interpretaciones muy diferentes dependiendo de lo que t significa. Si es un tipo, entonces será una declaración de un puntero f. Sin embargo, si no es un tipo, será una multiplicación. Así que el estándar C++ dice en el párrafo (3/7):

Algunos nombres denotan tipos o plantillas. En general, cada vez que se encuentra un nombre es necesario determinar si ese nombre denota una de estas entidades antes de continuar analizando el programa que lo contiene. El proceso que determina esto se llama búsqueda de nombres.

¿Cómo averiguará el compilador a qué se refiere un nombre t::x, si t se refiere a un parámetro de tipo de plantilla? x podría ser un miembro de datos int estático que podría multiplicarse o también podría ser una clase anidada o typedef que podría dar lugar a una declaración. Si un nombre tiene esta propiedad - que no se puede buscar hasta que se conozcan los argumentos de la plantilla-entonces se llama nombre dependiente ("depende" de los parámetros de la plantilla).

Puedes recomendar esperar hasta que el usuario instancie la plantilla:

Esperemos hasta que el usuario instancie la plantilla, y luego averigüemos el significado real de t::x * f;.

Esto funcionará y en realidad está permitido por el Estándar como un posible enfoque de implementación. Estos compiladores básicamente copian el texto de la plantilla en un búfer interno, y solo cuando se necesita una instanciación, analizan la plantilla y posiblemente detectan errores en la definición. Pero en lugar de molestar a los usuarios de la plantilla (¡pobres colegas!) con errores cometidos por el autor de una plantilla, otras implementaciones optan por comprobar las plantillas desde el principio y dar errores en la definición tan pronto como sea posible, incluso antes de que tenga lugar una instanciación.

Así que tiene que haber una manera de decirle al compilador que ciertos nombres son tipos y que ciertos nombres no lo son.

La palabra clave" typename "

La respuesta es: Nosotros decidimos cómo el compilador debe analizar esto. Si t::x es un nombre dependiente, entonces necesitamos prefijarlo por typename para decirle al compilador que lo analice de cierta manera. El Estándar dice en (14.6/2):

Un nombre utilizado en una declaración o definición de plantilla y que depende de un parámetro de plantilla es se supone que no nombra un tipo a menos que la búsqueda de nombre aplicable encuentre un nombre de tipo o el nombre esté calificado por la palabra clave typename.

Hay muchos nombres para los cuales typename no es necesario, porque el compilador puede, con la búsqueda de nombre aplicable en la plantilla definición, averigüe cómo analizar una construcción en sí misma, por ejemplo con T *f;, cuando T es un parámetro de plantilla de tipo. Pero para que t::x * f; sea una declaración, debe estar escrita como typename t::x *f;. Si omite la palabra clave y el nombre se toma como un no-tipo, pero cuando la instanciación encuentra que denota un tipo, los mensajes de error habituales son emitidos por el compilador. A veces, el error en consecuencia se da en el momento de la definición:

// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;

La sintaxis permite typename solo antes de calificado names - por lo tanto, se da por sentado que siempre se sabe que los nombres no calificados se refieren a tipos si lo hacen.

Existe un gotcha similar para los nombres que denotan plantillas, como se insinúa en el texto introductorio.

La palabra clave "template"

¿Recuerda la cita inicial anterior y cómo el Estándar requiere un manejo especial para las plantillas también? Tomemos el siguiente ejemplo de aspecto inocente:

boost::function< int() > f;

Podría parecer obvio para un lector humano. Ni así que para el compilador. Imagine la siguiente definición arbitraria de boost::function y f:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

Eso es en realidad una expresión válida! Usa el operador menor que para comparar boost::function con cero (int()), y luego usa el operador mayor que para comparar el resultado bool con f. Sin embargo, como bien sabes, boost::function en la vida real es una plantilla, por lo que el compilador sabe (14.2/3):

After name lookup (3.4) encuentra que un nombre es un template-name, si este nombre es seguido por un

Ahora volvemos al mismo problema que con typename. ¿Qué pasa si aún no podemos saber si el nombre es una plantilla al analizar el código? Tendremos que insertar template inmediatamente antes del nombre de la plantilla, como se especifica en 14.2/4. Esto parece:

t::template f<int>(); // call a function template

Los nombres de plantilla no solo pueden ocurrir después de un :: pero también después de un -> o . en un acceso de miembro de clase. Es necesario insertar la palabra clave allí también:

this->template f<int>(); // call a function template

Dependencias

Para las personas que tienen libros estándar gruesos en su estante y que quieren saber exactamente de qué estaba hablando, hablaré un poco sobre cómo se especifica esto en el Estándar.

En las declaraciones de plantilla, algunas construcciones tienen diferentes significados dependiendo de los argumentos de plantilla que use para instanciar la plantilla: Las expresiones pueden tener diferentes tipos o valores, las variables pueden tener diferentes tipos o las llamadas a funciones pueden terminar llamando a diferentes funciones. Tales construcciones generalmente se dice que dependen de los parámetros de la plantilla.

El Estándar define con precisión las reglas por si una construcción es dependiente o no. Los separa en grupos lógicamente diferentes: Uno atrapa tipos, otro atrapa expresiones. Las expresiones pueden depender de su valor y / o su tipo. Así que tenemos, con ejemplos típicos adjuntos:

  • Tipos dependientes (por ejemplo: un parámetro de plantilla de tipo T)
  • Expresiones dependientes del valor (por ejemplo: un parámetro de plantilla no de tipo N)
  • Expresiones dependientes del tipo (por ejemplo: una conversión a un parámetro de plantilla de tipo (T)0)

La mayoría de las reglas son intuitivas y se construyen recursivamente: Por ejemplo, un tipo construido como T[N] es un tipo dependiente si N es una expresión dependiente del valor o T es un tipo dependiente tipo. Los detalles de esto se pueden leer en la sección (14.6.2/1) para tipos dependientes, (14.6.2.2) para expresiones dependientes de tipo y (14.6.2.3) para expresiones dependientes de valor.

Nombres dependientes

El Estándar no está claro qué exactamente es un nombre dependiente. En una simple lectura (ya sabes, el principio de menos sorpresa), todo lo que define como un nombre dependiente es el caso especial para los nombres de funciones a continuación. Pero dado que claramente T::x también debe ser buscado en el contexto de instanciación, también necesita ser un nombre dependiente (afortunadamente, a partir de mediados de C++14 el comité ha comenzado a buscar cómo solucionar esta confusa definición).

Para evitar este problema, he recurrido a una interpretación simple del texto Estándar. De todas las construcciones que denotan tipos o expresiones dependientes, un subconjunto de ellas representa nombres. Por lo tanto, esos nombres son "nombres dependientes". Un nombre puede tomar diferentes formas - el Estándar dice:

Un nombre es el uso de un identificador (2.11), identificador de función de operador (13.5), identificador de función de conversión (12.3.2) o identificador de plantilla (14.2) que denota una entidad o etiqueta (6.6.4, 6.1)

Un identificador es solo una secuencia simple de caracteres / dígitos, mientras que los dos siguientes son la forma operator + y operator type. La última forma es template-name <argument list>. Todos estos son nombres, y por el uso convencional en el Estándar, un nombre también puede incluir calificadores que dicen qué espacio de nombres o nombre de clase a debería ser mirado hacia arriba.

Una expresión dependiente del valor 1 + N no es un nombre, pero N lo es. El subconjunto de todas las construcciones dependientes que son nombres se llama dependent name. Los nombres de funciones, sin embargo, pueden tener un significado diferente en diferentes instancias de una plantilla, pero desafortunadamente no están atrapados por esta regla general.

Nombres de funciones dependientes

No es principalmente una preocupación de este artículo, pero aún vale la pena mencionarlo: Los nombres de funciones son un excepción que se manejan por separado. Un nombre de función identificador no es dependiente por sí mismo, sino por las expresiones de argumento dependiente de tipo utilizadas en una llamada. En el ejemplo f((T)0), f es un nombre dependiente. En la Norma, esto se especifica en (14.6.2/1).

Notas y ejemplos adicionales

En suficientes casos necesitamos tanto typename como template. Su código debe tener el siguiente aspecto

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

La palabra clave template no siempre tiene que aparecer en la última parte de un nombre. Puede aparecer en el medio antes de un nombre de clase que se usa como ámbito, como en el siguiente ejemplo

typename t::template iterator<int>::value_type v;

En algunos casos, las palabras clave están prohibidas, como se detalla a continuación

  • En el nombre de una clase base dependiente no se le permite escribir typename. Se asume que el nombre dado es un nombre de tipo de clase. Esto es cierto para ambos nombres en la lista de la clase base y la lista del inicializador del constructor:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • En using-declarations es no es posible usar template después del último ::, y el comité de C++ dijo no trabajar en una solución.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    
 953
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-02 15:50:24

C++11

Problema

Mientras que las reglas en C++03 sobre cuándo se necesita typename y template son en gran medida razonables, hay una desventaja molesta de su formulación

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Como se puede ver, necesitamos la palabra clave de desambiguación incluso si el compilador pudiera perfectamente darse cuenta de que A::result_type solo puede ser int (y por lo tanto es un tipo), y this->g solo puede ser la plantilla miembro g declarada más tarde (incluso si A está explícitamente especializada en algún lugar, eso no afectar el código dentro de esa plantilla, por lo que su significado no puede ser afectado por una especialización posterior de A!).

Instanciación actual

Para mejorar la situación, en C++11 el lenguaje rastrea cuando un tipo se refiere a la plantilla que encierra. Para saber eso, el tipo debe haber sido formado usando una cierta forma de nombre, que es su propio nombre (en el anterior, A, A<T>, ::A<T>). Un tipo referenciado por tal nombre es conocido por ser la instanciación actual . Puede ser múltiples tipos que son todas las instancias actuales si el tipo a partir del cual se forma el nombre es una clase miembro/anidada (entonces, A::NestedClass y A son ambas instancias actuales).

Basado en esta noción, el lenguaje dice que CurrentInstantiation::Foo, Foo y CurrentInstantiationTyped->Foo (como A *a = this; a->Foo) son todos miembros de la instanciación actual si se encuentra que son miembros de una clase que es la instanciación actual o una de sus clases base no dependientes (simplemente haciendo el nombre buscar inmediatamente).

Las palabras clave typename y template ya no son necesarias si el calificador es miembro de la instanciación actual. Un punto clave aquí para recordar es que A<T>es todavía un nombre dependiente de tipo (después de todo T también es dependiente de tipo). Pero A<T>::result_type es conocido por ser un tipo - el compilador buscará "mágicamente" en este tipo de tipos dependientes para averiguar esto.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Eso es impresionante, pero ¿podemos hacerlo mejor? El lenguaje va más allá y requiere que una implementación vuelva a buscar D::result_type cuando instancie D::f (incluso si ya encontró su significado en el momento de la definición). Cuando ahora el resultado de la búsqueda difiere o resulta en ambigüedad, el programa está mal formado y se debe dar un diagnóstico. Imagine lo que sucede si definimos C así{[56]]}

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Se requiere un compilador para detectar el error al instanciar D<int>::f. Así que obtienes lo mejor de los dos mundos: búsqueda "retrasada" que te protege si puedes te metes en problemas con las clases base dependientes, y también con la búsqueda "Inmediata" que te libera de typename y template.

Especializaciones desconocidas

En el código de D, el nombre typename D::questionable_type no es un miembro de la instancia actual. En cambio, el idioma lo marca como un miembro de una especialización desconocida. En particular, este es siempre el caso cuando se está haciendo DependentTypeName::Foo o DependentTypedName->Foo y o bien el tipo dependiente es no la instanciación actual (en cuyo caso la el compilador puede darse por vencido y decir " veremos más tarde qué es Foo) o es la instanciación actual y el nombre no se encontró en ella o en sus clases base no dependientes y también hay clases base dependientes.

Imagine lo que sucede si tuviéramos una función miembro h dentro de la plantilla de clase definida anteriormente A

void h() {
  typename A<T>::questionable_type x;
}

En C++03, el lenguaje permitió detectar este error porque nunca podría haber una forma válida de instanciar A<T>::h (sea cual sea el argumento dar a T). En C++11, el lenguaje ahora tiene una comprobación adicional para dar más razones para que los compiladores implementen esta regla. Dado que A no tiene clases base dependientes, y A no declara ningún miembro questionable_type, el nombre A<T>::questionable_type no es ni un miembro de la instanciación actual ni un miembro de una especialización desconocida. En ese caso, no debería haber manera de que ese código pudiera compilarse válidamente en el momento de la instanciación, por lo que el lenguaje prohíbe un nombre donde el calificador es el actual instanciación no ser ni miembro de una especialización desconocida ni miembro de la instanciación actual (sin embargo, esta violación todavía no es necesaria para ser diagnosticada).

Ejemplos y curiosidades

Puede probar este conocimiento en esta respuesta y ver si las definiciones anteriores tienen sentido para usted en un ejemplo del mundo real (se repiten un poco menos detalladas en esa respuesta).

Las reglas de C++11 hacen que el siguiente código válido de C++03 esté mal formado (que no fue la intención del comité de C++, pero probablemente no será arreglado)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Este código válido de C++03 enlazaría this->f a A::f en el momento de la instanciación y todo está bien. Sin embargo, C++11 lo vincula inmediatamente a B::f y requiere una doble comprobación al instanciar, comprobando si la búsqueda sigue coincidiendo. Sin embargo, al instanciar C<A>::g, se aplica la Regla de Dominio y la búsqueda encontrará A::f en su lugar.

 124
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:22

PREFACIO

Este post está destinado a ser una alternativa fácil de leer al post de litb.

El propósito subyacente es el mismo; una explicación a "¿Cuándo?"y "¿por Qué?"typename y template deben aplicarse.


¿Cuál es el propósito de typename y template?

typename and template are usable in circumstances other than when declaring a template.

Hay ciertos contextos en C++ donde al compilador se le debe indicar explícitamente cómo tratar un nombre, y todos estos contextos tienen una cosa en común; dependen de al menos un parámetro-plantilla.

Nos referimos a tales nombres, donde puede haber una ambigüedad en la interpretación, como; " nombres dependientes".

Este post ofrecerá una explicación a la relación entre los nombres dependientes, y las dos palabras clave.


UN FRAGMENTO DICE MÁS DE 1000 PALABRAS

Trate de explicar lo que está pasando en la siguiente función-plantilla, ya sea a ti mismo, un amigo, o tal vez su gato; lo que está sucediendo en la declaración marcada (A)?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


Podría no ser tan fácil como uno piensa, más específicamente el resultado de evaluar (A) pesadamente depende de la definición del tipo pasado como parámetro de plantilla T.

Diferentes T s pueden cambiar drásticamente la semántica involucrados.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


Los dos escenarios diferentes:

  • Si creamos una instancia de la plantilla de función con el tipo X , como en (C ), tendremos una declaración de un puntero a int llamado x, pero;

  • Si creamos una instancia de la plantilla con el tipo Y , como en (D), (A) consistiría en su lugar en una expresión que calcula el producto de 123 multiplicado por alguna variable ya declarada x .



LA JUSTIFICACIÓN

El estándar C++ se preocupa por nuestra seguridad y bienestar, al menos en este caso.

Para evitar que una implementación sufra potencialmente sorpresas desagradables, el Estándar exige que resolvamos la ambigüedad de un nombre dependiente por explícitamente indicando la intención en cualquier lugar que nos gustaría tratar el nombre como un type-name, o un template-id.

Si no se indica nada, el nombre dependiente se considerará una variable o una función.



¿CÓMO MANEJAR NOMBRES DEPENDIENTES?

Si esta fuera una película de Hollywood, los nombres dependientes serían la enfermedad que se propaga a través del contacto corporal, afecta instantáneamente a su huésped para confundirlo. Confusión que podría, posiblemente, conducir a un mal formado perso -, erhm.. programa.

Un nombre-dependiente es cualquier nombre que depende directa o indirectamente de un parámetro-plantilla.

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

Tenemos cuatro nombres dependientes en el fragmento de código anterior:

  • E )
    • "type" depende de la instanciación de SomeTrait<T>, que incluye T, y;
  • F )
    • "NestedTrait" , que es un template-id , depende de SomeTrait<T>, y;
    • "type" al final de (F ) depende de NestedTrait , que depende de SomeTrait<T>, y;
  • G )
    • "data", que se parece a una plantilla de función miembro , es indirectamente un nombre dependiente ya que el tipo de foo depende de la instanciación de SomeTrait<T>.

Ninguno de los dos declaración ( E), (F ) o ( G) es válido si el compilador interpretaría los dependientes-nombres como variables/funciones (que como se indicó anteriormente es lo que sucede si no decimos explícitamente lo contrario).

LA SOLUCIÓN

Para que g_tmpl tenga una definición válida, debemos decirle explícitamente al compilador que esperamos un tipo en (E ), un template-id y un type in (F ), y un template-idin ( G).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

Cada vez que un nombre denota un tipo, todos los nombres involucrados deben ser nombres de tipoo espacios de nombres, con esto en mente es bastante fácil ver que aplicamos typenameal comienzo de nuestro nombre completo calificado.

template sin embargo, es diferente en este sentido, ya que no hay manera de llegar a una conclusión como; " oh, esto es una plantilla, que este otro la cosa también debe ser una plantilla " . Esto significa que aplicamos templatedirectamente delante de cualquier nombre que nos gustaría tratar como tal.



¿PUEDO PONER LAS PALABRAS CLAVE DELANTE DE CUALQUIER NOMBRE?

"¿Puedo poner typename y template delante de cualquier nombre? No quiero preocuparme por el contexto en el que aparecen..." - Some C++ Developer

Las reglas en el Estándar establecen que puede aplicar las palabras clave mientras que usted está tratando con un calificado-nombre (K), pero si el nombre no es calificado la aplicación está mal formado (L).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Nota : Aplicar typename o template en un contexto donde no se requiere no se considera una buena práctica; solo porque puedas hacer algo, no significa que debas hacerlo.


Además hay contextos donde typename y template son explícitamente no permitidos:

  • Al especificar las bases de las que hereda una clase

    Cada nombre escrito en una clase derivada base-specifier-list ya se trata como un type-name, especificando explícitamente typename es a la vez mal formado, y redundante.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };
    


  • Cuando el template-id es el que se refiere en una clase derivada directiva de utilización

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    
 78
Author: Filip Roséen - refp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:22
typedef typename Tail::inUnion<U> dummy;

Sin embargo, no estoy seguro de que la implementación de inUnion sea correcta. Si entiendo correctamente, se supone que esta clase no debe ser instanciada, por lo tanto, la pestaña "fail" nunca falla. Tal vez sería mejor indicar si el tipo está en la unión o no con un valor booleano simple.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PD: Echa un vistazo a Boost:: Variant

PS2: Echa un vistazo a typelists , notablemente en el libro de Andrei Alexandrescu: Modern C++ Design

 19
Author: Luc Touraille,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-04 13:37:47

Esta respuesta está destinada a ser bastante corta y dulce para responder (parte de) la pregunta titulada. Si quieres una respuesta con más detalle que explique por qué tienes que ponerlas ahí, por favor ve aquí.


La regla general para poner la palabra clave typename es sobre todo cuando se está utilizando un parámetro de plantilla y desea acceder a un typedef anidado o utilizando-alias, por ejemplo:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Tenga en cuenta que esto también se aplica a las funciones meta o cosas que toman parámetros de plantilla genéricos también. Sin embargo, si el parámetro de plantilla proporcionado es un tipo explícito, entonces no tiene que especificar typename, por ejemplo:

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Las reglas generales para agregar el calificador template son en su mayoría similares, excepto que típicamente involucran funciones miembro templadas (estáticas o no) de una estructura / clase que a su vez está templada, por ejemplo:

Dada esta estructura y función:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Intentando acceder a t.get<int>() desde dentro del la función dará lugar a un error:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Así que en este contexto necesitarías la palabra clave template de antemano y la llamarías así:

t.template get<int>()

De esta manera el compilador analizará esto correctamente en lugar de t.get < int.

 16
Author: Rapptz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:34

Estoy colocando la excelente respuesta de JLBorges a una pregunta similar textualmente de cplusplus.com, ya que es la explicación más sucinta que he leído sobre el tema.

En una plantilla que escribimos, hay dos tipos de nombres que podrían usarse: nombres dependientes y nombres no dependientes. Un nombre dependiente es un nombre que depende de un parámetro de plantilla; un nombre no dependiente tiene el mismo significado independientemente de cuáles sean los parámetros de plantilla.

Para ejemplo:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

A lo que se refiere un nombre de dependiente podría ser algo diferente para cada instancia diferente de la plantilla. Como consecuencia, las plantillas de C++ están sujetas a"búsqueda de nombres en dos fases". Cuando una plantilla se analiza inicialmente (antes de que tenga lugar cualquier instanciación), el compilador busca los nombres no dependientes. Cuando se lleva a cabo una instanciación particular de la plantilla, los parámetros de la plantilla se conocen para entonces, y el compilador busca nombres dependientes.

Durante la primera fase, el analizador necesita saber si un nombre dependiente es el nombre de un tipo o el nombre de un no tipo. De forma predeterminada, se asume que un nombre dependiente es el nombre de un no tipo. La palabra clave typename antes de un nombre dependiente especifica que es el nombre de un tipo.


Resumen

Use la palabra clave typename solo en declaraciones y definiciones de plantillas siempre que tenga un nombre calificado que se refiera a un tipo y dependa de una plantilla parámetro.

 2
Author: Nik-Lz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-11 12:20:50