Qualified name lookup
Un nom
qualifié
est un nom qui apparaît du côté droit de l'opérateur de résolution de portée
::
(voir aussi
identifiants qualifiés
).
Un nom qualifié peut faire référence à un
- membre de classe (incluant les fonctions statiques et non statiques, les types, les templates, etc.),
- membre d'espace de noms (incluant un autre espace de noms),
- énumérateur.
S'il n'y a rien sur le côté gauche du
::
, la recherche considère uniquement les déclarations dans
la portée de l'espace de noms global
. Cela permet de faire référence à ces noms même s'ils sont masqués par une déclaration locale :
#include <iostream> namespace M { const char* fail = "fail\n"; } using M::fail; namespace N { const char* ok = "ok\n"; } using namespace N; int main() { struct std {}; std::cout << ::fail; // Erreur : la recherche non qualifiée pour 'std' trouve la structure ::std::cout << ::ok; // OK : ::std trouve l'espace de noms std }
Avant que la recherche de nom puisse être effectuée pour le nom sur le côté droit de
::
, la recherche doit être terminée pour le nom sur son côté gauche (sauf si une
expression decltype
est utilisée, ou s'il n'y a rien à gauche). Cette recherche, qui peut être qualifiée ou non qualifiée, selon qu'il y a un autre
::
à gauche de ce nom, ne considère que les namespaces, les types classe, les énumérations et les templates dont les spécialisations sont des types. Si le nom trouvé à gauche ne désigne pas un namespace ou une classe, une énumération, ou un type dépendant, le programme est mal formé :
struct A { static int n; }; int main() { int A; A::n = 42; // OK : la recherche non qualifiée de A à gauche de :: ignore la variable A b; // Erreur : la recherche non qualifiée de A trouve la variable A } template<int> struct B : A {}; namespace N { template<int> void B(); int f() { return B<0>::n; // Erreur : N::B<0> n'est pas un type } }
Lorsqu'un nom qualifié est utilisé comme déclarateur , alors la recherche non qualifiée des noms utilisés dans le même déclarateur qui suivent ce nom qualifié, mais pas les noms qui le précèdent, est effectuée dans la portée de la classe ou de l'espace de noms du membre :
class X {}; constexpr int number = 100; struct C { class X {}; static const int number = 50; static X arr[number]; }; X C::arr[number], brr[number]; // Erreur : la recherche de X trouve ::X, pas C::X C::X C::arr[number], brr[number]; // OK : la taille de arr est 50, la taille de brr est 100
Si
::
est suivi du caractère
~
qui est à son tour suivi d'un identifiant (c'est-à-dire qu'il spécifie un destructeur ou un pseudo-destructeur), cet identifiant est recherché dans la même portée que le nom sur le côté gauche de
::
struct C { typedef int I; }; typedef int I1, I2; extern int *p, *q; struct A { ~A(); }; typedef A AB; int main() { p->C::I::~I(); // Le nom I après ~ est recherché dans la même portée que I avant :: // (c'est-à-dire dans la portée de C, donc il trouve C::I) q->I1::~I2(); // Le nom I2 est recherché dans la même portée que I1 // (c'est-à-dire depuis la portée actuelle, donc il trouve ::I2) AB x; x.AB::~AB(); // Le nom AB après ~ est recherché dans la même portée que AB avant :: // (c'est-à-dire depuis la portée actuelle, donc il trouve ::AB) }
ÉnumérateursSi la recherche du nom du côté gauche aboutit à une énumération (qu'elle soit scopée ou non scopée), la recherche du côté droit doit aboutir à un énumérateur appartenant à cette énumération, sinon le programme est mal formé. |
(depuis C++11) |
Membres de classe
Si la recherche du nom du côté gauche aboutit à un nom de classe/structure ou union, le nom du côté droit de
::
est recherché dans la portée de cette classe (et peut donc trouver une déclaration d'un membre de cette classe ou de sa classe de base), avec les exceptions suivantes :
- Un destructeur est recherché comme décrit ci-dessus (dans la portée du nom à gauche de ::).
- Un conversion-type-id dans un nom de fonction de conversion définie par l'utilisateur est d'abord recherché dans la portée de la classe. S'il n'est pas trouvé, le nom est ensuite recherché dans la portée courante.
- Les noms utilisés dans les arguments de template sont recherchés dans la portée courante (pas dans la portée du nom du template).
- Les noms dans les using-declarations considèrent également les noms de classe/enum qui sont masqués par le nom d'une variable, membre de données, fonction ou énumérateur déclaré dans la même portée.
|
Cette section est incomplète
Motif : micro-exemples pour ce qui précède |
Si le côté droit de
::
nomme la même classe que le côté gauche, le nom désigne le
constructeur
de cette classe. Un tel nom qualifié ne peut être utilisé que dans une déclaration de constructeur et dans la
using-declaration
pour un
constructeur hérité
. Dans les recherches où les noms de fonctions sont ignorés (c'est-à-dire lors de la recherche d'un nom à gauche de
::
, lors de la recherche d'un nom dans un
spécificateur de type élaboré
, ou un
spécificateur de base
), la même syntaxe résout vers le nom de classe injecté :
struct A { A(); }; struct B : A { B(); }; A::A() {} // A::A désigne un constructeur, utilisé dans une déclaration B::B() {} // B::B désigne un constructeur, utilisé dans une déclaration B::A ba; // B::A désigne le type A (recherché dans la portée de B) A::A a; // Erreur : A::A ne désigne pas un type struct A::A a2; // OK : la recherche dans le spécificateur de type élaboré ignore les fonctions // donc A::A désigne simplement la classe A telle que vue depuis la portée de A // (c'est-à-dire, le nom de classe injecté)
La recherche de nom qualifié peut être utilisée pour accéder à un membre de classe qui est masqué par une déclaration imbriquée ou par une classe dérivée. Un appel à une fonction membre qualifiée n'est jamais virtuel :
struct B { virtual void foo(); }; struct D : B { void foo() override; }; int main() { D x; B& b = x; b.foo(); // Appelle D::foo (dispatch virtuel) b.B::foo(); // Appelle B::foo (dispatch statique) }
Membres de l'espace de noms
Si le nom à gauche de
::
fait référence à un espace de noms ou s'il n'y a rien à gauche de
::
(auquel cas il fait référence à l'espace de noms global), le nom qui apparaît à droite de
::
est recherché dans la portée de cet espace de noms, sauf que
- les noms utilisés dans les arguments de template sont recherchés dans la portée actuelle :
namespace N { template<typename T> struct foo {}; struct X {}; } N::foo<X> x; // Erreur : X est recherché comme ::X, et non comme N::X
La recherche qualifiée dans la portée d'un
namespace
N
considère d'abord toutes les déclarations situées dans
N
et toutes les déclarations situées dans les
membres de namespace inline
de
N
(et, transitivement, dans leurs membres de namespace inline). S'il n'y a pas de déclarations dans cet ensemble, elle considère alors les déclarations dans tous les namespaces désignés par les
using-directives
trouvées dans
N
et dans tous les membres de namespace inline transitifs de
N
. Les règles sont appliquées récursivement :
int x; namespace Y { void f(float); void h(int); } namespace Z { void h(double); } namespace A { using namespace Y; void f(int); void g(int); int i; } namespace B { using namespace Z; void f(char); int i; } namespace AB { using namespace A; using namespace B; void g(); } void h() { AB::g(); // AB est recherché, AB::g trouvé par recherche et sélectionné AB::g(void) // (A et B ne sont pas recherchés) AB::f(1); // D'abord, AB est recherché. Il n'y a pas de f // Ensuite, A, B sont recherchés // A::f, B::f trouvés par recherche // (mais Y n'est pas recherché donc Y::f n'est pas considéré) // La résolution de surcharge sélectionne A::f(int) AB::x++; // D'abord, AB est recherché. Il n'y a pas de x // Ensuite A, B sont recherchés. Il n'y a pas de x // Puis Y et Z sont recherchés. Il n'y a toujours pas de x : c'est une erreur AB::i++; // AB est recherché. Il n'y a pas de i // Ensuite A, B sont recherchés. A::i et B::i trouvés par recherche : c'est une erreur AB::h(16.8); // D'abord, AB est recherché. Il n'y a pas de h // Ensuite A, B sont recherchés. Il n'y a pas de h // Puis Y et Z sont recherchés // La recherche trouve Y::h et Z::h. La résolution de surcharge sélectionne Z::h(double) }
Il est permis que la même déclaration soit trouvée plus d'une fois :
namespace A { int a; } namespace B { using namespace A; } namespace D { using A::a; } namespace BD { using namespace B; using namespace D; } void g() { BD::a++; // OK : trouve le même A::a via B et via D }
|
Cette section est incomplète
Motif : le reste de N4861 6.5.3.2[namespace.qual], essayez de raccourcir leurs exemples |
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 | Applicable à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 215 | C++98 |
le nom précédant
::
devait être un nom de classe ou d'espace de noms,
donc les paramètres de template n'étaient pas autorisés à cet endroit |
le nom doit désigner une classe,
un espace de noms ou un type dépendant |
| CWG 318 | C++98 |
si le côté droit de
::
nomme la même classe
que le côté gauche, le nom qualifié était toujours considéré comme nommant le constructeur de cette classe |
ne nomme le constructeur
que lorsque c'est acceptable (par ex. pas dans un spécificateur de type élaboré) |