Namespaces
Variants

Dependent names

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Dentro de la definición de una template (tanto class template como function template ), el significado de algunas construcciones puede diferir de una instanciación a otra. En particular, los tipos y expresiones pueden depender de tipos de parámetros de template de tipo y valores de parámetros de template constantes.

template<typename T>
struct X : B<T> // “B<T>” depende de T
{
    typename T::A* pa; // “T::A” depende de T
                       // (ver más abajo para el significado de este uso de “typename”)
    void f(B<T>* pb)
    {
        static int i = B<T>::i; // “B<T>::i” depende de T
        pb->j++; // “pb->j” depende de T
    }
};

Nombre lookup y vinculación son diferentes para nombres dependientes y nombres no dependientes.

Contenidos

Reglas de enlace

Los nombres no dependientes se buscan y vinculan en el punto de definición de la plantilla. Esta vinculación se mantiene incluso si en el punto de instanciación de la plantilla existe una coincidencia mejor:

#include <iostream>
void g(double) { std::cout << "g(double)\n"; }
template<class T>
struct S
{
    void f() const
    {
        g(1); // "g" es un nombre no dependiente, vinculado ahora
    }
};
void g(int) { std::cout << "g(int)\n"; }
int main()
{
    g(1);  // llama a g(int)
    S<int> s;
    s.f(); // llama a g(double)
}

Si el significado de un nombre no dependiente cambia entre el contexto de definición y el punto de instanciación de una especialización de la plantilla, el programa está mal formado, sin diagnóstico requerido. Esto es posible en las siguientes situaciones:

  • un tipo utilizado en un nombre no dependiente es incompleto en el punto de definición pero completo en el punto de instanciación
  • la búsqueda de un nombre en la definición de plantilla encontró una using-declaration , pero la búsqueda en el ámbito correspondiente en la instanciación no encuentra ninguna declaración porque la using-declaration era una expansión de paquete y el paquete correspondiente está vacío
(desde C++17)
  • una instanciación utiliza un argumento por defecto o un argumento de plantilla por defecto que no había sido definido en el punto de definición
  • una expresión constante en el punto de instanciación utiliza el valor de un objeto const de tipo integral o enumeración sin ámbito , el valor de un objeto constexpr, el valor de una referencia, o la definición de una función constexpr (desde C++11) , y ese objeto /referencia/función (desde C++11) no fue definido en el punto de definición
  • la plantilla utiliza una especialización de plantilla de clase no dependiente o especialización de plantilla de variable (desde C++14) en el punto de instanciación, y esta plantilla que utiliza está instanciada a partir de una especialización parcial que no fue definida en el punto de definición o nombra una especialización explícita que no fue declarada en el punto de definición

El enlace de nombres dependientes se pospone hasta que tiene lugar la búsqueda.

Reglas de búsqueda

La búsqueda de un nombre dependiente utilizado en una plantilla se pospone hasta que se conocen los argumentos de la plantilla, momento en el que

  • la búsqueda no-ADL examina declaraciones de funciones con enlace externo que son visibles desde el contexto de definición de la plantilla
  • ADL examina declaraciones de funciones con enlace externo que son visibles desde el contexto de definición de la plantilla o el contexto de instanciación de la plantilla

(en otras palabras, agregar una nueva declaración de función después de la definición de plantilla no la hace visible, excepto mediante ADL).

El propósito de esta regla es ayudar a proteger contra violaciones de la ODR para instanciaciones de plantillas:

// una biblioteca externa
namespace E
{
    template<typename T>
    void writeObject(const T& t)
    {
        std::cout << "Valor = " << t << '\n';
    }
}
// unidad de traducción 1:
// El Programador 1 quiere permitir que E::writeObject funcione con vector<int>
namespace P1
{
    std::ostream& operator<<(std::ostream& os, const std::vector<int>& v)
    {
        for (int n : v)
            os << n << ' ';
        return os;
    }
    void doSomething()
    {
        std::vector<int> v;
        E::writeObject(v); // Error: no encontrará P1::operator<<
    }
}
// unidad de traducción 2:
// El Programador 2 quiere permitir que E::writeObject funcione con vector<int>
namespace P2
{
    std::ostream& operator<<(std::ostream& os, const std::vector<int>& v)
    {
        for (int n : v)
            os << n << ':';
        return os << "[]";
    }
    void doSomethingElse()
    {
        std::vector<int> v;
        E::writeObject(v); // Error: no encontrará P2::operator<<
    }
}

