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


En 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 el typedef Tail::inUnion<U> dummy línea. Estoy bastante seguro de que inUnion es un nombre dependiente, y VC ++ tiene razón al asfixiarlo. También sé que debería ser capaz de agregar template en algún lugar para decirle al compilador que inUnion es una plantilla-id. Pero, ¿dónde exactamente? ¿Y debería suponer entonces que inUnion es una plantilla de clase, es decir inUnion<U> nombra un tipo y no una función?


901
2018-03-04 11:56


origen


Respuestas:


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 debe ser analizado? 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 arrojar interpretaciones muy diferentes dependiendo de qué t medio. 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 de C ++ dice en el párrafo (3/7):

Algunos nombres denotan tipos o plantillas. En general, siempre que se encuentre 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 nombre.

¿Cómo el compilador descubrirá qué nombre? t::x se refiere a, 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 bien podría ser una clase anidada o typedef que podría ceder a una declaración. Si un nombre tiene esta propiedad, que no se puede buscar hasta conocer los argumentos de la plantilla real, se llama nombre dependiente ("depende" de los parámetros de la plantilla).

Puede recomendar esperar solo hasta que el usuario crea la instancia de la plantilla:

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

Esto funcionará y, de hecho, está permitido por el Estándar como 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 (colegas pobres) con errores cometidos por el autor de una plantilla, otras implementaciones eligen revisar las plantillas desde el principio y dar errores en la definición lo antes posible, incluso antes de que se realice una instancia.

Entonces tiene que haber una forma de decirle al compilador que ciertos nombres son tipos y que ciertos nombres no lo son.

La palabra clave "typename"

La respuesta es: Nosotros decidir cómo el compilador debe analizar esto. Si t::x es un nombre dependiente, entonces tenemos que prefijarlo por typenamepara 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 se debe nombrar 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 definición de plantilla, descubrir cómo analizar una construcción en sí misma, por ejemplo con T *f;, cuando T es un parámetro de plantilla tipo. Pero para t::x * f; para ser una declaración, debe escribirse como typename t::x *f;. Si omite la palabra clave y se toma el nombre como no de tipo, pero cuando la creación de instancias encuentra que denota un tipo, el compilador emite los mensajes de error habituales. 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 nombres calificados - Por lo tanto, se da por sentado que siempre se sabe que los nombres no calificados hacen referencia a los tipos si lo hacen.

Existe una idea similar para los nombres que denotan plantillas, como lo sugiere 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::function y f:

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

Eso es realmente un válido expresión! Utiliza el operador inferior para comparar boost::function contra cero (int()), y luego utiliza el operador mayor que para comparar el resultado bool en contra f. Sin embargo, como bien podrías saber, boost::function  en la vida real es una plantilla, por lo que el compilador sabe (14.2 / 3):

Después de la búsqueda de nombres (3.4) encuentra que un nombre es un nombre de plantilla, si este nombre es seguido por un <, el <es   siempre tomado como el comienzo de una plantilla-argumento-lista y nunca como un nombre seguido por el menos de   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? Necesitaremos insertar template inmediatamente 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 las plantillas no solo pueden aparecer después de un :: pero también después de un -> o . en un acceso de miembro de la clase. También debe insertar la palabra clave allí:

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

Dependencias

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

En las declaraciones de plantilla, algunas construcciones tienen diferentes significados según los argumentos de la plantilla que utilice para crear 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 depender en los parámetros de la plantilla.

El estándar define con precisión las reglas según si un constructo es dependiente o no. Los separa en grupos lógicamente diferentes: uno capta tipos, otro capta expresiones. Las expresiones pueden depender de su valor y / o su tipo. Así que tenemos, con ejemplos típicos añadidos:

  • Tipos dependientes (p. Ej .: un parámetro de plantilla de tipo T)
  • Expresiones dependientes del valor (p. Ej .: un parámetro de plantilla sin tipo N)
  • Expresiones dependientes del tipo (por ejemplo, un molde a un parámetro de plantilla de tipo (T)0)

La mayoría de las reglas son intuitivas y se crean 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 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 del tipo y (14.6.2.3) para expresiones dependientes del valor.

