¿Dónde y por qué tengo que poner las palabras clave "plantilla" y "nombre de tipo"?

1127

En plantillas, dónde y por qué tengo que poner typenamey templatesobre los nombres de 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 typedef Tail::inUnion<U> dummylínea. Estoy bastante seguro de que inUniones un nombre dependiente, y VC ++ tiene razón al ahogarse.
También sé que debería poder agregar en templatealgún lugar para decirle al compilador que inUnion es un id de plantilla. ¿Pero dónde exactamente? ¿Y debería suponer que inUnion es una plantilla de clase, es decir, inUnion<U>nombra un tipo y no una función?

MSalters
fuente
1
Pregunta molesta: ¿por qué no impulsar :: variante?
Assaf Lavie el
58
Sensibilidades políticas, portabilidad.
MSalters
55
Hice que su pregunta real ("¿Dónde poner la plantilla / nombre de tipo?") Se destaque mejor al poner la pregunta final y el código al principio y acorte el código horizontalmente para que quepa en una pantalla de 1024x.
Johannes Schaub - litb
77
Se eliminaron los "nombres dependientes" del título porque parece que la mayoría de las personas que se preguntan sobre "typename" y "template" no saben qué son los "nombres dependientes". Debería ser menos confuso para ellos de esta manera.
Johannes Schaub - litb
2
@MSalters: boost es bastante portátil. Yo diría que solo la política es la razón general por la cual el impulso a menudo no se acepta. La única buena razón que conozco es el aumento de los tiempos de construcción. De lo contrario, se trata de perder miles de dólares reinventando la rueda.
v.oddou

Respuestas:

1165

(Vea aquí también para mi respuesta C ++ 11 )

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 idiomas, 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. Sin embargo, en C ++, lo anterior puede producir interpretaciones muy diferentes según el tmedio. Si es un tipo, entonces será una declaración de un puntero f. Sin embargo, si no es un tipo, será una multiplicación. Entonces, 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 descubrirá el compilador a qué se t::xrefiere un nombre si se trefiere a un parámetro de tipo de plantilla? xpodría ser un miembro de datos int estático que podría multiplicarse o podría ser igualmente 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 reales de la plantilla, se llama un nombre dependiente ("depende" de los parámetros de la plantilla).

Puede recomendar esperar hasta que el usuario cree una instancia de la plantilla:

Esperemos hasta que el usuario cree una instancia de la plantilla y luego descubra el significado real de t::x * f;.

Esto funcionará y, de hecho, el Estándar lo permite 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 eligen verificar las plantillas desde el principio y dar errores en la definición lo antes posible, incluso antes de que se produzca una instancia.

Entonces, 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: Decidimos cómo el compilador debe analizar esto. Si t::xes un nombre dependiente, entonces necesitamos ponerle un prefijo typenamepara decirle al compilador que lo analice de cierta manera. La Norma dice en (14.6 / 2):

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

Hay muchos nombres para los que typenameno es necesario, porque el compilador puede, con la búsqueda de nombre aplicable en la definición de plantilla, descubrir cómo analizar una construcción en sí misma, por ejemplo T *f;, cuando Tes un parámetro de plantilla de tipo. Pero para t::x * f;ser una declaración, debe escribirse como typename t::x *f;. Si omite la palabra clave y se considera que el nombre no es de tipo, pero cuando la instanciación lo encuentra denota un tipo, el compilador emite los mensajes de error habituales. A veces, el error se da en consecuencia 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 typenamesolo permite nombres calificados antes ; 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 problema similar para los nombres que denotan plantillas, como se insinúa en el texto introductorio.

La palabra clave "plantilla"

¿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;

Puede parecer obvio para un lector humano. No es así para el compilador. Imagine la siguiente definición arbitraria de boost::functiony f:

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

Esa es realmente una expresión válida ! Utiliza el operador menor que para comparar boost::functioncontra cero ( int()), y luego usa el operador mayor que para comparar el resultado boolcontra f. Sin embargo, como bien sabrá, boost::function en la vida real es una plantilla, por lo que el compilador sabe (14.2 / 3):

