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

Dans la définition d'un template (à la fois class template et function template ), la signification de certaines constructions peut différer d'une instanciation à l'autre. En particulier, les types et les expressions peuvent dépendre des types des paramètres de template de type et des valeurs des paramètres de template constants.

template<typename T>
struct X : B<T> // « B<T> » dépend de T
{
    typename T::A* pa; // « T::A » dépend de T
                       // (voir ci-dessous pour la signification de cet usage de « typename »)
    void f(B<T>* pb)
    {
        static int i = B<T>::i; // « B<T>::i » dépend de T
        pb->j++; // « pb->j » dépend de T
    }
};

Nom lookup et liaison sont différents pour les noms dépendants et les noms non dépendants.

Table des matières

Règles de liaison

Les noms non dépendants sont recherchés et liés au point de définition du modèle. Cette liaison reste valable même si au point d'instanciation du modèle il existe une meilleure correspondance :

#include <iostream>
void g(double) { std::cout << "g(double)\n"; }
template<class T>
struct S
{
    void f() const
    {
        g(1); // "g" est un nom non-dépendant, lié maintenant
    }
};
void g(int) { std::cout << "g(int)\n"; }
int main()
{
    g(1);  // appelle g(int)
    S<int> s;
    s.f(); // appelle g(double)
}
Traduction effectuée en respectant : - Conservation de tous les tags HTML et attributs - Non-traduction du code C++ dans les balises `
`
- Préservation des termes techniques C++ (template, struct, etc.)
- Formatage original maintenu
- Style professionnel et précis

Si la signification d'un nom non dépendant change entre le contexte de définition et le point d'instanciation d'une spécialisation du modèle, le programme est mal formé, aucun diagnostic requis. Ceci est possible dans les situations suivantes :

  • un type utilisé dans un nom non dépendant est incomplet au point de définition mais complet au point d'instanciation
  • la recherche d'un nom dans la définition du modèle a trouvé une using-declaration , mais la recherche dans la portée correspondante lors de l'instanciation ne trouve aucune déclaration car la using-declaration était une expansion de pack et le pack correspondant est vide
(depuis C++17)
  • une instanciation utilise un argument par défaut ou un argument de template par défaut qui n'était pas défini au point de définition
  • une expression constante au point d'instanciation utilise la valeur d'un objet const de type intégral ou énumération non-scopée , la valeur d'un objet constexpr, la valeur d'une référence, ou la définition d'une fonction constexpr (depuis C++11) , et cet objet /référence/fonction (depuis C++11) n'était pas défini au point de définition
  • le template utilise une spécialisation de template de classe non-dépendante ou une spécialisation de template de variable (depuis C++14) au point d'instanciation, et ce template utilisé est soit instancié à partir d'une spécialisation partielle qui n'était pas définie au point de définition, soit nomme une spécialisation explicite qui n'était pas déclarée au point de définition

La liaison des noms dépendants est reportée jusqu'à ce que la recherche ait lieu.

Règles de recherche

La recherche d'un nom dépendant utilisé dans un template est reportée jusqu'à ce que les arguments du template soient connus, moment auquel

  • La recherche non-ADL examine les déclarations de fonctions avec liaison externe qui sont visibles depuis le contexte de définition du template
  • ADL examine les déclarations de fonctions avec liaison externe qui sont visibles depuis le contexte de définition du template ou le contexte d'instanciation du template

(en d'autres termes, l'ajout d'une nouvelle déclaration de fonction après la définition du modèle ne la rend pas visible, sauf via ADL).

L'objectif de cette règle est d'aider à se prémunir contre les violations de la ODR pour les instanciations de templates :

// une bibliothèque externe
namespace E
{
    template<typename T>
    void writeObject(const T& t)
    {
        std::cout << "Valeur = " << t << '\n';
    }
}
// unité de traduction 1 :
// Le programmeur 1 souhaite permettre à E::writeObject de fonctionner avec 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); // Erreur : ne trouvera pas P1::operator<<
    }
}
// unité de traduction 2 :
// Le programmeur 2 souhaite permettre à E::writeObject de fonctionner avec 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); // Erreur : ne trouvera pas P2::operator<<
    }
}