Nombres dependientes

El estándar no está claro acerca de qué exactamente es un nombre dependiente. En una lectura simple (ya sabes, el principio de menos sorpresa), todo lo define como una nombre dependiente es el caso especial para los nombres de funciones a continuación. Pero desde que claramente T::x también debe buscarse en el contexto de instanciación, también debe 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 todos los constructos que denotan tipos o expresiones dependientes, un subconjunto de ellos 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), operador-función-id (13.5), conversión-función-id (12.3.2), o plantilla-id (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 siguientes dos son operator + y operator type formar. 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 indiquen en qué espacio de nombres o clase debe buscarse un nombre.

Una expresión dependiente del valor 1 + N no es un nombre, pero N es. El subconjunto de todas las construcciones dependientes que son nombres se llama nombre dependiente. Los nombres de funciones, sin embargo, pueden tener un significado diferente en diferentes instancias de una plantilla, pero desafortunadamente no son 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 una excepción que se manejan por separado. Un nombre de función de identificador no depende de sí mismo, sino de las expresiones de argumento dependientes de tipo utilizadas en una llamada. En el ejemplo f((T)0), f es un nombre dependiente En el Estándar, esto se especifica en (14.6.2/1).

Notas y ejemplos adicionales

En suficientes casos, necesitamos ambos typename y template. Tu código debería 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 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 está permitido 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 inicializadores del constructor:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • En el uso de declaraciones no es posible usar template después de la última ::y el comité 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
     };
    

931
2018-03-05 00:27



C ++ 11

Problema

Mientras que las reglas en C ++ 03 sobre cuándo necesitas 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 desambiguación, incluso si el compilador podría perfectamente entender que A::result_type Solo puede ser int (y por lo tanto es un tipo), y this->g solo puede ser la plantilla de miembro g declarado más tarde (incluso si A está explícitamente especializado en alguna parte, que no afectaría 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 idioma rastrea cuando un tipo se refiere a la plantilla adjunta. Para saberlo, el tipo debe haberse formado usando una cierta forma de nombre, que es su propio nombre (en el anterior, A, A<T>, ::A<T>) Se sabe que un tipo al que hace referencia ese nombre es el 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::NestedClass y A son ambas instanciaciones actuales).

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

