c++ - ¿Dónde y por qué tengo que colocar las palabras clave "plantilla" y "nombre de tipo"?




templates typename (4)

En las plantillas, ¿dónde y por qué tengo que poner el typename y la template en los nombres dependientes? ¿Cuáles 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 toda la razón al ahogarse en él. También sé que debería poder agregar una template algún lugar para decirle al compilador que inUnion es una plantilla-id. Pero ¿dónde exactamente? ¿Y debe asumir que inUnion es una plantilla de clase, es decir, inUnion<U> nombra un tipo y no una función?


PREFACIO

Esta publicación pretende ser una alternativa fácil de leer para la publicación de litb .

El propósito subyacente es el mismo; Una explicación para "¿Cuándo?" ¿y por qué?" typename debe aplicar el nombre typename y la template .

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

typename y la template pueden utilizar en circunstancias distintas de cuando se declara una plantilla.

Hay ciertos contextos en C ++ donde el compilador debe saber explícitamente 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 de los dependientes y las dos palabras clave.

UNA SNIPPET DICE MÁS DE 1000 PALABRAS

Intente explicar lo que sucede en la siguiente función de plantilla , ya sea a usted mismo, a un amigo o tal vez a 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-plantilla T

Diferentes T s 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 creamos una instancia de la función-plantilla 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 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 potencialmente de sorpresas desagradables, el Estándar exige que clasifiquemos la ambigüedad de un nombre dependiente indicando explícitamente la intención en cualquier lugar en el que nos gustaría tratar el nombre como un nombre de tipo o una plantilla. identificación

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


¿CÓMO MANEJAR NOMBRES DEPENDIENTES ?

Si se tratara de una película de Hollywood, los nombres de los dependientes serían la enfermedad que se propaga a través del contacto corporal y afecta de inmediato a su anfitrión para confundirlo. Confusión que podría, posiblemente, llevar a un programa de personas, erhm .. mal formado.

Un nombre dependiente es cualquier nombre que, directa o indirectamente, depende 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 SomeTrait<T> de instancias de SomeTrait<T> , que incluye T , y;
  • F )
    • "NestedTrait" , que es una plantilla-id , depende de SomeTrait<T> , y;
    • "tipo" al final de ( F ) depende de NestedTrait , que depende de SomeTrait<T> , y;
  • G )
    • "data" , que parece una plantilla de función miembro , es indirectamente un nombre dependiente ya que el tipo de foo depende de la SomeTrait<T> de instancias de SomeTrait<T> .

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

LA SOLUCIÓN

Para hacer que g_tmpl tenga una definición válida, debemos indicar 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 , teniendo esto en cuenta que es bastante fácil ver que aplicamos el typename al comienzo de nuestro nombre completo.

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


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

" ¿Puedo simplemente typename y la 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 esté tratando con un nombre calificado ( K ), pero si el nombre no está calificado, la aplicación no está bien 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 : la aplicación de un typename o una template en un contexto donde no se requiere no se considera una buena práctica; solo porque puedas hacer algo, no significa que debas.


Además, hay contextos en los que el typename y la template están explícitamente prohibidos:

  • Al especificar las bases de las que hereda una clase.

    Todos los nombres escritos en una lista de especificadores base de una clase derivada ya se tratan como un nombre de tipo , especificando explícitamente que typename está mal formado y es redundante.

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


  • Cuando la plantilla-id es a la que se hace referencia en una directiva de uso de clase derivada

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

C ++ 11

Problema