Dans l'exemple ci-dessus, si la recherche non-ADL pour operator<< était autorisée depuis le contexte d'instanciation, l'instanciation de E :: writeObject < vector < int >> aurait deux définitions différentes : une utilisant P1 :: operator << et une utilisant P2 :: operator << . Une telle violation ODR pourrait ne pas être détectée par l'éditeur de liens, conduisant à l'utilisation de l'une ou l'autre dans les deux instances.

Pour qu'ADL examine un espace de noms défini par l'utilisateur, soit std::vector doit être remplacé par une classe définie par l'utilisateur, soit son type d'élément doit être une classe définie par l'utilisateur :

namespace P1
{
    // si C est une classe définie dans l'espace de noms 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 : instancie writeObject(std::vector<P1::C>)
                           //     qui trouve P1::operator<< via ADL
    }
}

Note : cette règle rend peu pratique la surcharge des opérateurs pour les types de la bibliothèque standard :

#include <iostream>
#include <iterator>
#include <utility>
#include <vector>
// Mauvaise idée : opérateur dans l'espace de noms global, mais ses arguments sont dans 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 recherche ordinaire trouve ::operator<<
    std::copy(v.begin(), v.end(),
              std::ostream_iterator<elem_t>(std::cout, " "));
    // Erreur : la recherche ordinaire depuis le point de définition de
    // std::ostream_iterator et ADL ne considéreront que l'espace de noms std,
    // et trouveront de nombreuses surcharges de std::operator<<, donc la recherche sera effectuée.
    // La résolution de surcharge échouera alors à trouver operator<< pour elem_t
    // dans l'ensemble trouvé par la recherche.
}

Note: une recherche limitée (mais non contraignante) des noms dépendants a également lieu au moment de la définition du modèle, si nécessaire pour les distinguer des noms non dépendants et aussi pour déterminer s'ils sont membres de l'instanciation courante ou membres d'une spécialisation inconnue. Les informations obtenues par cette recherche peuvent être utilisées pour détecter des erreurs, voir ci-dessous.

Types dépendants

Les types suivants sont des types dépendants :

  • paramètre de template
  • un membre d'une spécialisation inconnue (voir ci-dessous)
  • une classe/enum imbriquée qui est un membre dépendant d'une spécialisation inconnue (voir ci-dessous)
  • une version qualifiée cv d'un type dépendant
  • un type composé construit à partir d'un type dépendant
  • un type tableau dont le type d'élément est dépendant ou dont la limite (le cas échéant) est dépendante de la valeur
(since C++11)
  • un type de fonction dont la spécification d'exception dépend de la valeur
  • un template-id où soit
  • le nom du template est un paramètre de template, ou
  • l'un des arguments du template est dépendant du type, ou dépendant de la valeur , ou est une expansion de pack (depuis C++11) (même si le template-id est utilisé sans sa liste d'arguments, comme injected-class-name )
  • le résultat de decltype appliqué à une expression dépendante du type

Le résultat de decltype appliqué à une expression dépendante du type est un type dépendant unique. Deux tels résultats se réfèrent au même type uniquement si leurs expressions sont équivalentes .

(depuis C++11)

Le spécificateur d'indexation de paquet appliqué à une expression constante dépendante du type est un type dépendant unique. Deux de ces spécificateurs d'indexation de paquet font référence au même type uniquement si leurs expressions constantes sont équivalentes. Sinon, deux de ces spécificateurs d'indexation de paquet font référence au même type uniquement si leurs indices ont la même valeur.

(depuis C++26)

Note : un typedef membre d'une instanciation courante est uniquement dépendant lorsque le type auquel il se réfère l'est.

Expressions dépendantes du type

Les expressions suivantes sont type-dependent :

  • une expression dont toute sous-expression est une expression dépendante du type
  • this , si la classe est un type dépendant.
  • une expression d'identifiant qui n'est pas un concept-id et (depuis C++20)
  • contient un identifiant pour lequel la recherche de nom trouve au moins une déclaration dépendante
  • contient un template-id dépendant
  • contient l'identifiant spécial __func__ (si une fonction englobante est un modèle, un membre non-modèle d'un modèle de classe , ou une lambda générique (depuis C++14) )