Después de que la búsqueda de nombre (3.4) encuentra que un nombre es un nombre de plantilla, si a este nombre le sigue un <, el <siempre se toma como el comienzo de una lista de argumentos de plantilla y nunca como un nombre seguido por el menor- que el operador

Ahora volvemos al mismo problema que con typename. ¿Qué sucede si aún no podemos saber si el nombre es una plantilla al analizar el código? Tendremos que insertar templateinmediatamente antes del nombre de la plantilla, según lo especificado por 14.2/4. Esto se ve así:

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

Los nombres de plantillas no sólo puede ocurrir después de un ::sino también después de un ->o .en un miembro de la clase de acceso. También debe insertar la palabra clave allí:

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

Dependencias

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

En las declaraciones de plantilla, algunas construcciones tienen diferentes significados según los argumentos de plantilla que use para crear una instancia de 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. Dichas construcciones generalmente se dice que dependen de parámetros de plantilla.

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

  • Tipos dependientes (por ejemplo, un parámetro de plantilla de tipo T)
  • Expresiones dependientes del valor (por ejemplo, un parámetro de plantilla que no es de tipo N)
  • Expresiones dependientes del tipo (p. Ej.: Conversión a un parámetro de plantilla de tipo (T)0)

La mayoría de las reglas son intuitivas y se crean de forma recursiva: por ejemplo, un tipo construido como T[N]un tipo dependiente si Nes una expresión dependiente del valor o Tes un tipo dependiente. 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 es claro sobre qué es exactamente un nombre dependiente . En una lectura simple (ya sabes, el principio de menor 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::xtambién debe buscarse en el contexto de creación de instancias, también debe ser un nombre dependiente (afortunadamente, a partir de mediados de C ++ 14 el comité ha comenzado a estudiar cómo solucionar esta definición confusa).

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. Esos nombres son, por lo tanto, "nombres dependientes". Un nombre puede tomar diferentes formas: el Estándar dice:

Un nombre es el uso de un identificador (2.11), id-función-operador (13.5), id-función-conversión (12.3.2) o id-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 uso convencional en el Estándar, un nombre también puede incluir calificadores que dicen en qué espacio de nombre o clase se debe buscar un nombre.

Una expresión dependiente del valor 1 + Nno es un nombre, sino que lo Nes. El subconjunto de todas las construcciones dependientes que son nombres se denomina nombre dependiente . Sin embargo, los nombres de las funciones pueden tener un significado diferente en las diferentes instancias de una plantilla, pero desafortunadamente esta regla general no los atrapa.

Nombres de funciones dependientes

No es principalmente una preocupación de este artículo, pero vale la pena mencionarlo: los nombres de las funciones son una excepción que se manejan por separado. El nombre de una función de identificador depende no solo, sino de las expresiones de argumento dependientes del tipo utilizadas en una llamada. En el ejemplo f((T)0), fes un nombre dependiente. En el Estándar, esto se especifica en (14.6.2/1).

Notas adicionales y ejemplos

En suficientes casos necesitamos ambos de typenamey template. Su código debe verse como el siguiente

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

La palabra clave templateno 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 supone que el nombre dado es un nombre de tipo de clase. Esto es cierto para ambos nombres en la lista de clase base y la lista de inicializador del constructor:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • En el uso de declaraciones no es posible usar templatedespués de la última ::, y el comité de C ++ dijo que no trabajara 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
     };
    
