Name lookup
Name lookup is the procedure by which a name, when encountered in a program, is associated with the declaration that introduced it.
For function names, name lookup can associate multiple declarations with the same name, and may obtain additional declarations from argument-dependent lookup. Template argument deduction may also apply, and the set of declarations is passed to overload resolution, which selects the declaration that will be used. Member access rules, if applicable, are considered only after name lookup and overload resolution.
For all other names (variables, namespaces, classes, etc), name lookup must produce a single declaration in order for the program to compile.
For example, to compile std::cout << std::endl;, the compiler performs:
- unqualified name lookup for the name
std
, which finds the declaration of namespace std in the header<iostream>
- qualified name lookup for the name
cout
, which finds a variable declaration in the namespacestd
- qualified name lookup for the name
endl
, which finds a function template declaration in the namespacestd
- argument-dependent lookup for the name
operator <<
, which finds multiple function template declarations in the namespace std
Contents |
[edit] Unqualified name lookup
For an unqualified name, that is name that does not appear to the right of a scope resolution operator ::
, name lookup examines the scopes as described below, until it finds at least one declaration of any kind, at which time the lookup stops and no further scopes are examined. (Note: lookup from some contexts skips some declarations, for example, lookup of the name used to the left of ::
ignores function, variable, and enumerator declarations, lookup of a name used a base class specifier ignores all non-type declarations)
For the purpose of unqualified name lookup, all declarations from a namespace nominated by a using directive appear as if declared in the nearest enclosing namespace which contains, directly or indirectly, both the using-directive and the nominated namespace.
Unqualified name lookup of the name used to the left of the function-call operator (and, equivalently, operator in an expression) is described in argument-dependent lookup.
int n = 1; // declaration of n int x = n + 1; // OK: lookup finds ::n int z = y - 1; // Error: lookup fails int y = 2; // declaration of y int main() {}
int n = 1; // declaration namespace N { int m = 2; namespace Y { int x = n; // OK, lookup finds ::n int y = m; // OK, lookup finds ::N::m int z = k; // Error: lookup fails } int k = 3; }
namespace A { namespace N { void f(); int i=3; // found 3rd (if 2nd is not present) } int i=4; // found 4th (if 3rd is not present) } int i=5; // found 5th (if 4th is not present) void A::N::f() { int i = 2; // found 2nd (if 1st is not present) while(true) { int i = 1; // found 1st: lookup is done std::cout << i; } } // int i; // not found namespace A { namespace N { // int i; // not found } }
namespace M { // const int i = 1; // never found class B { // const const int i = 3; // found 3nd (but later rejected by access check) }; } // const int i = 5; // found 5th namespace N { // const int i = 4; // found 4th class Y : public M::B { // static const int i = 2; // found 2nd class X { // static const int i = 1; // found 1st int a[i]; // use of i // static const int i = 1; // never found }; // static const int i = 2; // never found }; // const int i = 4; // never found } // const int i = 5; // never found
class B { // int i; // found 3rd }; namespace M { // int i; // found 5th namespace N { // int i; // found 4th class X : public B { // int i; // found 2nd void f(); // int i; // found 2nd as well }; // int i; // found 4th } } // int i; // found 6th void M::N::X::f() { // int i; // found 1st i = 16; // int i; // never found } namespace M { namespace N { // int i; // never found } }
-
- Either way, when examining the bases from which the class is derived, the following rules, sometime referred to as dominance in virtual inheritance, are followed:
A member name found in a sub-object B hides the same member name in any sub-object A if A is a base class sub-object of B . (note that this does not hide the name in any additional, non-virtual, copies of A on the inheritance lattice that aren't bases of B : this rule only has an effect on virtual inheritance). Names introduced by using-declarations are treated as names in the class containing the declaration. After examining each base, the resulting set must either include declarations of a static member from subobjects of the same type, or declarations of non-static members from the same subobject |
(until C++11) |
A lookup set is constructed, which consists of the declarations and the subobjects in which these declarations were found. Using-declarations are replaced by the members they represent and type declarations, including injected-class-names are replaced by the types they represent. If C is the class in whose scope the name was used, C is examined first. If the list of declarations in C is empty, lookup set is built for each of its direct bases Bi (recursively applying these rules if Bi has its own bases). Once built, the lookup sets for the direct bases are merged into the lookup set in C as follows
|
(since 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, calls X::f (qualified lookup) f(); // OK, calls B1::f (unqualified lookup) // C++98 rules: B1::f hides X::f, so even though X::f can be reached from D // through B2, it is not found by name lookup from D. // C++11 rules: lookup set for f in D finds nothing, proceeds to bases // lookup set for f in B1 finds B1::f, and is completed // merge replaces the empty set, now lookup set for f in C has B1::f in B1 // lookup set for f in B2 finds nothing, proceeds to bases // lookup for f in X finds X::f // merge replaces the empty set, now lookup set for f in B2 has X::f in X // merge into C finds that every subobject (X) in the lookup set in B2 is a base // of every subobject (B1) already merged, so the B2 set is discareded // C is left with just B1::f found in B1 // (if struct D : B2, B1 was used, then the last merge would *replace* C's // so far merged X::f in X because every subobject already added to C (that is X) // would be a base of at least one subobject in the new set (B1), the end // result would be the same: lookup set in C holds just B1::f found in B1) } };
-
- Unqualified name lookup that finds static members of
B
, nested types ofB
, and enumerators declared inB
is unambiguous even if there are multiple non-virtual base subobjects of typeB
in the inheritance tree of the class being examined:
- Unqualified name lookup that finds static members of
struct V { int v; }; struct A { int a; static int s; enum { e }; }; struct B : A, virtual V { }; struct C : A, virtual V { }; struct D : B, C { }; void f(D& pd) { ++pd.v; // OK: only one v because only one virtual base subobject ++pd.s; // OK: only one static A::s, even though found in B and in C int i = pd.e; // OK: only one enumerator A::e, even though found in B and C ++pd.a; // error, ambiguous: A::a in B and A::a in C }
int i = 3; // found 3rd for f1, found 2nd for f2 struct X { static const int i = 2; // found 2nd for f1, never found for f2 friend void f1(int x) { // int i; // found 1st i = x; // finds and modifies X::i } friend int f2(); // static const int i = 2; // found 2nd for f1 anywhere in class scope }; void f2(int x) { // int i; // found 1st i = x; // finds and modifies ::i }
// the class whose member functions are friended struct A { typedef int AT; void f1(AT); void f2(float); template <class T> void f3(); }; // the class that is granting friendship struct B { typedef char AT; typedef float BT; friend void A::f1(AT); // lookup for AT finds A::AT friend void A::f2(BT); // lookup for BT finds B::BT friend void A::f3<AT>(); // lookup for AT finds B::AT };
class X { int a, b, i, j; public: const int& r; X(int i): r(a), // initializes X::r to refer to X::a b(i), // initializes X::b to the value of the parameter i i(i), // initializes X::i to the value of the parameter i j(this->i) // initializes X::j to the value of X::i { } } int a; int f(int a, int b = a); // error: lookup for a finds the parameter a, not ::a // and parameters are not allowed as default arguments
const int RED = 7; enum class color { RED, GREEN = RED+2, // RED finds color::RED, not ::RED, so GREEN = 2 BLUE = ::RED+4 // qualified lookup finds ::RED, BLUE = 11 };
struct X { static int x; static const int n = 1; // found 1st }; int n = 2; // found 2nd. int X::x = n; // finds X::n, sets X::x to 1, not 2
namespace X { extern int x; // declaration, not definition int n = 1; // found 1st }; int n = 2; // found 2nd. int X::x = n; // finds X::n, sets X::x to 1
int n = 3; // found 3rd int f(int n = 2) // found 2nd try { int n = -1; // never found } catch(...) { // int n = 1; // found 1st assert(n == 2); // loookup for n finds function parameter f throw; }
operator+
used in a+b
), the lookup rules are slightly different from the operator used in an explicit function-call expression such as operator+(a,b)
: when parsing an expression, two separate lookups are performed: for the non-member operator overloads and for the member operator overloads (for the operators where both forms are permitted). Those sets are then merged with the built-in operator overloads on equal grounds as described in overload resolution. If explicit function call syntax is used, regular unqualified name lookup is performed:
struct A {}; void operator+(A, A); // user-defined non-member operator+ struct B { void operator+(B); // user-defined member operator+ void f (); }; A a; void B::f() // definition of a member function of B { operator+(a,a); // error: regular name lookup from a member function // finds the declaration of operator+ in the scope of B // and stops there, never reaching the global scope a + a; // OK: member lookup finds B::operator+, non-member lookup // finds ::operator+(A,A), overload resolution selects ::operator+(A,A) }
void f(char); // first declaration of f template<class T> void g(T t) { f(1); // non-dependent name: lookup finds ::f(char) and binds it now f(T(1)); // dependent name: lookup postponed f(t); // dependent name: lookup postponed // dd++; // non-dependent name: lookup finds no declaration } enum E { e }; void f(E); // second declaration of f void f(int); // third declaration of f double dd; void h() { g(e); // instantiates g<E>, at which point // the second and the third uses of the name 'f' // are looked up and find ::f(char) (by lookup) and ::f(E) (by ADL) // then overload resolution chooses ::f(E). // This calls f(char), then f(E) twice g(32); // instantiates g<int>, at which point // the second and the third uses of the name 'f' // are looked up and find ::f(char) only // then overload resolution chooses ::f(char) // This calls f(char) three times } typedef double A; template<class T> class B { typedef int A; }; template<class T> struct X : B<T> { A a; // lookup for A finds ::A (double), not B<T>::A };
This section is incomplete Reason: lookup of injected-class-name, lookup in out-of-body members of class templates |
This section is incomplete Reason: dual-scope lookup of the template name after -> and . |
[edit] Qualified name lookup
This section is incomplete |
[edit] References
- C++11 standard (ISO/IEC 14882:2011):
-
- 3.4 Name lookup [basic.lookup]
-
- 10.2 Member name lookup [class.member.lookup]
-
- 14.6 Name resolution [temp.res]
- C++98 standard (ISO/IEC 14882:1998):
-
- 3.4 Name lookup [basic.lookup]
-
- 10.2 Member name lookup [class.member.lookup]
-
- 14.6 Name resolution [temp.res]