(depuis C++11)
  • contient le nom de la conversion function vers un type dépendant
  • contient un spécificateur de nom imbriqué ou un qualified-id qui est un membre de spécialisation inconnue
  • nomme un membre dépendant de l'instanciation courante qui est une donnée membre statique de type "tableau de taille inconnue"
  • contient un identifiant pour lequel la recherche de nom trouve une ou plusieurs déclarations de fonctions membres de l'instanciation courante déclarées avec déduction de type de retour
(depuis C++14)
  • contient un identifiant pour lequel la recherche de nom trouve une déclaration de liaison structurée dont l'initialiseur est dépendant du type
  • contient un identifiant pour lequel la recherche de nom trouve un paramètre de template constant dont le type contient le placeholder auto
  • contient un identifiant pour lequel la recherche de nom trouve une variable déclarée avec un type qui contient un type placeholder (par exemple, auto membre de données statique), où l'initialiseur est dépendant du type,
(depuis C++17)
  • contient un identifiant pour lequel la recherche de nom trouve un pack
(depuis C++26)
  • toute expression de cast vers un type dépendant
  • new expression qui crée un objet d'un type dépendant
  • expression d'accès membre qui se réfère à un membre de l'instanciation courante dont le type est dépendant
  • expression d'accès membre qui se réfère à un membre de spécialisation inconnue
(depuis C++17)
(depuis C++26)

Les expressions suivantes ne sont jamais dépendantes du type car les types de ces expressions ne peuvent pas l'être :

(depuis C++11)
(depuis C++20)

Expressions dépendantes de la valeur

Les expressions suivantes sont value-dependent :

  • C'est un concept-id et l'un de ses arguments est dépendant.
(depuis C++20)
  • Il est dépendant du type.
  • C'est un nom d'un paramètre de template constant.
  • Il nomme un membre de données statique qui est un membre dépendant de l'instanciation courante et n'est pas initialisé.
  • Il nomme une fonction membre statique qui est un membre dépendant de l'instanciation courante.
  • C'est une constante avec un type entier ou énumération (jusqu'en C++11) littéral (depuis C++11) , initialisée à partir d'une expression dépendante de la valeur.
  • les expressions suivantes où l'opérande est une expression dépendante du type :
**Note:** Aucune traduction n'a été effectuée car : - Le texte se trouve à l'intérieur de balises ` ` - Il s'agit de termes spécifiques au C++ (`sizeof`, `typeid`) - Les balises HTML et leurs attributs ont été préservés - La mise en forme originale a été maintenue
(depuis C++11)
  • les expressions suivantes où l'opérande est un type-id dépendant :
*Note: Le code HTML fourni ne contient aucun texte à traduire en dehors des balises ` `, qui contiennent des termes spécifiques au C++ (`sizeof` et `typeid`) et ne doivent donc pas être traduits selon vos instructions.*
  • les expressions suivantes où le type cible est dépendant ou l'opérande est une expression dépendante du type :
  • cast de style fonction expression où le type cible est dépendant ou une expression dépendante de la valeur est entourée de parenthèses ou d'accolades (depuis C++11)
(depuis C++11)
(depuis C++17)
  • expression d'adresse où l'argument est un identifiant qualifié qui nomme un membre dépendant de l'instanciation courante
  • expression d'adresse où l'argument est toute expression qui, évaluée comme une expression constante de base, se réfère à une entité template qui est un objet avec une durée de stockage statique ou thread (depuis C++11) ou une fonction membre.

Noms dépendants

Instanciation courante

Dans une définition de modèle de classe (y compris ses fonctions membres et classes imbriquées), certains noms peuvent être déduits comme se référant à l' instanciation courante . Cela permet de détecter certaines erreurs au point de définition plutôt qu'à l'instanciation, et supprime l'exigence des désambiguïsateurs typename et template pour les noms dépendants, voir ci-dessous.