En el ejemplo anterior, si la búsqueda no-ADL para operator<< estuviera permitida desde el contexto de instanciación, la instanciación de E :: writeObject < vector < int >> tendría dos definiciones diferentes: una usando P1 :: operator << y otra usando P2 :: operator << . Tal violación de ODR podría no ser detectada por el enlazador, llevando a que se use una u otra en ambas instancias.

Para que ADL examine un espacio de nombres definido por el usuario, ya sea std::vector debe ser reemplazado por una clase definida por el usuario o su tipo de elemento debe ser una clase definida por el usuario:

namespace P1
{
    // si C es una clase definida en el espacio de nombres P1
    std::ostream& operator<<(std::ostream& os, const std::vector<C>& v)
    {
        for (C n : v)
            os << n;
        return os;
    }
    void doSomething()
    {
        std::vector<C> v;
        E::writeObject(v); // OK: instancia writeObject(std::vector<P1::C>)
                           //     que encuentra P1::operator<< mediante ADL
    }
}

Nota: esta regla hace poco práctico sobrecargar operadores para tipos de la biblioteca estándar:

#include <iostream>
#include <iterator>
#include <utility>
#include <vector>
// Mala idea: operador en el espacio de nombres global, pero sus argumentos están en std::
std::ostream& operator<<(std::ostream& os, std::pair<int, double> p)
{
    return os << p.first << ',' << p.second;
}
int main()
{
    typedef std::pair<int, double> elem_t;
    std::vector<elem_t> v(10);
    std::cout << v[0] << '\n'; // OK: la búsqueda ordinaria encuentra ::operator<<
    std::copy(v.begin(), v.end(),
              std::ostream_iterator<elem_t>(std::cout, " "));
    // Error: tanto la búsqueda ordinaria desde el punto de definición de
    // std::ostream_iterator como ADL solo considerarán el espacio de nombres std,
    // y encontrarán muchas sobrecargas de std::operator<<, por lo que la búsqueda se realizará.
    // La resolución de sobrecarga fallará entonces al encontrar operator<< para elem_t
    // en el conjunto encontrado por la búsqueda.
}

Nota: la búsqueda limitada (pero no el enlace) de nombres dependientes también ocurre en el momento de la definición de la plantilla, según sea necesario para distinguirlos de nombres no dependientes y también para determinar si son miembros de la instanciación actual o miembros de especialización desconocida. La información obtenida por esta búsqueda puede usarse para detectar errores, véase más abajo.

Tipos dependientes

Los siguientes tipos son dependent types :

  • parámetro de plantilla
  • un miembro de una especialización desconocida (ver abajo)
  • una clase/enumeración anidada que es un miembro dependiente de especialización desconocida (ver abajo)
  • una versión calificada cv de un tipo dependiente
  • un tipo compuesto construido a partir de un tipo dependiente
  • un tipo de array cuyo tipo de elemento es dependiente o cuyo límite (si existe) es dependiente de valor
  • un tipo de función cuyos parámetros incluyen uno o más parameter packs de función
(since C++11)
  • un tipo de función cuya especificación de excepciones depende de valores
  • un template-id donde cualquiera
  • el nombre de la plantilla es un parámetro de plantilla, o
  • cualquiera de los argumentos de plantilla es dependiente de tipo, o dependiente de valor , o es una expansión de paquete (desde C++11) (incluso si el template-id se utiliza sin su lista de argumentos, como injected-class-name )
  • el resultado de decltype aplicado a una expresión dependiente de tipo

El resultado de decltype aplicado a una expresión dependiente de tipo es un tipo dependiente único. Dos de estos resultados se refieren al mismo tipo solo si sus expresiones son equivalentes .

(desde C++11)

