Namespaces
Variants

Unqualified name lookup

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

Pour un nom non qualifié , c'est-à-dire un nom qui n'apparaît pas à droite d'un opérateur de résolution de portée :: , la recherche de nom examine les portées comme décrit ci-dessous, jusqu'à ce qu'elle trouve au moins une déclaration de n'importe quel type, moment auquel la recherche s'arrête et aucune portée supplémentaire n'est examinée. (Note : la recherche dans certains contextes ignore certaines déclarations, par exemple, la recherche du nom utilisé à gauche de :: ignore les déclarations de fonctions, de variables et d'énumérateurs, la recherche d'un nom utilisé comme spécificateur de classe de base ignore toutes les déclarations de non-type).

Pour les besoins de la recherche de nom non qualifiée, toutes les déclarations d'un espace de noms désigné par une directive using apparaissent comme si elles étaient déclarées dans l'espace de noms englobant le plus proche qui contient, directement ou indirectement, à la fois la directive using et l'espace de noms désigné.

La recherche de nom non qualifié du nom utilisé à gauche de l'opérateur d'appel de fonction (et, de manière équivalente, l'opérateur dans une expression) est décrite dans argument-dependent lookup .

Table des matières

Portée de fichier

Pour un nom utilisé dans la portée globale (espace de noms de niveau supérieur), en dehors de toute fonction, classe ou espace de noms déclaré par l'utilisateur, la portée globale avant l'utilisation du nom est examinée :

int n = 1;     // déclaration de n
int x = n + 1; // OK : la recherche trouve ::n
int z = y - 1; // Erreur : la recherche échoue
int y = 2;     // déclaration de y

Portée de l'espace de noms

Pour un nom utilisé dans un espace de noms déclaré par l'utilisateur en dehors de toute fonction ou classe, cet espace de noms est recherché avant l'utilisation du nom, puis l'espace de noms englobant cet espace de noms avant la déclaration de cet espace de noms, etc. jusqu'à ce que l'espace de noms global soit atteint.

int n = 1; // déclaration
namespace N
{
    int m = 2;
    namespace Y
    {
        int x = n; // OK, la recherche trouve ::n
        int y = m; // OK, la recherche trouve ::N::m
        int z = k; // Erreur : la recherche échoue
    }
    int k = 3;
}

Définition en dehors de son espace de noms

Pour un nom utilisé dans la définition d'une variable membre d'espace de noms en dehors de l'espace de noms, la recherche procède de la même manière que pour un nom utilisé à l'intérieur de l'espace de noms :

namespace X
{
    extern int x; // déclaration, pas définition
    int n = 1;    // trouvé en 1er
}
int n = 2;        // trouvé en 2ème
int X::x = n;     // trouve X::n, initialise X::x à 1

Définition de fonction non membre

Pour un nom utilisé dans la définition d'une fonction, soit dans son corps ou comme partie d'un argument par défaut, où la fonction est un membre d'un espace de noms global ou déclaré par l'utilisateur, le bloc dans lequel le nom est utilisé est recherché avant l'utilisation du nom, puis le bloc englobant est recherché avant le début de ce bloc, etc, jusqu'à atteindre le bloc qui est le corps de la fonction. Ensuite, l'espace de noms dans lequel la fonction est déclarée est recherché jusqu'à la définition (pas nécessairement la déclaration) de la fonction qui utilise le nom, puis les espaces de noms englobants, etc.

namespace A
{
    namespace N
    {
        void f();
        int i = 3; // trouvé en 3ème position (si le 2ème n'est pas présent)
    {
    int i = 4;     // trouvé en 4ème position (si le 3ème n'est pas présent)
{
int i = 5;         // trouvé en 5ème position (si le 4ème n'est pas présent)
void A::N::f()
{
    int i = 2;     // trouvé en 2ème position (si le 1er n'est pas présent)
    while (true)
    {
       int i = 1;  // trouvé en 1ère position : la recherche est terminée
       std::cout << i;
    {
{
// int i;          // non trouvé
namespace A
{
    namespace N
    {
        // int i;  // non trouvé
    {
{

Définition de classe

Pour un nom utilisé n'importe où dans la définition de classe (y compris les spécificateurs de classe de base et les définitions de classes imbriquées), sauf à l'intérieur du corps d'une fonction membre, d'un argument par défaut d'une fonction membre, d'une spécification d'exception d'une fonction membre, ou d'un initialiseur de membre par défaut, où le membre peut appartenir à une classe imbriquée dont la définition se trouve dans le corps de la classe englobante, les portées suivantes sont recherchées :

a) le corps de la classe dans lequel le nom est utilisé jusqu'au point d'utilisation,
b) le corps entier de sa(es) classe(s) de base, en récursant dans leurs bases lorsqu'aucune déclaration n'est trouvée,
c) si cette classe est imbriquée , le corps de la classe englobante jusqu'à la définition de cette classe et l'ensemble du corps de(s) classe(s) de base de la classe englobante,
d) si cette classe est locale , ou imbriquée dans une classe locale, la portée de bloc dans laquelle la classe est définie jusqu'au point de définition,
e) si cette classe est membre d'un espace de noms, ou est imbriquée dans une classe qui est membre d'un espace de noms, ou est une classe locale dans une fonction qui est membre d'un espace de noms, la portée de l'espace de noms est recherchée jusqu'à la définition de la classe, de la classe englobante ou de la fonction ; la recherche continue dans les espaces de noms englobants jusqu'à la portée globale.

Pour une déclaration friend , la recherche pour déterminer si elle fait référence à une entité précédemment déclarée se déroule comme ci-dessus, sauf qu'elle s'arrête après l'espace de noms englobant le plus interne.

namespace M
{
    // const int i = 1; // jamais trouvé
    class B
    {
        // static const int i = 3;     // trouvé en 3ème (mais ne passera pas la vérification d'accès)
    };
}
// const int i = 5;                    // trouvé en 5ème
namespace N
{
    // const int i = 4;                // trouvé en 4ème
    class Y : public M::B
    {
        // static const int i = 2;     // trouvé en 2ème
        class X
        {
            // static const int i = 1; // trouvé en 1er
            int a[i]; // utilisation de i
            // static const int i = 1; // jamais trouvé
        };
        // static const int i = 2;     // jamais trouvé
    };
    // const int i = 4;                // jamais trouvé
}
// const int i = 5;                    // jamais trouvé

Nom de classe injecté

Pour le nom d'une classe ou d'un modèle de classe utilisé dans la définition de cette classe ou modèle ou dérivé de celle-ci, la recherche de nom non qualifié trouve la classe qui est définie comme si le nom était introduit par une déclaration de membre (avec un accès membre public). Pour plus de détails, voir injected-class-name .

Définition de fonction membre

Pour un nom utilisé dans le corps d'une fonction membre, un argument par défaut d'une fonction membre, une spécification d'exception d'une fonction membre, ou un initialiseur de membre par défaut, les portées recherchées sont les mêmes que dans la définition de classe , sauf que la portée entière de la classe est considérée, et non seulement la partie précédant la déclaration qui utilise le nom. Pour les classes imbriquées, le corps entier de la classe englobante est recherché.

class B
{
    // int i;         // trouvé en 3ème position
};
namespace M
{
    // int i;         // trouvé en 5ème position
    namespace N
    {
        // int i;     // trouvé en 4ème position
        class X : public B
        {
            // int i; // trouvé en 2ème position
            void f();
            // int i; // également trouvé en 2ème position
        };
        // int i;     // trouvé en 4ème position
    }
}
// int i;             // trouvé en 6ème position
void M::N::X::f()
{
    // int i;         // trouvé en 1ère position
    i = 16;
    // int i;         // jamais trouvé
}
namespace M
{
    namespace N
    {
        // int i;     // jamais trouvé
    }
}
Dans tous les cas, lors de l'examen des bases dont la classe est dérivée, les règles suivantes, parfois appelées dominance dans l'héritage virtuel , sont appliquées :
Un nom de membre trouvé dans un sous-objet B masque le même nom de membre dans tout sous-objet A si A est un sous-objet de classe de base de B . (Notez que cela ne masque pas le nom dans les copies supplémentaires non virtuelles de A sur le réseau d'héritage qui ne sont pas des bases de B : cette règle n'a d'effet qu'en cas d'héritage virtuel.) Les noms introduits par les using-declarations sont traités comme des noms dans la classe contenant la déclaration. Après examen de chaque base, l'ensemble résultant doit inclure soit des déclarations d'un membre statique provenant de sous-objets du même type, soit des déclarations de membres non statiques provenant du même sous-objet. (jusqu'à C++11)
Un ensemble de recherche est construit, qui consiste en les déclarations et les sous-objets dans lesquels ces déclarations ont été trouvées. Les using-declarations sont remplacées par les membres qu'elles représentent et les déclarations de type, y compris les noms de classe injectés, sont remplacées par les types qu'elles représentent. Si C est la classe dans la portée de laquelle le nom a été utilisé, C est examinée en premier. Si la liste des déclarations dans C est vide, un ensemble de recherche est construit pour chacune de ses bases directes Bi (en appliquant récursivement ces règles si Bi a ses propres bases). Une fois construits, les ensembles de recherche pour les bases directes sont fusionnés dans l'ensemble de recherche de C comme suit :
  • si l'ensemble des déclarations dans Bi est vide, il est ignoré,
  • si l'ensemble de recherche de C construit jusqu'à présent est vide, il est remplacé par l'ensemble de recherche de Bi ,
  • si chaque sous-objet dans l'ensemble de recherche de Bi est une base d'au moins un des sous-objets déjà ajoutés à l'ensemble de recherche de C , l'ensemble de recherche de Bi est ignoré,
  • si chaque sous-objet déjà ajouté à l'ensemble de recherche de C est une base d'au moins un sous-objet dans l'ensemble de recherche de Bi , alors l'ensemble de recherche de C est ignoré et remplacé par l'ensemble de recherche de Bi ,
  • sinon, si les ensembles de déclarations dans Bi et dans C sont différents, le résultat est une fusion ambiguë : le nouvel ensemble de recherche de C a une déclaration invalide et une union des sous-objets précédemment fusionnés dans C et introduits depuis Bi . Cet ensemble de recherche invalide peut ne pas être une erreur s'il est ignoré ultérieurement,
  • sinon, le nouvel ensemble de recherche de C a les ensembles de déclarations partagés et l'union des sous-objets précédemment fusionnés dans C et introduits depuis Bi .
(depuis C++11)
struct X { void f(); };
struct B1: virtual X { void f(); };
struct B2: virtual X {};
struct D : B1, B2
{
    void foo()
    {
        X::f(); // OK, appelle X::f (recherche qualifiée)
        f(); // OK, appelle B1::f (recherche non qualifiée)
    }
};
// Règles C++98 : B1::f masque X::f, donc même si X::f peut être atteint depuis D
// via B2, il n'est pas trouvé par la recherche de nom depuis D.
// Règles C++11 : l'ensemble de recherche pour f dans D ne trouve rien, passe aux bases
//  l'ensemble de recherche pour f dans B1 trouve B1::f, et est complété
// la fusion remplace l'ensemble vide, maintenant l'ensemble de recherche pour f dans C a B1::f dans B1
//  l'ensemble de recherche pour f dans B2 ne trouve rien, passe aux bases
//    la recherche pour f dans X trouve X::f
//  la fusion remplace l'ensemble vide, maintenant l'ensemble de recherche pour f dans B2 a X::f dans X
// la fusion dans C trouve que chaque sous-objet (X) dans l'ensemble de recherche dans B2 est une base
// de chaque sous-objet (B1) déjà fusionné, donc l'ensemble B2 est ignoré
// C reste avec seulement B1::f trouvé dans B1
// (si struct D : B2, B1 était utilisé, alors la dernière fusion *remplacerait* le
//  X::f dans X jusqu'à présent fusionné dans C car chaque sous-objet déjà ajouté à C (c'est-à-dire X)
//  serait une base d'au moins un sous-objet dans le nouvel ensemble (B1), le résultat
//  final serait le même : l'ensemble de recherche dans C contient seulement B1::f trouvé dans B1)
La recherche de nom non qualifié qui trouve les membres statiques de B , les types imbriqués de B , et les énumérateurs déclarés dans B est non ambiguë même s'il existe plusieurs sous-objets de base non virtuels de type B dans l'arbre d'héritage de la classe examinée :
struct V { int v; };
struct B
{
    int a;
    static int s;
    enum { e };
};
struct B1 : B, virtual V {};
struct B2 : B, virtual V {};
struct D : B1, B2 {};
void f(D& pd)
{
    ++pd.v;       // OK : un seul v car un seul sous-objet de base virtuelle
    ++pd.s;       // OK : un seul B::s statique, même s'il est trouvé dans B1 et B2
    int i = pd.e; // OK : un seul énumérateur B::e, même s'il est trouvé dans B1 et B2
    ++pd.a;       // erreur, ambigu : B::a dans B1 et B::a dans B2
}

Définition de fonction amie

Pour un nom utilisé dans une définition de fonction friend à l'intérieur du corps de la classe qui accorde l'amitié, la recherche de nom non qualifié procède de la même manière que pour une fonction membre. Pour un nom utilisé dans une fonction friend qui est définie à l'extérieur du corps d'une classe, la recherche de nom non qualifié procède de la même manière que pour une fonction dans un espace de noms.

int i = 3;                     // trouvé 3ème pour f1, trouvé 2ème pour f2
struct X
{
    static const int i = 2;    // trouvé 2ème pour f1, jamais trouvé pour f2
    friend void f1(int x)
    {
        // int i;              // trouvé 1er
        i = x;                 // trouve et modifie X::i
    }
    friend int f2();
    // static const int i = 2; // trouvé 2ème pour f1 n'importe où dans la portée de la classe
};
void f2(int x)
{
    // int i;                  // trouvé 1er
    i = x;                     // trouve et modifie ::i
}

Déclaration de fonction amie

Pour un nom utilisé dans le déclarateur d'une fonction friend qui déclare une fonction membre d'une autre classe comme amie, si le nom ne fait partie d'aucun argument de template dans l'identifiant du déclarateur , la recherche non qualifiée examine d'abord la portée entière de la classe de la fonction membre. Si elle n'est pas trouvée dans cette portée (ou si le nom fait partie d'un argument de template dans l'identifiant du déclarateur), la recherche continue comme pour une fonction membre de la classe qui accorde l'amitié.

template<class T>
struct S;
// la classe dont les fonctions membres sont déclarées amies
struct A
{ 
    typedef int AT;
    void f1(AT);
    void f2(float);
    template<class T>
    void f3();
    void f4(S<AT>);
};
// la classe qui accorde l'amitié pour f1, f2 et f3
struct B
{
    typedef char AT;
    typedef float BT;
    friend void A::f1(AT);    // la recherche de AT trouve A::AT (AT trouvé dans A)
    friend void A::f2(BT);    // la recherche de BT trouve B::BT (BT non trouvé dans A)
    friend void A::f3<AT>();  // la recherche de AT trouve B::AT (aucune recherche dans A, car
                              //     AT est dans l'identifiant du déclarateur A::f3<AT>)
};
// la classe template qui accorde l'amitié pour f4
template<class AT>
struct C
{
    friend void A::f4(S<AT>); // la recherche de AT trouve A::AT
                              // (AT n'est pas dans l'identifiant du déclarateur A::f4)
};

Argument par défaut

Pour un nom utilisé dans un argument par défaut dans une déclaration de fonction, ou un nom utilisé dans la partie expression d'un initialiseur de membre d'un constructeur, les noms des paramètres de fonction sont trouvés en premier, avant que les portées de bloc englobant, de classe ou d'espace de noms ne soient examinées :

class X
{
    int a, b, i, j;
public:
    const int& r;
    X(int i): r(a),      // initialise X::r pour référencer X::a
              b(i),      // initialise X::b à la valeur du paramètre i
              i(i),      // initialise X::i à la valeur du paramètre i
              j(this->i) // initialise X::j à la valeur de X::i
    {}
};
int a;
int f(int a, int b = a); // erreur : la recherche de a trouve le paramètre a, pas ::a
                         // et les paramètres ne sont pas autorisés comme arguments par défaut

Définition de membre de données statique

Pour un nom utilisé dans la définition d'un static data member , la recherche s'effectue de la même manière que pour un nom utilisé dans la définition d'une fonction membre.

struct X
{
    static int x;
    static const int n = 1; // trouvé en premier
};
int n = 2;                  // trouvé en second
int X::x = n;               // trouve X::n, définit X::x à 1, pas 2

Déclaration d'énumérateur

Pour un nom utilisé dans la partie initialiseur d'une déclaration d'énumérateur , les énumérateurs précédemment déclarés dans la même énumération sont trouvés en premier, avant que la recherche de nom non qualifié ne procède à l'examen de la portée de bloc, de classe ou d'espace de noms englobante.

const int RED = 7;
enum class color
{
    RED,
    GREEN = RED + 2, // RED trouve color::RED, pas ::RED, donc GREEN = 2
    BLUE = ::RED + 4 // recherche qualifiée trouve ::RED, BLUE = 11
};

Gestionnaire d'une fonction try block

Pour un nom utilisé dans le gestionnaire d'un bloc try de fonction , la recherche procède comme pour un nom utilisé au tout début du bloc le plus externe du corps de la fonction (en particulier, les paramètres de fonction sont visibles, mais les noms déclarés dans ce bloc le plus externe ne le sont pas)

int n = 3;          // trouvé 3ème
int f(int n = 2)    // trouvé 2ème
try
{
    int n = -1;     // jamais trouvé
}
catch(...)
{
    // int n = 1;   // trouvé 1er
    assert(n == 2); // recherche de n trouve le paramètre de fonction f
    throw;
}

Opérateur surchargé

Pour un opérateur utilisé dans une expression (par ex., operator + utilisé dans a + b ), les règles de recherche sont légèrement différentes de celles pour un opérateur utilisé dans une expression d'appel de fonction explicite comme operator + ( a, b ) : lors de l'analyse d'une expression, deux recherches distinctes sont effectuées : pour les surcharges d'opérateur non membres et pour les surcharges d'opérateur membres (pour les opérateurs où les deux formes sont autorisées). Ces ensembles sont ensuite fusionnés avec les surcharges d'opérateur intégrées sur un pied d'égalité, comme décrit dans la résolution de surcharge . Si la syntaxe d'appel de fonction explicite est utilisée, une recherche de nom non qualifiée régulière est effectuée :

struct A {};
void operator+(A, A);  // opérateur non-membre operator+ défini par l'utilisateur
struct B
{
    void operator+(B); // opérateur membre operator+ défini par l'utilisateur
    void f();
};
A a;
void B::f() // définition d'une fonction membre de B
{
    operator+(a, a); // erreur : la recherche de nom normale depuis une fonction membre
                     // trouve la déclaration de operator+ dans la portée de B
                     // et s'arrête là, n'atteignant jamais la portée globale
    a + a; // OK : la recherche membre trouve B::operator+, la recherche non-membre
           // trouve ::operator+(A, A), la résolution de surcharge sélectionne ::operator+(A, A)
}

Définition de modèle

Pour un nom non dépendant utilisé dans une définition de template, la recherche de nom non qualifiée a lieu lors de l'examen de la définition du template. La liaison aux déclarations effectuées à ce moment n'est pas affectée par les déclarations visibles au point d'instanciation. Pour un nom dépendant utilisé dans une définition de template, la recherche est reportée jusqu'à ce que les arguments du template soient connus, moment auquel ADL examine les déclarations de fonctions avec liaison externe (jusqu'à C++11) qui sont visibles depuis le contexte de définition du template ainsi que dans le contexte d'instanciation du template, tandis que la recherche non-ADL n'examine que les déclarations de fonctions avec liaison externe (jusqu'à C++11) qui sont visibles depuis le contexte de définition du template (en d'autres termes, l'ajout d'une nouvelle déclaration de fonction après la définition du template ne la rend pas visible sauf via ADL). Le comportement est indéfini s'il existe une meilleure correspondance avec liaison externe dans les espaces de noms examinés par la recherche ADL, déclarée dans une autre unité de traduction, ou si la recherche aurait été ambiguë si ces unités de traduction avaient été examinées. Dans tous les cas, si une classe de base dépend d'un paramètre de template, sa portée n'est pas examinée par la recherche de nom non qualifiée (ni au point de définition ni au point d'instanciation).

void f(char); // première déclaration de f
template<class T> 
void g(T t)
{
    f(1);    // nom non dépendant : la recherche trouve ::f(char) et le lie maintenant
    f(T(1)); // nom dépendant : recherche reportée
    f(t);    // nom dépendant : recherche reportée
//  dd++;    // nom non dépendant : la recherche ne trouve aucune déclaration
}
enum E { e };
void f(E);   // deuxième déclaration de f
void f(int); // troisième déclaration de f
double dd;
void h()
{
    g(e);  // instancie g<E>, à ce moment
           // les deuxième et troisième utilisations du nom 'f'
           // sont recherchées et trouvent ::f(char) (par recherche) et ::f(E) (par ADL)
           // puis la résolution de surcharge choisit ::f(E).
           // Ceci appelle f(char), puis f(E) deux fois
    g(32); // instancie g<int>, à ce moment
           // les deuxième et troisième utilisations du nom 'f'
           // sont recherchées et trouvent ::f(char) uniquement
           // puis la résolution de surcharge choisit ::f(char)
           // Ceci appelle f(char) trois fois
}
typedef double A;
template<class T>
class B
{
    typedef int A;
};
template<class T>
struct X : B<T>
{
    A a; // la recherche de A trouve ::A (double), pas B<T>::A
};

Note : voir les règles de recherche des noms dépendants pour le raisonnement et les implications de cette règle.

Nom du modèle

Membre d'un modèle de classe en dehors du modèle

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 publié Comportement corrigé
CWG 490 C++98 tout nom dans un argument de template dans une déclaration
de fonction membre friend n'était pas recherché
dans la portée de la classe de la fonction membre
exclut uniquement les noms
dans les arguments de template
de l'identifiant du déclarateur
CWG 514 C++98 tout nom non qualifié utilisé dans la portée
d'un namespace était d'abord recherché dans cette portée
les noms non qualifiés utilisés pour définir un
membre variable de namespace en dehors de ce
namespace sont d'abord recherchés dans ce namespace

Références

  • Norme C++23 (ISO/CEI 14882:2024) :
  • 6.5 Recherche de nom [basic.lookup] (p: 44-45)
  • 6.5.2 Recherche de nom de membre [class.member.lookup] (p: 45-47)
  • 13.8 Résolution de nom [temp.res] (p: 399-403)
  • Norme C++20 (ISO/CEI 14882:2020) :
  • 6.5 Recherche de nom [basic.lookup] (p: 38-50)
  • 11.8 Recherche de nom de membre [class.member.lookup] (p: 283-285)
  • 13.8 Résolution de nom [temp.res] (p: 385-400)
  • Norme C++17 (ISO/CEI 14882:2017) :
  • 6.4 Recherche de noms [basic.lookup] (p: 50-63)
  • 13.2 Recherche de noms de membres [class.member.lookup] (p: 259-262)
  • 17.6 Résolution de noms [temp.res] (p: 375-378)
  • Norme C++14 (ISO/CEI 14882:2014) :
  • 3.4 Recherche de nom [basic.lookup] (p: 42-56)
  • 10.2 Recherche de nom de membre [class.member.lookup] (p: 233-236)
  • 14.6 Résolution de nom [temp.res] (p: 346-359)
  • Norme C++11 (ISO/IEC 14882:2011) :
  • 3.4 Recherche de nom [basic.lookup]
  • 10.2 Recherche de nom de membre [class.member.lookup]
  • 14.6 Résolution de nom [temp.res]
  • Norme C++98 (ISO/CEI 14882:1998) :
  • 3.4 Recherche de nom [basic.lookup]
  • 10.2 Recherche de nom de membre [class.member.lookup]
  • 14.6 Résolution de nom [temp.res]

Voir aussi