命名空間
變體
動作

非限定名稱查找

出自 cppreference.com
< cpp‎ | language
 
 
C++ 語言
一般主題
流程控制
條件執行陳述式
if
疊代陳述式 (迴圈)
for
範圍 for (C++11)
跳躍陳述式
函式
函式宣告
Lambda 函式運算式
inline 指定符
動態例外規範 (直到 C++17*)
noexcept 指定符 (C++11)
例外
命名空間
型別
指定符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
儲存期指定符
初始化
 
 

對於未限定(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
    }
}

[編輯] 類別定義

對於在 類別定義 中任何位置使用的名稱(包括基底類別說明符和巢狀類別定義),除了在成員函式主體、成員函式預設引數、成員函式異常規格或預設成員初始化程式內(其中成員可能屬於巢狀類別,而該類別定義位於外圍類別的主體中)之外,會搜尋以下作用域:

a) 使用該名稱的類別主體,直到使用點為止;
b) 其基底類別的整個主體,若未發現宣告,則遞迴搜尋其基底;
c) 若此類別是 巢狀 的,搜尋外圍類別的主體直到此類別的定義處,以及外圍類別基底類別的整個主體;
d) 若此類別是 局部(local)類別,或巢狀於局部類別中,搜尋定義該類別的區塊作用域直到定義點為止;
e) 若此類別是某命名空間的成員,或巢狀於某命名空間成員類別中,或是某命名空間成員函式中的局部類別,則搜尋該命名空間的作用域,直到該類別、外圍類別或函式的定義點;查找會繼續往外圍命名空間進行直到抵達全域作用域。

對於 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 中的相同成員名稱,前提是 AB 的基底類別子物件。(注意,這並不會隱藏繼承格架上任何非 B 基底的額外非虛擬 A 副本中的名稱:此規則僅對虛擬繼承有效。)由 using-declarations 引入的名稱被視為包含該宣告的類別中的名稱。檢查每個基底後,產生的集合必須包含來自相同類型子物件的靜態成員宣告,或來自相同子物件的非靜態成員宣告。 (直到 C++11)
會建構一個查找集(lookup set),由宣告以及發現這些宣告的子物件組成。Using-declarations 會被它們代表的成員取代,而類型宣告(包括 injected-class-names)會被它們代表的類型取代。若 C 是使用該名稱的作用域所屬的類別,則先檢查 C。若 C 中的宣告列表為空,則為其每個直接基底 Bi 建立查找集(若 Bi 有自己的基底,則遞迴應用這些規則)。建立完成後,直接基底的查找集會按如下方式合併到 C 的查找集中:
  • Bi 中的宣告集為空,則捨棄它;
  • 若目前為止 C 建立的查找集為空,則由 Bi 的查找集取代;
  • Bi 查找集中的每個子物件都是已加入 C 查找集之子物件中至少一個的基底,則捨棄 Bi 的查找集;
  • 若已加入 C 查找集的每個子物件都是 Bi 查找集中至少一個子物件的基底,則捨棄 C 的查找集並由 Bi 的查找集取代;
  • 否則,若 BiC 中的宣告集不同,則結果為歧義合併:C 的新查找集具有無效宣告,以及先前合併到 C 和從 Bi 引入的子物件聯集。若此無效查找集稍後被捨棄,則它可能不會導致錯誤;
  • 否則,C 的新查找集具有共享的宣告集,以及先前合併到 C 和從 Bi 引入的子物件聯集。
(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]

[編輯] 參見

English Deutsch 日本語 中文(简体) 中文(繁體)