非限定名查找
对于非限定名,即不出现在作用域解析运算符 ::
右侧的名称,名称查找会检查下述作用域,直到找到至少一个任何种类的声明,此时查找停止,不再检查其他作用域。(注意:来自某些上下文的查找会跳过某些声明,例如,用于 ::
左侧的名称查找会忽略函数、变量和枚举数声明,用作基类说明符的名称查找会忽略所有非类型声明)。
为了非限定名查找的目的,来自 using 指令指定的命名空间的所有声明,都如同声明于最近的外围命名空间中,该命名空间直接或间接地同时包含 using 指令和指定的命名空间。
用于函数调用运算符左侧(以及等效地,表达式中的运算符)的名称的非限定名查找,在实参依赖查找中描述。
目录 |
[edit] 文件作用域
对于在全局(顶层命名空间)作用域中使用的名称,在任何函数、类或用户声明的命名空间之外,会检查使用名称之前的全局作用域
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
[edit] 命名空间作用域
对于在用户声明的命名空间中使用的名称,在任何函数或类之外,会在使用名称之前搜索此命名空间,然后搜索包围此命名空间的命名空间(在此命名空间声明之前),依此类推,直到到达全局命名空间。
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; }
[edit] 在其命名空间外部的定义
对于在命名空间成员变量的定义中使用的名称(在命名空间外部),查找过程与在命名空间内部使用的名称相同
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
[edit] 非成员函数定义
对于在函数定义中使用的名称,无论是在其主体中还是作为默认实参的一部分,其中函数是用户声明的或全局命名空间的成员,都会在使用名称之前搜索使用该名称的块,然后搜索外围块(在该块开始之前),依此类推,直到到达作为函数主体的块。然后,搜索声明函数的命名空间,直到使用该名称的函数的定义(不一定是声明),然后是外围命名空间等。
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 } }
[edit] 类定义
对于在类定义中的任何位置(包括基类说明符和嵌套类定义)使用的名称,除了在成员函数主体内部、成员函数的默认实参、成员函数的异常规范或默认成员初始化器内部,其中成员可能属于嵌套类,而嵌套类的定义在外围类的主体中,则搜索以下作用域
对于友元声明,确定它是否引用先前声明的实体的查找过程与上述相同,但会在最内层的外围命名空间之后停止。
namespace M { // const int i = 1; // never found class B { // static const int i = 3; // found 3rd (but will not pass 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
[edit] 注入类名
对于在类或类模板的定义内使用的类或类模板的名称,或从类或类模板派生的名称,非限定名查找会找到正在定义的类,就好像该名称是由成员声明(具有公共成员访问权限)引入的一样。有关更多详细信息,请参见注入类名。
[edit] 成员函数定义
对于在成员函数主体内部、成员函数的默认实参、成员函数的异常规范或默认成员初始化器内部使用的名称,搜索的作用域与类定义中相同,不同之处在于,会考虑类的整个作用域,而不仅仅是使用该名称的声明之前的部分。对于嵌套类,会搜索外围类的整个主体。
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 } }
- 无论哪种方式,当检查类派自的基类时,都遵循以下规则,有时称为虚拟继承中的支配:
在子对象 B 中找到的成员名称会隐藏任何子对象 A 中的相同成员名称,如果 A 是 B 的基类子对象。(请注意,这不会隐藏继承格子上任何额外的、非虚拟的 A 副本中的名称,这些副本不是 B 的基类:此规则仅对虚拟继承有效。)由 using 声明引入的名称被视为包含声明的类中的名称。在检查每个基类后,结果集必须包含来自相同类型的子对象的静态成员的声明,或来自相同子对象的非静态成员的声明。 |
(直到 C++11) |
构造一个查找集,它由声明和在其中找到这些声明的子对象组成。 Using 声明被它们表示的成员替换,类型声明(包括注入类名)被它们表示的类型替换。如果 C 是在其作用域中使用名称的类,则首先检查 C 。如果 C 中的声明列表为空,则为每个直接基类 Bi 构建查找集(如果 Bi 有自己的基类,则递归应用这些规则)。构建完成后,直接基类的查找集将合并到 C 中的查找集中,如下所示:
|
(自 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 discarded // 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)
- 即使被检查的类的继承树中有类型
B
的多个非虚拟基类子对象,找到B
的静态成员、B
的嵌套类型以及在B
中声明的枚举数的非限定名查找也是明确的
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: only one v because only one virtual base subobject ++pd.s; // OK: only one static B::s, even though found in both B1 and B2 int i = pd.e; // OK: only one enumerator B::e, even though found in both B1 and B2 ++pd.a; // error, ambiguous: B::a in B1 and B::a in B2 }
[edit] 友元函数定义
对于在授予友元关系的类的主体内部的友元函数定义中使用的名称,非限定名查找的过程与成员函数相同。 对于在类主体外部定义的友元函数中使用的名称,非限定名查找的过程与命名空间中的函数相同。
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 }
[edit] 友元函数声明
对于在友元函数声明的声明符中使用的名称,该声明声明了来自另一个类的成员函数为友元,如果该名称不是声明符标识符中任何模板实参的一部分,则非限定查找首先检查成员函数类的整个作用域。 如果在该作用域中未找到(或者如果该名称是声明符标识符中模板实参的一部分),则查找继续,就好像是授予友元关系的类的成员函数一样。
template<class T> struct S; // the class whose member functions are friended struct A { typedef int AT; void f1(AT); void f2(float); template<class T> void f3(); void f4(S<AT>); }; // the class that is granting friendship for f1, f2 and f3 struct B { typedef char AT; typedef float BT; friend void A::f1(AT); // lookup for AT finds A::AT (AT found in A) friend void A::f2(BT); // lookup for BT finds B::BT (BT not found in A) friend void A::f3<AT>(); // lookup for AT finds B::AT (no lookup in A, because // AT is in the declarator identifier A::f3<AT>) }; // the class template that is granting friendship for f4 template<class AT> struct C { friend void A::f4(S<AT>); // lookup for AT finds A::AT // (AT is not in the declarator identifier A::f4) };
[edit] 默认实参
对于在函数声明中的默认实参中使用的名称,或在构造函数的成员初始化列表的表达式部分中使用的名称,首先找到函数形参名称,然后再检查外围块、类或命名空间作用域
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
[edit] 静态数据成员定义
对于在静态数据成员的定义中使用的名称,查找过程与在成员函数的定义中使用的名称相同。
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
[edit] 枚举数声明
对于在枚举数声明的初始化器部分中使用的名称,首先找到同一枚举中先前声明的枚举数,然后再继续非限定名查找以检查外围块、类或命名空间作用域。
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 };
[edit] 函数 try 块的处理程序
对于在函数 try 块的处理程序中使用的名称,查找过程与在函数主体的最外层块的开头使用的名称相同(特别是,函数形参可见,但该最外层块中声明的名称不可见)
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; }
[edit] 重载运算符
对于在表达式中使用的运算符(例如,在 a + b 中使用的 operator+),查找规则与在显式函数调用表达式(例如 operator+(a, b))中使用的运算符略有不同:在解析表达式时,会执行两次单独的查找:针对非成员运算符重载和成员运算符重载(对于两种形式都允许的运算符)。 然后,这些集合与内置运算符重载合并,地位平等,如重载解析中所述。 如果使用显式函数调用语法,则执行常规的非限定名查找
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) }
[edit] 模板定义
对于在模板定义中使用的非依赖名称,非限定名查找在检查模板定义时进行。 在该点进行的声明绑定不受实例化点可见的声明的影响。 对于在模板定义中使用的依赖名称,查找会推迟到模板实参已知时,此时ADL会检查函数声明具有外部链接(直到 C++11),这些声明在模板定义上下文以及模板实例化上下文中可见,而非 ADL 查找仅检查函数声明具有外部链接(直到 C++11),这些声明在模板定义上下文中可见(换句话说,在模板定义后添加新的函数声明不会使其可见,除非通过 ADL)。 如果 ADL 查找检查的命名空间中存在具有外部链接的更好匹配,该匹配在其他翻译单元中声明,或者如果检查这些翻译单元会导致查找不明确,则行为是未定义的。 在任何情况下,如果基类依赖于模板形参,则非限定名查找不会检查其作用域(无论是在定义点还是在实例化点)。
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 };
注意:有关此规则的理由和含义,请参见依赖名称查找规则。
[edit] 模板名称
本节不完整 原因:模板名称在 -> 和 . 之后的双重作用域查找。 |
[edit] 模板外部的类模板成员
本节不完整 |
[edit] 缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 490 | C++98 | 友元中模板实参中的任何名称 成员函数声明都未查找 在成员函数类的作用域中 |
仅排除名称 在模板实参中 声明符标识符 |
CWG 514 | C++98 | 在命名空间中使用的任何非限定名 作用域首先在该作用域中查找 |
用于定义命名空间变量成员的非限定名(在该命名空间外部) 命名空间变量成员(在该命名空间外部)首先在该命名空间中查找 命名空间成员变量(在该命名空间外部)首先在该命名空间中查找 |
[edit] 引用
- C++23 标准 (ISO/IEC 14882:2024)
- 6.5 名称查找 [basic.lookup] (p: 44-45)
- 6.5.2 成员名称查找 [class.member.lookup] (p: 45-47)
- 13.8 名称解析 [temp.res] (p: 399-403)
- C++20 标准 (ISO/IEC 14882:2020)
- 6.5 名称查找 [basic.lookup] (p: 38-50)
- 11.8 成员名称查找 [class.member.lookup] (p: 283-285)
- 13.8 名称解析 [temp.res] (p: 385-400)
- C++17 标准 (ISO/IEC 14882:2017)
- 6.4 名称查找 [basic.lookup] (p: 50-63)
- 13.2 成员名称查找 [class.member.lookup] (p: 259-262)
- 17.6 名称解析 [temp.res] (p: 375-378)
- C++14 标准 (ISO/IEC 14882:2014)
- 3.4 名称查找 [basic.lookup] (p: 42-56)
- 10.2 成员名称查找 [class.member.lookup] (p: 233-236)
- 14.6 名称解析 [temp.res] (p: 346-359)
- C++11 标准 (ISO/IEC 14882:2011)
- 3.4 名称查找 [basic.lookup]
- 10.2 成员名称查找 [class.member.lookup]
- 14.6 名称解析 [temp.res]
- C++98 标准 (ISO/IEC 14882:1998)
- 3.4 名称查找 [basic.lookup]
- 10.2 成员名称查找 [class.member.lookup]
- 14.6 名称解析 [temp.res]