非限定名稱查找
對於未限定(unqualified)名稱,即不出現在範圍解析運算子 :: 右側的名稱,名稱查找會檢查如下文所述的作用域(scopes),直到找到至少一個任何類型的宣告為止,此時查找停止且不再檢查後續作用域。(註:從某些語境進行的查找會跳過某些宣告,例如,用於 :: 左側的名稱查找會忽略函式、變數和列舉元宣告;用作基底類別說明符的名稱查找會忽略所有非類型宣告)。
為了未限定名稱查找的目的,所有來自透過 using 指令(using-directive)提名的命名空間中的宣告,其呈現方式就像是宣告在同時直接或間接包含該 using 指令與被提名命名空間的最內層外圍命名空間中。
用於函式呼叫運算子左側(以及同等地,運算式中的運算子)的名稱之未限定名稱查找,描述於引數相依查找(ADL)中。
目錄 |
[編輯] 檔案作用域
對於在全域(頂層命名空間)作用域中使用,且位於任何函式、類別或使用者宣告的命名空間之外的名稱,會檢查該名稱使用前的全域作用域。
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 } }
[編輯] 類別定義
對於在 類別定義 中任何位置使用的名稱(包括基底類別說明符和巢狀類別定義),除了在成員函式主體、成員函式預設引數、成員函式異常規格或預設成員初始化程式內(其中成員可能屬於巢狀類別,而該類別定義位於外圍類別的主體中)之外,會搜尋以下作用域:
對於 friend 宣告,判斷其是否指向先前宣告實體的查找程序如上所述,但它在最內層外圍命名空間之後就會停止。
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
[編輯] 注入類別名稱
對於在類別或類別模板定義內(或衍生自其中的類別)使用的該類別或模板名稱,未限定名稱查找會找到正在定義的類別,就好像該名稱是由成員宣告(具備 public 成員存取權限)所引入的一樣。詳見 注入類別名稱(injected-class-name)。
[編輯] 成員函式定義
對於在成員函式主體、成員函式預設引數、成員函式異常規格或預設成員初始化程式內使用的名稱,搜尋的作用域與類別定義中相同,差別在於會考慮類別的整個作用域,而不僅僅是使用該名稱的宣告之前的部分。對於巢狀類別,會搜尋外圍類別的整個主體。
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 } }
- 無論哪種方式,在檢查衍生該類別的基底時,都會遵循以下規則,有時稱為虛擬繼承中的主導地位(dominance):
若在子物件 B 中發現的成員名稱隱藏了任何子物件 A 中的相同成員名稱,前提是 A 是 B 的基底類別子物件。(注意,這並不會隱藏繼承格架上任何非 B 基底的額外非虛擬 A 副本中的名稱:此規則僅對虛擬繼承有效。)由 using-declarations 引入的名稱被視為包含該宣告的類別中的名稱。檢查每個基底後,產生的集合必須包含來自相同類型子物件的靜態成員宣告,或來自相同子物件的非靜態成員宣告。 |
(直到 C++11) |
會建構一個查找集(lookup set),由宣告以及發現這些宣告的子物件組成。Using-declarations 會被它們代表的成員取代,而類型宣告(包括 injected-class-names)會被它們代表的類型取代。若 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 }
[編輯] 友元函式定義
對於在授予友元關係的類別主體內的 friend 函式定義中使用的名稱,其未限定名稱查找方式與成員函式相同。對於在類別主體外定義的 friend 函式中使用的名稱,其未限定名稱查找方式與命名空間中的函式相同。
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 }
[編輯] 友元函式宣告
對於在宣告另一個類別成員函式為友元的 friend 函式宣告宣告符中使用的名稱,若該名稱不是 宣告符 識別碼中任何模板引數的一部分,則未限定查找首先檢查該成員函式類別的整個作用域。若在該作用域內找不到(或者該名稱是宣告符識別碼中模板引數的一部分),則繼續查找,就好像它是授予友元關係的類別的成員函式一樣。
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) };
[編輯] 預設引數
對於在函式宣告的 預設引數 中使用的名稱,或在建構子 成員初始化清單 的 expression 部分使用的名稱,會先尋找函式參數名稱,接著才檢查外圍區塊、類別或命名空間作用域。
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 區塊 的 處理常式(handler)中使用的名稱,查找程序就像是針對在函式主體最外層區塊開端處使用的名稱一樣(特別是,函式參數是可見的,但在該最外層區塊中宣告的名稱則不可見)。
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++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| CWG 490 | C++98 | 友元成員函式宣告中模板引數 裡的任何名稱未在成員函式 所屬類別的作用域中查找 |
僅排除位於宣告符識別碼之 模板引數中的 名稱 |
| 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]