命名空间
变体
操作

非限定名称查找

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
表达式
替代表示
字面量
布尔值 - 整数 - 浮点数
字符 - 字符串 - nullptr (C++11)
用户定义 (C++11)
实用程序
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
内存分配
特定于类的函数属性
explicit (C++11)
static

特殊成员函数
模板
其他
 
 

对于非限定名称,即未出现在作用域解析运算符::右侧的名称,名称查找会按如下所述检查作用域,直到找到至少一个声明,查找此时停止,不再检查其他作用域。(注意:某些上下文中的查找会跳过一些声明,例如,在::左侧使用的名称查找会忽略函数、变量和枚举器声明,在用作基类说明符的名称查找会忽略所有非类型声明)

为了进行非限定名称查找,由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
    }
}

[编辑] 类定义

对于在类定义中的任何地方使用的名称(包括基类说明符和嵌套类定义),除了在成员函数体内部、成员函数的默认参数、成员函数的异常说明或默认成员初始化器内部,该成员可能属于嵌套类,其定义在封闭类的体内,会搜索以下作用域

a) 使用该名称的类的体,直到使用该名称的位置,
b) 其基类(如果存在)的整个主体,如果未找到声明,则递归至它们的基类,
c) 如果此类是 嵌套 的,则从包含类的定义处到此类定义处的所有主体,以及包含类的基类(如果存在)的整个主体,
d) 如果此类是 局部 的,或者嵌套在一个局部类中,则搜索定义此类的块作用域,直到定义点,
e) 如果此类是命名空间的成员,或者嵌套在一个是命名空间成员的类中,或者是在命名空间成员函数中定义的局部类,则搜索命名空间的作用域,直到找到类、包含类或函数的定义;查找将继续搜索包含该命名空间的命名空间,直到全局作用域。

对于 友元 声明,查找以确定它是否引用了先前声明的实体,与上述方法相同,但它在最内层包含命名空间后停止。

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
    }
}
无论哪种方式,在检查从其派生类的基类时,将遵循以下规则,有时称为 虚拟继承中的支配
如果 AB 的基类子对象,则在子对象 B 中找到的成员名称将隐藏在任何子对象 A 中的相同成员名称。(请注意,这不会隐藏在继承层次结构中任何其他非虚拟 A 副本中的名称,这些副本不是 B 的基类:此规则仅对虚拟继承有影响。)使用声明引入的名称将被视为包含该声明的类中的名称。在检查每个基类后,结果集必须包含来自相同类型的子对象的静态成员声明,或者包含来自同一子对象的非静态成员声明。 (直到 C++11)
将构造一个 查找集,它包含声明和找到这些声明的子对象。使用声明将被它们表示的成员和类型声明替换,包括注入类名将被它们表示的类型替换。如果 C 是在其中使用该名称的类,则首先检查 C。如果 C 中的声明列表为空,则为其每个直接基类 Bi(如果 Bi 有自己的基类,则递归应用这些规则)构建查找集。构建完成后,将直接基类的查找集合并到 C 中的查找集中,如下所示:
  • 如果 Bi 中的声明集为空,则将其丢弃;
  • 如果到目前为止构建的 C 的查找集为空,则将其替换为 Bi 的查找集;
  • 如果 Bi 的查找集中的每个子对象都是已添加到 C 的查找集中的至少一个子对象的基类,则丢弃 Bi 的查找集;
  • 如果已添加到 C 的查找集中的每个子对象都是 Bi 的查找集中至少一个子对象的基类,则丢弃 C 的查找集,并将其替换为 Bi 的查找集;
  • 否则,如果 Bi 中的声明集与 C 中的声明集不同,则结果将是模糊的合并: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 
}

[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 在命名空间作用域中使用的任何非限定名称
首先在该作用域中查找
用于在该命名空间之外定义命名空间变量成员的非限定名称
命名空间首先在该命名空间中查找
命名空间

[编辑] 参考资料

  • 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]

[编辑] 另请参阅