Seuls les noms suivants peuvent faire référence à l'instanciation courante :

  • dans la définition d'un modèle de classe, d'une classe imbriquée d'un modèle de classe, d'un membre d'un modèle de classe, ou d'un membre d'une classe imbriquée d'un modèle de classe :
    • le nom de classe injecté du modèle de classe ou de la classe imbriquée
  • dans la définition d'un modèle de classe primaire ou d'un membre d'un modèle de classe primaire :
    • le nom du modèle de classe suivi d'une liste d'arguments de modèle (ou d'une spécialisation de modèle d'alias équivalente) pour le modèle primaire où chaque argument est équivalent (défini ci-dessous) à son paramètre correspondant.
  • dans la définition d'une classe imbriquée d'un modèle de classe :
    • le nom de la classe imbriquée utilisé comme membre de l'instanciation courante
  • dans la définition d'une spécialisation partielle de modèle de classe ou d'un membre d'une spécialisation partielle de modèle de classe :
    • le nom du modèle de classe suivi d'une liste d'arguments de modèle pour la spécialisation partielle, où chaque argument est équivalent à son paramètre correspondant
  • dans la définition d'une fonction template :

Un argument de template est équivalent à un paramètre de template si

  • pour un paramètre de type , l'argument de template désigne le même type que le paramètre de template.
  • pour un paramètre constant , l'argument de template est un identifiant qui nomme une variable qui est équivalente au paramètre de template. Une variable est équivalente à un paramètre de template si
  • il a le même type que le paramètre de modèle (en ignorant les qualifications cv) et
  • son initialiseur consiste en un seul identifiant qui nomme le paramètre de modèle ou, récursivement, une telle variable.
template<class T>
class A
{
    A* p1;      // A est l'instanciation courante
    A<T>* p2;   // A<T> est l'instanciation courante
    ::A<T>* p4; // ::A<T> est l'instanciation courante
    A<T*> p3;   // A<T*> n'est pas l'instanciation courante
    class B
    {
        B* p1;                 // B est l'instanciation courante
        A<T>::B* p2;           // A<T>::B est l'instanciation courante
        typename A<T*>::B* p3; // A<T*>::B n'est pas l'instanciation courante
    };
};
template<class T>
class A<T*>
{
    A<T*>* p1; // A<T*> est l'instanciation courante
    A<T>* p2;  // A<T> n'est pas l'instanciation courante
};
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> est l'instanciation courante :
                  //   my_I a le même type que I,
                  //   et il est initialisé uniquement avec I
    B<my_I2>* b2; // B<my_I2> n'est pas l'instanciation courante :
                  //   I + 0 n'est pas un identifiant unique
    B<my_I3>* b3; // B<my_I3> est l'instanciation courante :
                  //   my_I3 a le même type que I,
                  //   et il est initialisé uniquement avec my_I (qui équivaut à I)
    B<my_I4>* b4; // B<my_I4> n'est pas l'instanciation courante :
                  //   le type de my_I4 (long) n'est pas le même que le type de I (int)
    B<my_I5>* b5; // B<my_I5> n'est pas l'instanciation courante :
                  //   (I) n'est pas un identifiant unique
};

Notez qu'une classe de base peut être l'instanciation courante si une classe imbriquée dérive de son modèle de classe englobante. Les classes de base qui sont des types dépendants mais ne sont pas l'instanciation courante sont des classes de base dépendantes :

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
};
**Note:** Le code C++ n'a pas été traduit conformément aux instructions, car il se trouve dans des balises `
` et contient des termes spécifiques au C++. Seul le commentaire dans le code a été traduit :
- `// OK, A::M` → `// OK, A::M` (conservé en anglais car il s'agit d'un terme technique C++)
La structure HTML et le code C++ ont été préservés intacts.

Un nom est classé comme membre de l'instanciation courante s'il est

  • un nom non qualifié qui est trouvé par unqualified lookup dans l'instanciation courante ou dans sa base non dépendante.
  • qualified name , si le qualificateur (le nom à gauche de :: ) désigne l'instanciation courante et que la recherche trouve le nom dans l'instanciation courante ou dans sa base non dépendante
  • un nom utilisé dans une expression d'accès à un membre de classe ( y dans x. y ou xp - > y ), où l'expression d'objet ( x ou * xp ) est l'instanciation courante et que la recherche trouve le nom dans l'instanciation courante ou dans sa base non dépendante