Johannes Schaub - litb
fuente
22
Esta respuesta se copió de mi entrada anterior de Preguntas frecuentes que eliminé, porque descubrí que debería utilizar mejor las preguntas similares existentes en lugar de inventar nuevas "pseudo preguntas" solo con el propósito de responderlas. Gracias a @Prasoon , quien editó las ideas de la última parte (casos en los que el nombre de tipo / plantilla está prohibido) en la respuesta.
Johannes Schaub - litb
1
¿Me pueden ayudar cuando debo usar esta sintaxis? this-> template f <int> (); Recibo este error 'plantilla' (como desambigador) solo está permitido dentro de las plantillas pero sin la palabra clave de plantilla, funciona bien.
balki
1
Hice una pregunta similar hoy, que pronto se marcó como duplicado: stackoverflow.com/questions/27923722/… . Me dieron instrucciones de revivir esta pregunta en lugar de crear una nueva. Debo decir que no estoy de acuerdo en que sean duplicados, pero ¿quién soy yo? Entonces, ¿hay alguna razón que typenamese aplique incluso cuando la sintaxis no permite interpretaciones alternativas que no sean nombres de tipo en este momento?
JorenHeit
1
@Pablo no te falta nada. Pero aún así se requiere escribir la desambiguación, incluso si la línea completa ya no fuera ambigua.
Johannes Schaub - litb
1
@Pablo el propósito es mantener el lenguaje y los compiladores más simples. Hay propuestas para permitir que más situaciones resuelvan las cosas automáticamente, de modo que necesite la palabra clave con menos frecuencia. Tenga en cuenta que en su ejemplo, el token es ambiguo y solo después de haber visto ">" después del doble, puede desambiguarlo como un soporte de ángulo de plantilla. Para más detalles, soy la persona equivocada para preguntar, porque no tengo experiencia en la implementación de un analizador de compiladores de C ++.
Johannes Schaub - litb
136

C ++ 11

Problema

