Qualified name lookup
Un
nombre calificado
es un nombre que aparece en el lado derecho del operador de resolución de ámbito
::
(ver también
identificadores calificados
).
Un nombre calificado puede referirse a un
- miembro de clase (incluyendo funciones estáticas y no estáticas, tipos, plantillas, etc.),
- miembro de espacio de nombres (incluyendo otro espacio de nombres),
- enumerador.
Si no hay nada en el lado izquierdo del
::
, la búsqueda considera únicamente declaraciones en
el ámbito del espacio de nombres global
. Esto permite hacer referencia a dichos nombres incluso si estaban ocultos por una declaración local:
#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; // Error: la búsqueda no calificada para 'std' encuentra la estructura ::std::cout << ::ok; // OK: ::std encuentra el espacio de nombres std }
Antes de que se pueda realizar la búsqueda de nombres para el nombre en el lado derecho de
::
, la búsqueda debe completarse para el nombre en su lado izquierdo (a menos que se use una expresión
decltype
, o no haya nada a la izquierda). Esta búsqueda, que puede ser calificada o no calificada, dependiendo de si hay otro
::
a la izquierda de ese nombre, considera solo espacios de nombres, tipos de clase, enumeraciones y plantillas cuyas especializaciones son tipos. Si el nombre encontrado a la izquierda no designa un espacio de nombres o una clase, enumeración, o tipo dependiente, el programa está mal formado:
struct A { static int n; }; int main() { int A; A::n = 42; // OK: la búsqueda no calificada de A a la izquierda de :: ignora la variable A b; // Error: la búsqueda no calificada de A encuentra la variable A } template<int> struct B : A {}; namespace N { template<int> void B(); int f() { return B<0>::n; // Error: N::B<0> no es un tipo } }
Cuando se utiliza un nombre calificado como declarador , entonces la búsqueda no calificada de los nombres utilizados en el mismo declarador que siguen a ese nombre calificado, pero no los nombres que lo preceden, se realiza en el ámbito de la clase o espacio de nombres del miembro:
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]; // Error: la búsqueda de X encuentra ::X, no C::X C::X C::arr[number], brr[number]; // OK: el tamaño de arr es 50, el tamaño de brr es 100
Si
::
es seguido por el carácter
~
que a su vez es seguido por un identificador (es decir, especifica un destructor o pseudo-destructor), ese identificador se busca en el mismo ámbito que el nombre en el lado izquierdo 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(); // El nombre I después de ~ se busca en el mismo ámbito que I antes de :: // (es decir, dentro del ámbito de C, por lo que encuentra C::I) q->I1::~I2(); // El nombre I2 se busca en el mismo ámbito que I1 // (es decir, desde el ámbito actual, por lo que encuentra ::I2) AB x; x.AB::~AB(); // El nombre AB después de ~ se busca en el mismo ámbito que AB antes de :: // (es decir, desde el ámbito actual, por lo que encuentra ::AB) }
EnumeradoresSi la búsqueda del nombre del lado izquierdo resulta en una enumeración (ya sea con ámbito o sin ámbito), la búsqueda del lado derecho debe resultar en un enumerador que pertenezca a esa enumeración, de lo contrario el programa está mal formado. |
(desde C++11) |
Miembros de clase
Si la búsqueda del nombre del lado izquierdo resulta en un nombre de clase/struct o unión, el nombre del lado derecho de
::
se busca en el ámbito de esa clase (y por lo tanto puede encontrar una declaración de un miembro de esa clase o de su base), con las siguientes excepciones:
- Un destructor se busca como se describió anteriormente (en el alcance del nombre a la izquierda de ::).
- Un conversion-type-id en un user-defined conversion function name se busca primero en el alcance de la clase. Si no se encuentra, el nombre se busca luego en el alcance actual.
- Los nombres utilizados en argumentos de plantilla se buscan en el alcance actual (no en el alcance del nombre de la plantilla).
- Los nombres en using-declarations también consideran nombres de clase/enum que están ocultos por el nombre de una variable, miembro de datos, función o enumerador declarado en el mismo alcance.
|
Esta sección está incompleta
Motivo: micro-ejemplos para lo anterior |
Si el lado derecho de
::
nombra la misma clase que el lado izquierdo, el nombre designa el
constructor
de esa clase. Tal nombre calificado solo puede usarse en una declaración de un constructor y en la
using-declaration
para un
inheriting constructor
. En aquellas búsquedas donde se ignoran los nombres de función (es decir, al buscar un nombre a la izquierda de
::
, al buscar un nombre en un
elaborated type specifier
, o en un
base specifier
), la misma sintaxis resuelve al injected-class-name:
struct A { A(); }; struct B : A { B(); }; A::A() {} // A::A nombra un constructor, usado en una declaración B::B() {} // B::B nombra un constructor, usado en una declaración B::A ba; // B::A nombra el tipo A (buscado en el ámbito de B) A::A a; // Error: A::A no nombra un tipo struct A::A a2; // OK: la búsqueda en especificador de tipo elaborado ignora funciones // por lo que A::A simplemente nombra la clase A vista desde dentro del ámbito de A // (es decir, el nombre de clase inyectado)
La búsqueda de nombre calificado puede utilizarse para acceder a un miembro de clase que está oculto por una declaración anidada o por una clase derivada. Una llamada a una función miembro calificada nunca es virtual:
struct B { virtual void foo(); }; struct D : B { void foo() override; }; int main() { D x; B& b = x; b.foo(); // Llama a D::foo (despacho virtual) b.B::foo(); // Llama a B::foo (despacho estático) }
Miembros del espacio de nombres
Si el nombre a la izquierda de
::
se refiere a un namespace o si no hay nada a la izquierda de
::
(en cuyo caso se refiere al namespace global), el nombre que aparece en el lado derecho de
::
se busca en el ámbito de ese namespace, excepto que
- los nombres utilizados en los argumentos de plantilla se buscan en el ámbito actual:
namespace N { template<typename T> struct foo {}; struct X {}; } N::foo<X> x; // Error: X se busca como ::X, no como N::X
La búsqueda calificada dentro del ámbito de un
namespace
N
primero considera todas las declaraciones que se encuentran en
N
y todas las declaraciones que se encuentran en los
miembros de namespace inline
de
N
(y, transitivamente, en sus miembros de namespace inline). Si no hay declaraciones en ese conjunto, entonces considera las declaraciones en todos los namespaces nombrados por
using-directives
encontrados en
N
y en todos los miembros de namespace inline transitivos de
N
. Las reglas se aplican recursivamente:
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(); // Se busca en AB, AB::g se encuentra mediante búsqueda y se selecciona AB::g(void) // (A y B no se buscan) AB::f(1); // Primero, se busca en AB. No hay f // Luego, se buscan A y B // A::f, B::f se encuentran mediante búsqueda // (pero Y no se busca, por lo que Y::f no se considera) // La resolución de sobrecarga selecciona A::f(int) AB::x++; // Primero, se busca en AB. No hay x // Luego se buscan A y B. No hay x // Luego se buscan Y y Z. Todavía no hay x: esto es un error AB::i++; // Se busca en AB. No hay i // Luego se buscan A y B. A::i y B::i se encuentran mediante búsqueda: esto es un error AB::h(16.8); // Primero, se busca en AB. No hay h // Luego se buscan A y B. No hay h // Luego se buscan Y y Z // La búsqueda encuentra Y::h y Z::h. La resolución de sobrecarga selecciona Z::h(double) }
Se permite que la misma declaración se encuentre más de una vez:
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: encuentra el mismo A::a a través de B y a través de D }
|
Esta sección está incompleta
Razón: el resto de N4861 6.5.3.2[namespace.qual], intentar acortar sus ejemplos |
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Se aplica a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 215 | C++98 |
el nombre precedente a
::
debía ser un nombre de clase o espacio de nombres,
por lo que no se permitían parámetros de plantilla allí |
el nombre debe designar una clase,
espacio de nombres o tipo dependiente |
| CWG 318 | C++98 |
si el lado derecho de
::
nombra la misma clase
que el lado izquierdo, el nombre calificado siempre se consideraba que nombraba el constructor de esa clase |
solo nombra el constructor
cuando es aceptable (ej. no en un especificador de tipo elaborado) |