Las palabras clave typename y template ya no se requieren más si el calificador es miembro de la creación de instancias actual. Un punto clave aquí para recordar es que A<T> es todavía un nombre dependiente del tipo (después de todo T también depende del tipo). Pero A<T>::result_type se sabe que es un tipo: el compilador buscará "mágicamente" este tipo de tipos dependientes para resolverlo.

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 nuevamente mira hacia arriba D::result_type cuando crea una instancia D::f (incluso si encontró su significado ya 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 C Me gusta esto

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. Así que obtienes lo mejor de los dos mundos: búsqueda "retrasada" que te protege si pudieras tener problemas con clases base dependientes, y también 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 miembro de la instanciación actual. En cambio, el lenguaje lo marca como un miembro de una especialización desconocida. En particular, este es siempre el caso cuando estás haciendo DependentTypeName::Foo o DependentTypedName->Foo y el tipo dependiente es no la instanciación actual (en cuyo caso el compilador puede darse por vencido y decir "veremos más adelante Foo es) o es la instanciación actual y el nombre no se encontraron en él o en sus clases base no dependientes y también hay clases base dependientes.

Imagina lo que sucede si tuviéramos una función miembro h dentro de lo definido anteriormente A plantilla de clase

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

Ejemplos y curiosidades

Puedes probar este conocimiento en esta respuesta y vea 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 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 se uniría this->f a A::f en el momento de instanciación y todo está bien. C ++ 11 sin embargo, inmediatamente se une a B::f y requiere una doble verificación al crear instancias, verificando si la búsqueda aún coincide. Sin embargo, al crear instancias C<A>::g, el Regla de dominación aplica y la búsqueda encontrará A::f en lugar.


118
2017-07-10 20:02



PREFACIO

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

El propósito subyacente es el mismo; una explicación a "¿Cuándo?" ¿y por qué?" typename y template debe ser aplicado.


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

typename y template se pueden usar en circunstancias que no sean al declarar una plantilla.

Hay ciertos contextos en C ++ donde al compilador se le debe decir explícitamente cómo tratar un nombre, y todos estos contextos tienen algo en común; ellos dependen de al menos uno template-parameter.

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 nombres dependientesy las dos palabras clave


UN SNIPPET DICE MÁS DE 1000 PALABRAS

Intenta explicar lo que está sucediendo en el siguiente plantilla de función, ya sea a ti mismo, a un amigo, o tal vez a tu gato; qué está sucediendo en la declaración marcada (UN)?

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


Puede que no sea tan fácil como uno piensa, más específicamente el resultado de la evaluación (UNfuertemente depende sobre la definición del tipo pasado como parámetro de plantilla T.

Diferente Ts puede 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 (do), tendremos una declaración de puntero a int llamado X, pero;

  • si instanciamos la plantilla con el tipo Y, como en (re), (UN) en cambio 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, la Norma exige que solucionemos la ambigüedad de una nombre-dependiente por explícitamente indicando el intento en cualquier lugar que nos gustaría tratar el nombre como un escribe un nombre, o un template-id.

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



CÓMO MANEJAR NOMBRES DEPENDIENTES?

Si esta fue una película de Hollywood, nombres dependientes sería 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 programa perso- erhm .. mal formado.

UN nombre-dependiente es alguna nombre que directamente, o indirectamente, depende de un template-parameter.

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 dependiente nombres en el fragmento de arriba:

  • mi)
    • "tipo" depende de la instanciación de SomeTrait<T>, que incluye Ty
  • F)
    • "NestedTrait", el cual es un template-id, depende de SomeTrait<T>y
    • "tipo" al final de (F) depende de NestedTrait, que depende de SomeTrait<T>y
  • GRAMO)
    • "datos", que se parece a un plantilla de función de miembro, es indirectamente un nombre-dependiente ya que el tipo de foo depende de la instanciación de SomeTrait<T>.

Ninguno de los enunciados (mi), (F) o (GRAMO) es válido si el compilador interpreta el nombres dependientes como variables / funciones (que, como se dijo anteriormente, es lo que sucede si no se dice explícitamente lo contrario).

LA SOLUCIÓN

Para hacer g_tmpl tener una definición válida debemos decirle explícitamente al compilador que esperamos un tipo (mi), un template-id y un tipo en (F) y un template-id en (GRAMO)

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 nombre denota un tipo, todas  nombres involucrado debe ser cualquiera nombres de tipos o espacios de nombresCon esto en mente, es bastante fácil ver que aplicamos typename al comienzo de nuestra plena nombre calificado.

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



PUEDO SOLO PEGAR EL PALABRAS CLAVE ¿FRENTE A CUALQUIER NOMBRE?

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

Las reglas de la Norma establecen que puede aplicar las palabras clave siempre que se trate de una nombre calificado (K), pero si el nombre no es 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: Aplicando typename o template en 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 typename y template son explícitamente no permitido

  • Al especificar las bases de las cuales una clase hereda

    Cada nombre escrito en una clase derivada base-specifier-list ya se trata como un escribe un nombre, especificando explícitamente typename es 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 referido en una clase derivada using-directive

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    

73
2018-06-07 20:28



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 crearse una instancia, por lo tanto, la pestaña "fallar" nunca fallará. 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 :: Variante

PS2: echa un vistazo a listas de materiales, notablemente en el libro de Andrei Alexandrescu: Modern C ++ Design


17
2018-03-04 13:37



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


La regla general para poner el typename la palabra clave es mayormente cuando usa un parámetro de plantilla y desea acceder a un archivo anidado typedef o usando-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 metafunciones o cosas que también toman los parámetros genéricos de la plantilla. Sin embargo, si el parámetro de plantilla proporcionado es un tipo explícito, entonces no es necesario 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 template Los calificadores son en su mayoría similares, excepto que generalmente involucran funciones de miembros con plantilla (estáticas o de otro tipo) de una estructura / clase que a su vez está modelada, 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 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ías la template palabra clave de antemano y llámala así:

t.template get<int>()

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


14
2018-06-06 22:23