El especificador de indexación de paquetes aplicado a una expresión constante dependiente de tipo es un tipo dependiente único. Dos de estos especificadores de indexación de paquetes se refieren al mismo tipo solo si sus expresiones constantes son equivalentes. De lo contrario, dos de estos especificadores de indexación de paquetes se refieren al mismo tipo solo si sus índices tienen el mismo valor.

(desde C++26)

Nota: un typedef miembro de una instanciación actual solo es dependiente cuando el tipo al que se refiere lo es.

Expresiones dependientes del tipo

Las siguientes expresiones son type-dependent :

  • contiene un identificador para el cual la búsqueda de nombre encuentra al menos una declaración dependiente
  • contiene un template-id dependiente
  • contiene el identificador especial __func__ (si alguna función envolvente es una plantilla, un miembro no plantilla de una plantilla de clase , o una lambda genérica (since C++14) )
(since C++11)
  • contiene el nombre de la función de conversión a un tipo dependiente
  • contiene un especificador de nombre anidado o un qualified-id que es miembro de una especialización desconocida
  • nombra un miembro dependiente de la instanciación actual que es un miembro de datos estáticos de tipo "array de límite desconocido"
  • contiene un identificador para el cual la búsqueda de nombre encuentra una o más declaraciones de funciones miembro de la instanciación actual declaradas con deducción de tipo de retorno
(desde C++14)
  • contiene un identificador para el cual la búsqueda de nombre encuentra una declaración de enlace estructurado cuyo inicializador es dependiente del tipo
  • contiene un identificador para el cual la búsqueda de nombre encuentra un parámetro de plantilla constante cuyo tipo contiene el marcador de posición auto
  • contiene un identificador para el cual la búsqueda de nombre encuentra una variable declarada con un tipo que contiene un tipo de marcador de posición (por ejemplo, auto miembro de datos estático), donde el inicializador es dependiente del tipo,
(desde C++17)
  • contiene un identificador para el cual la búsqueda de nombre encuentra un paquete
(desde C++26)
  • cualquier expresión de conversión a un tipo dependiente
  • new expresión que crea un objeto de un tipo dependiente
  • expresión de acceso a miembro que hace referencia a un miembro de la instanciación actual cuyo tipo es dependiente
  • expresión de acceso a miembro que hace referencia a un miembro de especialización desconocida
(desde C++17)
(desde C++26)

Las siguientes expresiones nunca son dependientes del tipo porque los tipos de estas expresiones no pueden serlo:

(desde C++11)
(desde C++20)

Expresiones dependientes de valor

Las siguientes expresiones son value-dependent :

  • Es un concept-id y cualquiera de sus argumentos es dependiente.
(desde C++20)
  • Es dependiente del tipo.
  • Es el nombre de un parámetro de plantilla constante.
  • Nombra un miembro de datos estático que es un miembro dependiente de la instanciación actual y no está inicializado.
  • Nombra una función miembro estática que es un miembro dependiente de la instanciación actual.
  • Es una constante con un tipo entero o enumeración (hasta C++11) literal (desde C++11) , inicializada a partir de una expresión dependiente de valor.
  • las siguientes expresiones donde el operando es una expresión dependiente del tipo:
(desde C++11)
  • las siguientes expresiones donde el operando es un type-id dependiente:
  • las siguientes expresiones donde el tipo destino es dependiente o el operando es una expresión dependiente de tipo:
  • function-style cast expresión donde el tipo destino es dependiente o una expresión dependiente de valor está encerrada entre paréntesis o llaves (desde C++11)
(desde C++11)
(desde C++17)
  • expresión address-of donde el argumento es un identificador calificado que nombra un miembro dependiente de la instanciación actual
  • expresión address-of donde el argumento es cualquier expresión que, evaluada como una expresión constante principal, se refiere a una entidad con plantilla que es un objeto con duración de almacenamiento estático o de hilo (desde C++11) o una función miembro.

Nombres dependientes

Instanciación actual

Dentro de la definición de una plantilla de clase (incluyendo sus funciones miembro y clases anidadas) algunos nombres pueden deducirse como referencias a la instanciación actual . Esto permite detectar ciertos errores en el punto de definición, en lugar de en la instanciación, y elimina el requisito de los desambiguadores typename y template para nombres dependientes, ver más abajo.