Si bien las reglas en C ++ 03 sobre cuándo necesita el typename y la template son razonables, existe 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 podría entender perfectamente 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 adelante (Incluso si A está especializado explícitamente en algún lugar, eso 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 lenguaje realiza un seguimiento cuando un tipo se refiere a la plantilla adjunta. Para saber eso, el tipo se debe haber formado usando una cierta forma de nombre, que es su propio nombre (en lo anterior, A , A<T> , ::A<T> ). Se sabe que un tipo al que se hace referencia con ese nombre es la instanciación actual . Puede haber varios tipos que sean todos los ejemplos actuales si el tipo a partir del cual se forma el nombre es un miembro / clase anidada (entonces, A::NestedClass y A son ambos A::NestedClass actuales).

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

Las palabras clave typename y template ya no son necesarias si el calificador es un miembro de la instanciación actual. Un punto clave aquí para recordar es que A<T> sigue siendo un nombre dependiente del tipo (después de todo, T también es dependiente del tipo). Pero se sabe que A<T>::result_type es un tipo: el compilador buscará "mágicamente" este tipo de tipos dependientes para resolver 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 incluso va más allá y requiere que una implementación busque de nuevo D::result_type al crear una instancia de D::f (incluso si 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 pasa si definimos C como esta

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

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

Especializaciones desconocidas

En el código de D , el nombre typename D::questionable_type no es un 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 se hace DependentTypeName::Foo o DependentTypedName->Foo y el tipo dependiente no es la creación de instancias actual (en cuyo caso el compilador puede darse por vencido y decir "veremos más adelante 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 A 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 manera válida de crear A<T>::h instancia de A<T>::h (cualquier argumento que le dé a T ). En C ++ 11, el lenguaje ahora tiene una verificación adicional para dar más razón a los compiladores para implementar 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 un miembro de la instanciación actual ni un miembro de una especialización desconocida. En ese caso, no debe haber ninguna manera de que ese código pueda compilarse válidamente en el momento de la creación de instancias, por lo que el lenguaje prohíbe un nombre donde el calificador sea la creación de instancias actual para que no sea miembro de una especialización desconocida ni miembro de la creación de instancias actual , esta infracción aún no está obligada a ser diagnosticada).

Ejemplos y trivialidades.

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 en esa respuesta).

Las reglas de C ++ 11 hacen que el siguiente código válido de C ++ 03 no esté bien formado (lo que 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 de C ++ 03 válido vincularía this->f a A::f en el momento de la creación de instancias y todo está bien. Sin embargo, C ++ 11 se une inmediatamente a B::f y requiere una doble verificación al crear instancias, verificando si la búsqueda aún coincide. Sin embargo, al crear una instancia de C<A>::g , se aplica la Regla de Dominación y la búsqueda encontrará A::f lugar.


Para analizar un programa en 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. 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 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 sabrá el compilador a qué se refiere el nombre t::x , si t hace referencia a un parámetro de tipo de plantilla? x podría ser un miembro de datos int estáticos 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 denomina nombre dependiente ("depende" de los parámetros de la plantilla).

Puede recomendar que espere hasta que el usuario cree una 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, 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 creación de instancias, analizan la plantilla y posiblemente detectan errores en la definición. Pero en lugar de molestar a los usuarios de la plantilla (¡pobres colegas!) Por los 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 produzca 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: decidimos cómo el compilador debe analizar esto. Si t::x es un nombre dependiente, entonces debemos prefijarlo con un typename para decirle al compilador que lo typename de cierta manera. El estándar 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 da nombre a un tipo a menos que la búsqueda de nombre aplicable encuentre un nombre de tipo o el nombre se califique con la palabra clave typename.

Hay muchos nombres para los que typename no es necesario, porque el compilador puede, con la búsqueda de nombres aplicable en la definición de la plantilla, averiguar cómo analizar una construcción en sí misma, por ejemplo con T *f; , cuando T es un parámetro de tipo de plantilla. Pero para t::x * f; para 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 encuentra que 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 solo permite el typename antes de los nombres calificados ; por lo tanto, se considera que siempre se sabe que los nombres no calificados hacen referencia a los tipos si lo hacen.

Un gotcha similar existe para los nombres que denotan plantillas, como se indica 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. Imagina la siguiente definición arbitraria de boost::function y f :

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

¡Esa es en realidad una expresión válida! Utiliza el operador menor que para comparar boost::function zero ( int() ), y luego usa el operador mayor que para comparar el bool resultante con f . Sin embargo, como usted bien sabe, 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) se encuentra que un nombre es un nombre de plantilla, si este nombre va seguido de un <, el <siempre se toma como el principio de una lista de argumentos de la plantilla y nunca como un nombre seguido por el menos 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? Necesitaremos insertar la template inmediatamente antes del nombre de la plantilla, como se especifica en 14.2/4 . Esto se ve como

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

Los nombres de las plantillas no solo pueden aparecer después de un :: sino también después de un -> o . en una clase de acceso de miembros. Necesitas insertar la palabra clave allí también:

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

Dependencias

Para las personas que tienen libros gruesos de Standardese en su estantería y que quieren saber de qué estaba hablando exactamente, hablaré un poco sobre cómo se especifica esto en la Norma.

En las declaraciones de plantillas, algunas construcciones tienen significados diferentes según los argumentos de la plantilla que use para crear instancias 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 los parámetros de la 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 adjuntos:

  • Tipos dependientes (por ejemplo: un parámetro de plantilla tipo T )
  • Expresiones dependientes del valor (por ejemplo, un parámetro de plantilla no 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 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 de tipo y (14.6.2.3) para expresiones dependientes de valor.

Nombres dependientes

El estándar es un poco confuso sobre qué es exactamente un nombre dependiente . En una lectura simple (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 buscarse en el contexto de creación de instancias, también debe ser un nombre dependiente (afortunadamente, a partir de C ++ 14, el comité ha comenzado a estudiar 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. Esos nombres son por lo tanto "nombres dependientes". Un nombre puede tomar diferentes formas; la norma dice:

Un nombre es un 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 dos siguientes son el operator + y el formulario de operator type . El último formulario es template-name <argument list> . Todos estos son nombres, y por el uso convencional en la Norma, un nombre también puede incluir calificadores que indican 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 . Sin embargo, los nombres de funciones pueden tener diferentes significados en diferentes instancias de una plantilla, pero desafortunadamente no están atrapados en esta regla general.

Nombres de funciones dependientes

No es una preocupación primordial de este artículo, pero merece la pena mencionarlo: los nombres de funciones son una excepción que se manejan por separado. Un nombre de función de identificador depende no solo de sí mismo, sino de las expresiones de argumento dependientes del 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 adicionales y ejemplos.

En suficientes casos necesitamos tanto el nombre de typename como la 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 template palabras clave 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 el typename . Se asume que el nombre dado es un nombre de tipo de clase. Esto es cierto tanto para los nombres en la lista de clases base como para la lista de inicializadores del constructor:

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

Esta respuesta pretende ser más bien breve y dulce para responder (parte de) la pregunta del título. Si desea una respuesta con más detalles que explique por qué tiene que ponerlos allí, por favor vaya here .

La regla general para colocar la palabra clave typename es principalmente cuando está utilizando un parámetro de plantilla y desea acceder a un typedef anidado o alias-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 funciones meta o cosas que también toman parámetros de plantilla genéricos. Sin embargo, si el parámetro de plantilla proporcionado es un tipo explícito, no tiene que especificar el 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 de la template son en su mayoría similares, excepto que generalmente involucran funciones miembro con plantilla (estáticas o no) de una estructura / clase que a su vez 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
}

Si intenta acceder a t.get<int>() desde el interior de la función, se producirá 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 palabra clave de la template antemano y la llamarías así:

t.template get<int>()

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





dependent-name