非限定名查詢
對於一個*非限定*名稱,即不出現在作用域解析運算子::
右側的名稱,名稱查詢會檢查下述作用域,直到找到至少一個任何型別的宣告,此時查詢停止,不再檢查其他作用域。(注意:某些上下文中的查詢會跳過一些宣告,例如,在::
左側使用的名稱的查詢會忽略函式、變數和列舉器宣告,用作基類說明符的名稱的查詢會忽略所有非型別宣告)。
為了非限定名稱查詢的目的,透過using 指令指定的名稱空間中的所有宣告,都表現得如同宣告在包含 using 指令以及被指定的名稱空間(直接或間接)的最近的封閉名稱空間中。
用於函式呼叫運算子(以及,等效地,表示式中的運算子)左側的名稱的非限定名稱查詢,在實參依賴查詢中描述。
目錄 |
[編輯] 檔案作用域
對於在全域性(頂層名稱空間)作用域、任何函式、類或使用者宣告的名稱空間之外使用的名稱,在名稱使用之前檢查全域性作用域。
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 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 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
[編輯] 非成員函式定義
對於在函式定義中使用的名稱(在其函式體中或作為預設實參的一部分),如果該函式是使用者宣告的名稱空間或全域性名稱空間的成員,則在名稱使用之前搜尋使用該名稱的塊,然後在該塊開始之前搜尋封閉塊,依此類推,直到到達函式體所在的塊。然後,在函式宣告所在的名稱空間中搜索,直到使用該名稱的函式的定義(不一定是宣告),然後是封閉名稱空間,依此類推。
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 { // 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
[編輯] 注入的類名
對於在類或類模板的定義內部或從其派生出來的類或類模板的名稱,非限定名稱查詢會找到正在定義的類,如同該名稱是透過成員宣告(具有公共成員訪問許可權)引入的一樣。有關更多詳細資訊,請參見注入的類名。
[編輯] 成員函式定義
對於在成員函式體內部、成員函式的預設實參、成員函式的異常規範或預設成員初始化器中使用的名稱,搜尋的作用域與類定義中的相同,只是考慮類的整個作用域,而不僅僅是使用該名稱的宣告之前的部分。對於巢狀類,搜尋封閉類的整個體。
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 的基類子物件。(請注意,這不會隱藏繼承層次結構中不是B 的基類的A 的任何額外非虛副本中的名稱:此規則僅對虛繼承有效。)透過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 }
[編輯] 友元函式定義
對於在授予友元關係的類的體內部的友元函式定義中使用的名稱,非限定名稱查詢過程與成員函式相同。對於在類體外部定義的友元函式中使用的名稱,非限定名稱查詢過程與名稱空間中的函式相同。
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 }
[編輯] 友元函式宣告
對於在友元來自另一個類的成員函式的友元函式宣告的宣告符中使用的名稱,如果該名稱不是宣告符識別符號中任何模板實參的一部分,則非限定查詢首先檢查成員函式類的整個作用域。如果在該作用域中未找到(或者如果該名稱是宣告符識別符號中模板實參的一部分),則查詢繼續進行,如同查詢授予友元關係的類的成員函式一樣。
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) };
[編輯] 預設實參
對於在函式宣告中的預設實參中使用的名稱,或在建構函式的成員初始化器的表示式部分中使用的名稱,函式引數名稱首先被找到,然後才檢查封閉塊、類或名稱空間作用域。
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
[編輯] 靜態資料成員定義
對於在靜態資料成員的定義中使用的名稱,查詢過程與在成員函式定義中使用的名稱相同。
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
[編輯] 列舉器宣告
對於在列舉器宣告的初始化器部分中使用的名稱,首先找到同一列舉中先前宣告的列舉器,然後非限定名稱查詢繼續檢查封閉塊、類或名稱空間作用域。
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 };
[編輯] 函式 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; }
[編輯] 過載運算子
對於表示式中使用的運算子(例如,在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) }
[編輯] 模板定義
對於模板定義中使用的非依賴名稱,非限定名稱查詢在檢查模板定義時進行。在該點進行的宣告繫結不受例項化點可見的宣告影響。對於模板定義中使用的依賴名稱,查詢會延遲到模板實參已知時,此時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 };
注意:請參閱依賴名稱查詢規則以瞭解此規則的原因和含義。
[編輯] 模板名稱
本節不完整 原因:在 -> 和 . 之後對模板名稱進行雙作用域查詢。 |
[編輯] 類模板的非模板成員
本節不完整 |
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 490 | C++98 | 友元函式宣告中模板實參中的任何名稱 沒有在成員函式類的作用域中查詢。 只排除宣告符識別符號中模板實參中的名稱 |
僅排除模板實參中的名稱 在宣告符識別符號中 CWG 514 |
CWG 514 | C++98 | 在名稱空間作用域中使用的任何非限定名稱 首先在該作用域中查詢 |
用於定義名稱空間變數成員的非限定名稱 在該名稱空間之外,首先在該名稱空間中查詢。 首先在該名稱空間中查詢 |
[編輯] 參考文獻
- 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]