Solo los siguientes nombres pueden referirse a la instanciación actual:

  • en la definición de una plantilla de clase, una clase anidada de una plantilla de clase, un miembro de una plantilla de clase, o un miembro de una clase anidada de una plantilla de clase:
    • el nombre de clase inyectado de la plantilla de clase o clase anidada
  • en la definición de una plantilla de clase primaria o un miembro de una plantilla de clase primaria:
    • el nombre de la plantilla de clase seguido de la lista de argumentos de plantilla (o una especialización de plantilla de alias equivalente) para la plantilla primaria donde cada argumento es equivalente (definido a continuación) a su parámetro correspondiente.
  • en la definición de una clase anidada de una plantilla de clase:
    • el nombre de la clase anidada usado como miembro de la instanciación actual
  • en la definición de una especialización parcial de plantilla de clase o un miembro de una especialización parcial de plantilla de clase:
    • el nombre de la plantilla de clase seguido de la lista de argumentos de plantilla para la especialización parcial, donde cada argumento es equivalente a su parámetro correspondiente
  • en la definición de una función plantilla :

Un argumento de plantilla es equivalente a un parámetro de plantilla si

  • para un parámetro de tipo , el argumento de plantilla denota el mismo tipo que el parámetro de plantilla.
  • para un parámetro constante , el argumento de plantilla es un identificador que nombra una variable que es equivalente al parámetro de plantilla. Una variable es equivalente a un parámetro de plantilla si
  • tiene el mismo tipo que el parámetro de plantilla (ignorando calificadores cv) y
  • su inicializador consiste en un único identificador que nombra el parámetro de plantilla o, recursivamente, dicha variable.
template<class T>
class A
{
    A* p1;      // A es la instanciación actual
    A<T>* p2;   // A<T> es la instanciación actual
    ::A<T>* p4; // ::A<T> es la instanciación actual
    A<T*> p3;   // A<T*> no es la instanciación actual
    class B
    {
        B* p1;                 // B es la instanciación actual
        A<T>::B* p2;           // A<T>::B es la instanciación actual
        typename A<T*>::B* p3; // A<T*>::B no es la instanciación actual
    };
};
template<class T>
class A<T*>
{
    A<T*>* p1; // A<T*> es la instanciación actual
    A<T>* p2;  // A<T> no es la instanciación actual
};
template<int I>
struct B
{
    static const int my_I = I;
    static const int my_I2 = I + 0;
    static const int my_I3 = my_I;
    static const long my_I4 = I;
    static const int my_I5 = (I);
    B<my_I>* b1;  // B<my_I> es la instanciación actual:
                  //   my_I tiene el mismo tipo que I,
                  //   y está inicializado únicamente con I
    B<my_I2>* b2; // B<my_I2> no es la instanciación actual:
                  //   I + 0 no es un identificador único
    B<my_I3>* b3; // B<my_I3> es la instanciación actual:
                  //   my_I3 tiene el mismo tipo que I,
                  //   y está inicializado únicamente con my_I (que equivale a I)
    B<my_I4>* b4; // B<my_I4> no es la instanciación actual:
                  //   el tipo de my_I4 (long) no es el mismo que el tipo de I (int)
    B<my_I5>* b5; // B<my_I5> no es la instanciación actual:
                  //   (I) no es un identificador único
};

Tenga en cuenta que una clase base puede ser la instanciación actual si una clase anidada deriva de su clase contenedora plantilla. Las clases base que son tipos dependientes pero no son la instanciación actual son dependent base classes :

template<class T>
struct A
{
    typedef int M;
    struct B
    {
        typedef void M;
        struct C;
    };
};
template<class T>
struct A<T>::B::C : A<T>
{
    M m; // OK, A<T>::M
};

Un nombre se clasifica como miembro de la instanciación actual si es

  • un nombre no calificado que se encuentra mediante búsqueda no calificada en la instanciación actual o en su base no dependiente.
  • nombre calificado , si el calificador (el nombre a la izquierda de :: ) nombra la instanciación actual y la búsqueda encuentra el nombre en la instanciación actual o en su base no dependiente
  • un nombre usado en una expresión de acceso a miembro de clase ( y en x. y o xp - > y ), donde la expresión de objeto ( x o * xp ) es la instanciación actual y la búsqueda encuentra el nombre en la instanciación actual o en su base no dependiente