Si bien las reglas en C ++ 03 sobre cuándo las necesita typenamey templateson en gran medida razonables, su formulación tiene una desventaja molesta

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 podría descubrir perfectamente que A::result_typesolo puede ser int(y, por lo tanto, es un tipo), y this->gsolo puede ser la plantilla miembro gdeclarada más tarde (incluso si Aestá explícitamente especializada en alguna parte, eso sería no afecta el código dentro de esa plantilla, por lo que su significado no puede verse 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 adjunta. Para saber que, el tipo debe haber sido formado mediante el uso de una cierta forma de nombre, que es su propio nombre (en lo anterior, A, A<T>, ::A<T>). Se sabe que un tipo referenciado por dicho nombre es la instanciación actual . Puede haber varios tipos que son todas las instancias actuales si el tipo del que se forma el nombre es una clase miembro / anidada (entonces, A::NestedClassy Aambas son instancias actuales).

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

Las palabras clave typenamey templateahora ya no se necesitan si el calificador es un miembro de la instanciación actual. Un punto clave aquí para recordar es que todavíaA<T> es un nombre dependiente del tipo (después de todo, también es dependiente del tipo). Pero se sabe que es un tipo: el compilador buscará "mágicamente" este tipo de tipos dependientes para resolver esto.TA<T>::result_type

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 incluso va más allá y requiere que una implementación vuelva a buscar D::result_typeal crear instancias 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. Imagina lo que sucede si definimos Casí

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

Se requiere un compilador para detectar el error al crear instancias D<int>::f. Entonces obtienes lo mejor de los dos mundos: la búsqueda "retrasada" que te protege si puedes tener problemas con las clases base dependientes, y también la búsqueda "inmediata" que te libera de typenamey template.

Especializaciones desconocidas

En el código de D, el nombre typename D::questionable_typeno es miembro de la instanciación actual. En cambio, el lenguaje lo marca como miembro de una especialización desconocida . En particular, este es siempre el caso cuando está haciendo DependentTypeName::Fooo DependentTypedName->Fooy el tipo dependiente no es la instanciación actual (en cuyo caso el compilador puede darse por vencido y decir "veremos más tarde lo que Fooes) o es la instanciación actual y el no se encontró el nombre en él 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 hdentro de la Aplantilla de clase definida anteriormente

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(cualquier argumento que le des T). En C ++ 11, el lenguaje ahora tiene una verificación adicional para dar más razones para que los compiladores implementen esta regla. Como Ano tiene clases base dependientes y Ano declara miembro questionable_type, el nombre noA<T>::questionable_type es miembro de la instanciación actual niUn miembro de una especialización desconocida. En ese caso, no debe haber forma de que ese código pueda compilarse válidamente en el momento de la instanciación, por lo que el lenguaje prohíbe que un nombre donde el calificador sea la instanciación actual no sea miembro de una especialización desconocida ni miembro de la instanciación actual (sin embargo , aún no se requiere diagnosticar esta violación).

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 (lo cual no fue pensado por el comité de C ++, pero probablemente no se solucionará)

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 C ++ 03 se uniría this->fa A::fen tiempo de creación de instancias y todo está bien. Sin embargo, C ++ 11 lo vincula inmediatamente B::fy requiere una doble verificación al crear instancias, verificando si la búsqueda aún coincide. Sin embargo, al crear instancias C<A>::g, se aplica la Regla de Dominio y la búsqueda se encuentra en su A::flugar.

Johannes Schaub - litb
fuente
para su información: aquí se hace referencia a esta respuesta: stackoverflow.com/questions/56411114/… Gran parte del código de esta respuesta no se compila en varios compiladores.
Adam Rackis
@AdamRack supone que la especificación de C ++ no ha cambiado desde 2013 (fecha en que escribí esta respuesta), luego los compiladores con los que probó su código simplemente no implementan esta característica de C ++ 11 + todavía.
Johannes Schaub - litb
100

PREFACIO

Esta publicación está destinada a ser una alternativa fácil de leer a la publicación de litb .

El propósito subyacente es el mismo; Una explicación de "¿Cuándo?" ¿y por qué?" typenamey templatedebe ser aplicado.


¿Para qué sirve typenamey template?

typenamey templateson utilizables en circunstancias que no sean al declarar una plantilla.

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

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

Esta publicación ofrecerá una explicación de la relación entre los nombres dependientes y las dos palabras clave.


UN SNIPPET DICE MÁS DE 1000 PALABRAS

Trate de explicar lo que está sucediendo en la siguiente plantilla de función , ya sea para usted, un amigo o quizás su gato; ¿Qué está pasando en la declaración marcada ( A )?

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


Puede que no sea tan fácil como se piensa, más específicamente el resultado de evaluar ( A ) depende en gran medida de la definición del tipo pasado como parámetro de plantilla T.

Diferentes Ts pueden cambiar drásticamente la semántica involucrada.

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 instanciamos 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 instanciamos la plantilla con el tipo Y , como en ( D ), ( A ) consistiría en una expresión que calcula el producto de 123 multiplicado por alguna variable ya declarada x .



LO RACIONAL

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

Para evitar que una implementación sufra sorpresas desagradables, el Estándar exige que resolvamos la ambigüedad de un nombre dependiente al establecer explícitamente la intención en cualquier lugar donde nos gustaría tratar el nombre como un nombre de tipo o una plantilla. id .

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



¿CÓMO MANEJAR LOS NOMBRES DEPENDIENTES ?

Si se tratara de 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 anfitrión para confundirlo. Confusión que podría, posiblemente, conducir a un programa perso-, erhm .. mal formado.

Un nombre dependiente es cualquier nombre que depende directa o indirectamente de un parámetro de 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 anterior:

  • E )
    • "tipo" depende de la instanciación de SomeTrait<T>, que incluye T, y;
  • F )
    • "NestedTrait" , que es un id de plantilla , depende de SomeTrait<T>y;
    • "tipo" 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>.

Ninguna de las afirmaciones ( E ), ( F ) o ( G ) es válida si el compilador interpretaría los nombres dependientes como variables / funciones (lo que, como se indicó anteriormente, es lo que sucede si no decimos explícitamente lo contrario).

LA SOLUCIÓN