template<class T>
class A
{
    static const int i = 5;
    int n1[i];       // i fait référence à un membre de l'instanciation courante
    int n2[A::i];    // A::i fait référence à un membre de l'instanciation courante
    int n3[A<T>::i]; // A<T>::i fait référence à un membre de l'instanciation courante
    int f();
};
template<class T>
int A<T>::f()
{
    return i; // i fait référence à un membre de l'instanciation courante
}

Les membres de l'instanciation courante peuvent être à la fois dépendants et non dépendants.

Si la recherche d'un membre de l'instanciation courante donne un résultat différent entre le point d'instanciation et le point de définition, la recherche est ambiguë. Notez cependant que lorsqu'un nom de membre est utilisé, il n'est pas automatiquement converti en une expression d'accès à un membre de classe, seules les expressions d'accès explicites aux membres indiquent les membres de l'instanciation courante :

struct A { int m; };
struct B { int m; };
template<typename T>
struct C : A, T
{
    int f() { return this->m; } // trouve A::m dans le contexte de définition du template
    int g() { return m; }       // trouve A::m dans le contexte de définition du template
};
template int C<B>::f(); // erreur : trouve à la fois A::m et B::m
template int C<B>::g(); // OK : la transformation en syntaxe d'accès aux membres de classe
                        // ne se produit pas dans le contexte de définition du template

Spécialisations inconnues

Dans une définition de template, certains noms sont déduits comme appartenant à une spécialisation inconnue , en particulier,

  • un nom qualifié , si un nom apparaissant à gauche de :: est un type dépendant qui n'est pas un membre de l'instanciation courante
  • un nom qualifié , dont le qualificateur est l'instanciation courante, et le nom n'est pas trouvé dans l'instanciation courante ou l'une de ses classes de base non dépendantes, et il existe une classe de base dépendante
  • un nom de membre dans une expression d'accès à un membre de classe (le y dans x. y ou xp - > y ), si le type de l'expression objet ( x ou * xp ) est un type dépendant et n'est pas l'instanciation courante
  • un nom de membre dans une expression d'accès à un membre de classe (le y dans x. y ou xp - > y ), si le type de l'expression objet ( x ou * xp ) est l'instanciation courante, et le nom n'est pas trouvé dans l'instanciation courante ou l'une de ses classes de base non dépendantes, et il existe une classe de base dépendante
template<typename T>
struct Base {};
template<typename T>
struct Derived : Base<T>
{
    void f()
    {
        // Derived<T> fait référence à l'instanciation courante
        // il n'y a pas de "unknown_type" dans l'instanciation courante
        // mais il y a une base dépendante (Base<T>)
        // Par conséquent, "unknown_type" est un membre de spécialisation inconnue
        typename Derived<T>::unknown_type z;
    }
};
template<>
struct Base<int> // cette spécialisation le fournit
{
    typedef int unknown_type;
};


Cette classification permet de détecter les erreurs suivantes au niveau de la définition du template (plutôt qu'à l'instanciation) :

  • Si une définition de modèle a un nom qualifié dans lequel le qualificatif fait référence à l'instanciation courante et que le nom n'est ni un membre de l'instanciation courante ni un membre de spécialisation inconnue, le programme est mal formé (aucun diagnostic requis) même si le modèle n'est jamais instancié.
template<class T>
class A
{
    typedef int type;
    void f()
    {
        A<T>::type i; // OK : « type » est un membre de l'instanciation courante
        typename A<T>::other j; // Erreur :
        // « other » n'est pas un membre de l'instanciation courante
        // et n'est pas un membre d'une spécialisation inconnue
        // car A<T> (qui désigne l'instanciation courante)
        // n'a pas de bases dépendantes où « other » pourrait être caché.
    }
};
  • Si une définition de modèle comporte une expression d'accès membre où l'expression objet est l'instanciation courante, mais où le nom n'est ni un membre de l'instanciation courante ni un membre d'une spécialisation inconnue, le programme est mal formé même si le modèle n'est jamais instancié.