template<class T>
class A
{
    static const int i = 5;
    int n1[i];       // i se refiere a un miembro de la instanciación actual
    int n2[A::i];    // A::i se refiere a un miembro de la instanciación actual
    int n3[A<T>::i]; // A<T>::i se refiere a un miembro de la instanciación actual
    int f();
};
template<class T>
int A<T>::f()
{
    return i; // i se refiere a un miembro de la instanciación actual
}

Los miembros de la instanciación actual pueden ser tanto dependientes como no dependientes.

Si la búsqueda de un miembro de la instanciación actual da un resultado diferente entre el punto de instanciación y el punto de definición, la búsqueda es ambigua. Sin embargo, nótese que cuando se utiliza un nombre de miembro, no se convierte automáticamente a una expresión de acceso a miembro de clase; solo las expresiones de acceso a miembro explícitas indican miembros de la instanciación actual:

struct A { int m; };
struct B { int m; };
template<typename T>
struct C : A, T
{
    int f() { return this-> m; } // encuentra A::m en el contexto de definición de la plantilla
    int g() { return m; }       // encuentra A::m en el contexto de definición de la plantilla
};
template int C<B>::f(); // error: encuentra tanto A::m como B::m
template int C<B>::g(); // OK: la transformación a la sintaxis de acceso a miembros de clase
                        // no ocurre en el contexto de definición de la plantilla

Especializaciones desconocidas

Dentro de una definición de plantilla, ciertos nombres se deducen como pertenecientes a una especialización desconocida , en particular,

  • un nombre calificado , si cualquier nombre que aparece a la izquierda de :: es un tipo dependiente que no es miembro de la instanciación actual
  • un nombre calificado , cuyo calificador es la instanciación actual, y el nombre no se encuentra en la instanciación actual ni en ninguna de sus clases base no dependientes, y existe una clase base dependiente
  • un nombre de miembro en una expresión de acceso a miembro de clase (la y en x. y o xp - > y ), si el tipo de la expresión de objeto ( x o * xp ) es un tipo dependiente y no es la instanciación actual
  • un nombre de miembro en una expresión de acceso a miembro de clase (la y en x. y o xp - > y ), si el tipo de la expresión de objeto ( x o * xp ) es la instanciación actual, y el nombre no se encuentra en la instanciación actual ni en ninguna de sus clases base no dependientes, y existe una clase base dependiente
template<typename T>
struct Base {};
template<typename T>
struct Derived : Base<T>
{
    void f()
    {
        // Derived<T> se refiere a la instanciación actual
        // no existe "unknown_type" en la instanciación actual
        // pero hay una base dependiente (Base<T>)
        // Por lo tanto, "unknown_type" es un miembro de especialización desconocida
        typename Derived<T>::unknown_type z;
    }
};
template<>
struct Base<int> // esta especialización lo proporciona
{
    typedef int unknown_type;
};


Esta clasificación permite detectar los siguientes errores en el punto de definición de la plantilla (en lugar de en la instanciación):

  • Si cualquier definición de plantilla tiene un nombre calificado en el cual el calificador se refiere a la instanciación actual y el nombre no es ni un miembro de la instanciación actual ni un miembro de especialización desconocida, el programa está mal formado (no se requiere diagnóstico) incluso si la plantilla nunca se instancia.
template<class T>
class A
{
    typedef int type;
    void f()
    {
        A<T>::type i; // OK: "type" es un miembro de la instanciación actual
        typename A<T>::other j; // Error:
        // "other" no es un miembro de la instanciación actual
        // y no es un miembro de una especialización desconocida
        // porque A<T> (que nombra la instanciación actual),
        // no tiene bases dependientes donde "other" pueda ocultarse.
    }
};
  • Si cualquier definición de plantilla tiene una expresión de acceso a miembro donde la expresión de objeto es la instanciación actual, pero el nombre no es un miembro de la instanciación actual ni un miembro de especialización desconocida, el programa está mal formado incluso si la plantilla nunca se instancia.