Para que g_tmpltenga una definición válida, debemos decirle explícitamente al compilador que esperamos un tipo en ( E ), una plantilla-id y un tipo en ( F ), y una plantilla-id en ( 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 tipo o espacios de nombres , con esto en mente es bastante fácil ver que aplicamos typenameal comienzo de nuestro nombre totalmente calificado .

templatesin embargo, es diferente a este respecto, ya que no hay forma de llegar a una conclusión como; "oh, esta es una plantilla, entonces esta otra cosa también debe ser una plantilla" . Esto significa que aplicamos templatedirectamente frente a cualquier nombre que nos gustaría tratar como tal.



¿PUEDO PEGAR LAS PALABRAS CLAVE DELANTE DE CUALQUIER NOMBRE?

" ¿Puedo sólo se adhieren typenamey templatedelante de cualquier nombre no quiere preocuparse por el contexto en el que aparecen ...? " -Some C++ Developer

Las reglas en el Estándar establecen que puede aplicar las palabras clave siempre que se trate de un nombre calificado ( K ), pero si el nombre no está calificado, la aplicación está mal formada ( 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 typenameo templateen un contexto donde no se requiere no se considera una buena práctica; solo porque puedes hacer algo, no significa que debas hacerlo.


Además, hay contextos donde typenamey templateestán explícitamente prohibidos:

  • Al especificar las bases de las cuales hereda una clase

    Cada nombre escrito en la lista de especificador base de una clase derivada ya se trata como un nombre de tipo , especificando explícitamente que typenameestá mal formado y es redundante.

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


  • Cuando el id de plantilla es el que se menciona en la directiva using de una clase derivada

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    
Filip Roséen - refp
fuente
20
typedef typename Tail::inUnion<U> dummy;

Sin embargo, no estoy seguro de que su implementación de inUnion sea correcta. Si entiendo correctamente, se supone que esta clase no debe ser instanciada, por lo tanto, la pestaña "falla" nunca fallará. Quizás 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 las listas de tipos , especialmente en el libro de Andrei Alexandrescu: Modern C ++ Design

Luc Touraille
fuente
inUnion <U> se instanciaría si, por ejemplo, intentara llamar a Union <float, bool> :: operator = (U) con U == int. Llama a un conjunto privado (U, inUnion <U> * = 0).
MSalters
Y el trabajo con result = true / false es que necesitaría boost :: enable_if <>, que es incompatible con nuestra cadena de herramientas OSX actual. Sin embargo, la plantilla separada sigue siendo una buena idea.
MSalters
Luc significa el tipeo typedef Tail :: inUnion <U>; línea. eso creará una instancia de Tail. pero no en Unión <U>. se instancia cuando necesita la definición completa de la misma. eso sucede, por ejemplo, si toma el tamaño de, o accede a un miembro (usando :: foo). @MSalters de todos modos, tienes otro problema:
Johannes Schaub - litb 05 de
-sizeof (U) nunca es negativo :) porque size_t es un tipo entero sin signo. Obtendrás un número muy alto. probablemente quieras hacer sizeof (U)> = 1? -1: 1 o similar :)
Johannes Schaub - litb 05 de
simplemente lo dejaría sin definir y solo lo declararía: template <typename U> struct inUnion; entonces ciertamente no puede ser instanciado. Creo que tenerlo con sizeof, el compilador también puede darte un error, incluso si no lo instancias, porque si sabe sizeof (U) es siempre> = 1 y ...
Johannes Schaub - litb
20

Esta respuesta está destinada a ser bastante breve y dulce para responder (parte de) la pregunta titulada. Si desea una respuesta con más detalles que explique por qué tiene que ponerlas allí, vaya aquí .


La regla general para colocar la typenamepalabra clave es principalmente cuando está usando un parámetro de plantilla y desea acceder a un typedefalias anidado o de uso, 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 también toman parámetros genéricos de plantilla. 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 templatecalificador son en su mayoría similares, excepto que generalmente involucran funciones miembro con plantilla (estáticas o de otro tipo) de una estructura / clase que en sí misma tiene plantilla, 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
}

Intentar acceder t.get<int>()desde dentro de la función dará como resultado un error:

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

Por lo tanto, en este contexto, necesitaría la templatepalabra clave de antemano y llamarla así:

t.template get<int>()

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

Rapptz
fuente
2

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 los parámetros de la plantilla.

Por 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 qué se refiere un nombre dependiente podría ser algo diferente para cada instanciación 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 se produzca una instanciación), el compilador busca los nombres no dependientes. Cuando se produce una instanciación particular de la plantilla, los parámetros de la plantilla son conocidos 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. Por defecto, se supone 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 las declaraciones y definiciones de plantillas siempre que tenga un nombre calificado que se refiera a un tipo y dependa de un parámetro de plantilla.

Nikos
fuente