Les membres de spécialisation inconnue sont toujours dépendants, et sont recherchés et liés au point d'instanciation comme tous les noms dépendants (voir ci-dessus)

Le typename désambiguïsateur pour les noms dépendants

Dans une déclaration ou une définition de template, y compris un alias template, un nom qui n'est pas un membre de l'instanciation courante et qui dépend d'un paramètre template n'est pas considéré comme un type à moins que le mot-clé typename soit utilisé ou qu'il n'ait déjà été établi comme nom de type, par exemple avec une déclaration typedef ou en étant utilisé pour nommer une classe de base.

#include <iostream>
#include <vector>
int p = 1;
template<typename T>
void foo(const std::vector<T> &v)
{
    // std::vector<T>::const_iterator est un nom dépendant,
    typename std::vector<T>::const_iterator it = v.begin();
    // sans "typename", l'expression suivante est analysée comme une multiplication
    // du membre de données dépendant du type "const_iterator"
    // et d'une variable "p". Comme il existe un "p" global visible
    // à ce point, cette définition de template compile.
    std::vector<T>::const_iterator* p;
    typedef typename std::vector<T>::const_iterator iter_t;
    iter_t * p2; // "iter_t" est un nom dépendant, mais il est connu comme étant un nom de type
}
template<typename T>
struct S
{
    typedef int value_t; // membre de l'instanciation courante
    void f()
    {
        S<T>::value_t n{}; // S<T> est dépendant, mais "typename" n'est pas nécessaire
        std::cout << n << '\n';
    }
};
int main()
{
    std::vector<int> v;
    foo(v); // l'instanciation du template échoue : il n'y a pas de variable membre
            // appelée "const_iterator" dans le type std::vector<int>
    S<int>().f();
}

Le mot-clé typename ne peut être utilisé de cette manière que devant des noms qualifiés (par exemple T :: x ), mais les noms ne sont pas nécessairement dépendants.

La recherche de nom qualifiée habituelle est utilisée pour l'identifiant préfixé par typename . Contrairement au cas du spécificateur de type élaboré , les règles de recherche ne changent pas malgré le qualificatif :

struct A // A possède une variable imbriquée X et un type imbriqué struct X
{
    struct X {};
    int X;
};
struct B
{
    struct X {}; // B possède un type imbriqué struct X
};
template<class T>
void f(T t)
{
    typename T::X x;
}
void foo()
{
    A a;
    B b;
    f(b); // OK : instancie f<B>, T::X fait référence à B::X
    f(a); // erreur : ne peut pas instancier f<A> :
          // car la recherche de nom qualifié pour A::X trouve le membre de données
}

Le mot-clé typename peut être utilisé même en dehors des templates.

#include <vector>
int main()
{
    // Les deux sont corrects (après résolution de CWG 382)
    typedef typename std::vector<int>::const_iterator iter_t;
    typename std::vector<int> v;
}

Dans certains contextes, seuls les noms de types peuvent valablement apparaître. Dans ces contextes, un nom qualifié dépendant est supposé désigner un type et aucun typename n'est requis :

  • Un nom qualifié qui apparaît dans un identifiant de type , où le plus petit identifiant de type englobant est :
(depuis C++20)

Le template désambiguïsateur pour les noms dépendants

De même, dans une définition de template, un nom dépendant qui n'est pas un membre de l'instanciation courante n'est pas considéré comme un nom de template sauf si le mot-clé de désambiguïsation template est utilisé ou sauf s'il était déjà établi comme nom de template :

template<typename T>
struct S
{
    template<typename U>
    void foo() {}
};
template<typename T>
void bar()
{
    S<T> s;
    s.foo<T>();          // erreur: < interprété comme opérateur inférieur à
    s.template foo<T>(); // OK
}

Le mot-clé template ne peut être utilisé de cette manière qu'après les opérateurs :: (résolution de portée), - > (accès membre via pointeur), et . (accès membre), les exemples suivants sont tous valides :

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