Los miembros de especialización desconocida son siempre dependientes, y se buscan y vinculan en el punto de instanciación como todos los nombres dependientes (ver arriba)

El desambiguador typename para nombres dependientes

En una declaración o definición de una plantilla, incluyendo plantillas de alias, un nombre que no es miembro de la instanciación actual y depende de un parámetro de plantilla no se considera un tipo a menos que se utilice la palabra clave typename o a menos que ya se haya establecido como nombre de tipo, por ejemplo con una declaración typedef o por ser utilizado para nombrar una clase base.

#include <iostream>
#include <vector>
int p = 1;
template<typename T>
void foo(const std::vector<T> &v)
{
    // std::vector<T>::const_iterator es un nombre dependiente,
    typename std::vector<T>::const_iterator it = v.begin();
    // sin "typename", lo siguiente se analiza como multiplicación
    // del miembro de datos dependiente del tipo "const_iterator"
    // y alguna variable "p". Dado que hay una "p" global visible
    // en este punto, esta definición de plantilla compila.
    std::vector<T>::const_iterator* p;
    typedef typename std::vector<T>::const_iterator iter_t;
    iter_t * p2; // "iter_t" es un nombre dependiente, pero se sabe que es un nombre de tipo
}
template<typename T>
struct S
{
    typedef int value_t; // miembro de la instanciación actual
    void f()
    {
        S<T>::value_t n{}; // S<T> es dependiente, pero no se necesita "typename"
        std::cout << n << '\n';
    }
};
int main()
{
    std::vector<int> v;
    foo(v); // la instanciación de la plantilla falla: no hay variable miembro
            // llamada "const_iterator" en el tipo std::vector<int>
    S<int>().f();
}

La palabra clave typename solo puede utilizarse de esta manera antes de nombres calificados (por ejemplo, T :: x ), pero los nombres no necesitan ser dependientes.

Se utiliza la búsqueda de nombres calificada usual para el identificador precedido por typename . A diferencia del caso con el especificador de tipo elaborado , las reglas de búsqueda no cambian a pesar del calificador:

struct A // A tiene una variable anidada X y un tipo anidado struct X
{
    struct X {};
    int X;
};
struct B
{
    struct X {}; // B tiene un tipo anidado struct X
};
template<class T>
void f(T t)
{
    typename T::X x;
}
void foo()
{
    A a;
    B b;
    f(b); // OK: instancia f<B>, T::X se refiere a B::X
    f(a); // error: no se puede instanciar f<A>:
          // porque la búsqueda de nombre calificado para A::X encuentra el miembro de datos
}

La palabra clave typename puede utilizarse incluso fuera de plantillas.

#include <vector>
int main()
{
    // Ambos correctos (después de resolver CWG 382)
    typedef typename std::vector<int>::const_iterator iter_t;
    typename std::vector<int> v;
}

En algunos contextos, solo los nombres de tipo pueden aparecer válidamente. En estos contextos, un nombre calificado dependiente se asume que nombra un tipo y no se requiere typename :

  • Un nombre calificado que aparece en identificador de tipo , donde el identificador de tipo contenedor más pequeño es:
(desde C++20)

El desambiguador template para nombres dependientes

De manera similar, en una definición de plantilla, un nombre dependiente que no es miembro de la instanciación actual no se considera un nombre de plantilla a menos que se utilice la palabra clave de desambiguación template o a menos que ya se haya establecido como nombre de plantilla:

template<typename T>
struct S
{
    template<typename U>
    void foo() {}
};
template<typename T>
void bar()
{
    S<T> s;
    s.foo<T>();          // error: < interpretado como operador menor que
    s.template foo<T>(); // Correcto
}

La palabra clave template solo puede utilizarse de esta manera después de los operadores :: (resolución de ámbito), - > (acceso a miembro mediante puntero), y . (acceso a miembro), los siguientes son todos ejemplos válidos:

  • T :: template foo < X > ( ) ;
  • s. template foo < X > ( ) ;
  • this - > template foo < X > ( ) ;
  • typename T :: template iterator < int > :: value_type v ;

Como es el caso de typename , el prefijo template está permitido incluso si el nombre no es dependiente o el uso no aparece en el ámbito de una plantilla.

Incluso si el nombre a la izquierda de :: se refiere a un namespace, se permite el desambiguador de plantilla:

template<typename>
struct S {};
::template S<void> q; // permitido, pero innecesario

Debido a las reglas especiales para la búsqueda de nombres no calificados de nombres de plantillas en expresiones de acceso a miembros, cuando un nombre de plantilla no dependiente aparece en una expresión de acceso a miembro (después de - > o después de . ), el desambiguador es innecesario si hay una plantilla de clase o alias (desde C++11) con el mismo nombre encontrada mediante búsqueda ordinaria en el contexto de la expresión. Sin embargo, si la plantilla encontrada por la búsqueda en el contexto de la expresión difiere de la encontrada en el contexto de la clase, el programa está mal formado (hasta C++11)

template<int>
struct A { int value; };
template<class T>
void f(T t)
{
    t.A<0>::value; // Ordinary lookup of A finds a class template.
                   // A<0>::value names member of class A<0>
    // t.A < 0;    // Error: “<” is treated as the start of template argument list
}
(hasta C++23)

Palabras clave

template , typename

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Se aplica a Comportamiento publicado Comportamiento correcto
CWG 206 C++98 no estaba especificado en qué punto se aplican las restricciones semánticas
cuando un tipo utilizado en un nombre no dependiente está
incompleto en el punto donde se define una plantilla pero está
completo en el punto donde se realiza una instanciación
el programa está mal formado
y no se requiere diagnóstico
en este caso
CWG 224 C++98 la definición de tipos dependientes se basaba
en la forma del nombre en lugar de la búsqueda
definición renovada
CWG 382 C++98 el desambiguador typename solo se permitía en el ámbito de plantillas también permitido fuera
de plantillas
CWG 468 C++98 el desambiguador template solo se permitía en el ámbito de plantillas también permitido fuera
de plantillas
CWG 502 C++98 no estaba especificado si las enumeraciones anidadas son dependientes dependientes como clases anidadas
CWG 1047 C++98 las expresiones typeid nunca eran dependientes de valor dependientes de valor si el
operando es dependiente de tipo
CWG 1160 C++98 no estaba especificado si un nombre se refiere a la instanciación actual
cuando un template-id que coincide con una plantilla principal o especialización
parcial aparece en la definición de un miembro de la plantilla
especificado
CWG 1413 C++98 miembro de datos estático no inicializado, función miembro estática, y dirección
de miembro de una plantilla de clase no estaban listados como dependientes de valor
listados
CWG 1471 C++98 un tipo anidado de una base no dependiente de
la instanciación actual era dependiente
no es dependiente
CWG 1850 C++98 la lista de casos donde el significado puede cambiar entre el
contexto de definición y el punto de instanciación estaba incompleta
completada
CWG 1929 C++98 no estaba claro si el desambiguador template puede
seguir a un :: donde el nombre a su izquierda se refiere a un espacio de nombres
permitido
CWG 2066 C++98 this nunca era dependiente de valor puede ser
dependiente de valor
CWG 2100 C++98 la dirección de un miembro de datos estático de plantilla de clase
no estaba listada como dependiente de valor
listada
CWG 2109 C++98 las expresiones de identificador dependientes de tipo podrían no ser dependientes de valor siempre son
dependientes de valor
CWG 2276 C++98 un tipo de función cuya especificación de excepciones
es dependiente de valor no era un tipo dependiente
lo es
CWG 2307 C++98 un parámetro de plantilla constante entre paréntesis usado como
argumento de plantilla era equivalente a ese parámetro de plantilla
ya no equivalente
CWG 2457 C++11 un tipo de función con paquete de parámetros de función
no era un tipo dependiente
lo es
CWG 2785 C++20 las expresiones requires podrían ser dependientes de tipo nunca son
dependientes de tipo
CWG 2905 C++11 una expresión noexcept solo era dependiente de valor
si su operando es dependiente de valor
es dependiente de valor
si su operando involucra
un parámetro de plantilla
CWG 2936 C++98 los nombres de clases locales de funciones
plantilladas no eran parte de la instanciación actual
lo son