Comme c'est le cas avec typename , le préfixe template est autorisé même si le nom n'est pas dépendant ou si l'utilisation n'apparaît pas dans la portée d'un template.

Même si le nom à gauche de :: fait référence à un namespace, le désambiguïsateur de template est autorisé :

template<typename>
struct S {};
::template S<void> q; // autorisé, mais inutile

En raison des règles spéciales pour la recherche de nom non qualifié des noms de templates dans les expressions d'accès membre, lorsqu'un nom de template non dépendant apparaît dans une expression d'accès membre (après - > ou après . ), le disambiguateur est inutile s'il existe un template de classe ou d'alias (depuis C++11) portant le même nom trouvé par recherche ordinaire dans le contexte de l'expression. Cependant, si le template trouvé par la recherche dans le contexte de l'expression diffère de celui trouvé dans le contexte de la classe, le programme est mal formé (jusqu'à 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
}
(jusqu'à C++23)

Mots-clés

template , typename

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement tel que publié Comportement correct
CWG 206 C++98 il n'était pas spécifié à quel moment les contraintes sémantiques sont
appliquées lorsqu'un type utilisé dans un nom non dépendant est
incomplet au point où un template est défini mais est
complet au point où une instanciation est effectuée
le programme est mal formé
et aucun diagnostic n'est
requis dans ce cas
CWG 224 C++98 la définition des types dépendants était basée
sur la forme du nom plutôt que sur la recherche
définition refondue
CWG 382 C++98 le typename désambiguïsateur n'était autorisé que dans la portée du template également autorisé en dehors
des templates
CWG 468 C++98 le template désambiguïsateur n'était autorisé que dans la portée du template également autorisé en dehors
des templates
CWG 502 C++98 il n'était pas spécifié si les énumérations imbriquées sont dépendantes dépendantes comme les classes imbriquées
CWG 1047 C++98 typeid les expressions n'étaient jamais dépendantes en valeur dépendantes en valeur si l'
opérande est dépendant en type
CWG 1160 C++98 il n'était pas spécifié si un nom se réfère à l'instanciation courante
lorsqu'un template-id correspondant à un template primaire ou une
spécialisation partielle apparaît dans la définition d'un membre du template
spécifié
CWG 1413 C++98 membre de données statique non initialisé, fonction membre statique, et adresse
de membre d'un template de classe n'étaient pas listés comme dépendants en valeur
listés
CWG 1471 C++98 un type imbriqué d'une base non dépendante de
l'instanciation courante était dépendant
il n'est pas dépendant
CWG 1850 C++98 la liste des cas où la signification peut changer entre le
contexte de définition et le point d'instanciation était incomplète
rendue complète
CWG 1929 C++98 il n'était pas clair si le template désambiguïsateur peut
suivre un :: où le nom à sa gauche se réfère à un espace de noms
autorisé
CWG 2066 C++98 this n'était jamais dépendant en valeur il peut être
dépendant en valeur
CWG 2100 C++98 l'adresse d'un membre de données statique d'un template de classe
n'était pas listée comme dépendante en valeur
listée
CWG 2109 C++98 les expressions d'identificateur dépendantes en type pourraient ne pas être dépendantes en valeur elles sont toujours
dépendantes en valeur
CWG 2276 C++98 un type de fonction dont la spécification d'exception
est dépendante en valeur n'était pas un type dépendant
il l'est
CWG 2307 C++98 un paramètre de template constant entre parenthèses utilisé comme
argument de template était équivalent à ce paramètre de template
n'est plus équivalent
CWG 2457 C++11 un type de fonction avec un paquet de paramètres de fonction
n'était pas un type dépendant
il l'est
CWG 2785 C++20 requires les expressions pourraient être dépendantes en type elles ne sont jamais
dépendantes en type
CWG 2905 C++11 une noexcept expression n'était dépendante en valeur
que si son opérande est dépendant en valeur
elle est dépendante en valeur
si son opérande implique
un paramètre de template
CWG 2936 C++98 les noms des classes locales des fonctions
template n'étaient pas partie de l'instanciation courante
ils le sont