Qualified name lookup
一个
限定
名称是指出现在作用域解析运算符
::
右侧的名称(另见
限定标识符
)。
限定名称可以引用
- 类成员(包括静态和非静态函数、类型、模板等),
- 命名空间成员(包括其他命名空间),
- 枚举项。
如果
::
左侧没有任何内容,查找操作仅考虑
全局命名空间作用域
中的声明。这使得即使这些名称被局部声明所隐藏,仍然可以引用它们:
在能够对
::
右侧名称执行名称查找之前,必须首先完成其左侧名称的查找(除非使用
decltype
表达式,或左侧无内容)。该查找(根据该名称左侧是否存在另一个
::
,可能是有限定或无限定的)仅考虑命名空间、类类型、枚举以及其特化为类型的模板。如果在左侧找到的名称不指向命名空间、类、枚举或依赖类型,则程序非良构:
struct A { static int n; }; int main() { int A; A::n = 42; // 正确:在 :: 左侧的 A 非限定查找会忽略该变量 A b; // 错误:A 的非限定查找找到了变量 A } template<int> struct B : A {}; namespace N { template<int> void B(); int f() { return B<0>::n; // 错误:N::B<0> 不是类型 } }
当限定名称被用作 声明符 时,对于同一声明符中出现在该限定名称之后(但不在其之前)的名称,将在该成员所属类或命名空间的作用域内执行 非限定查找 :
class X {}; constexpr int number = 100; struct C { class X {}; static const int number = 50; static X arr[number]; }; X C::arr[number], brr[number]; // 错误:对 X 的查找找到的是 ::X,而非 C::X C::X C::arr[number], brr[number]; // 正确:arr 的大小为 50,brr 的大小为 100
如果
::
后跟字符
~
,而该字符后面又跟着一个标识符(即指定了析构函数或伪析构函数),则该标识符将在与
::
左侧名称相同的作用域中进行查找。
struct C { typedef int I; }; typedef int I1, I2; extern int *p, *q; struct A { ~A(); }; typedef A AB; int main() { p->C::I::~I(); // ~ 后的名称 I 在与 :: 前的 I 相同的作用域中查找 //(即在 C 的作用域内,因此找到 C::I) q->I1::~I2(); // 名称 I2 在与 I1 相同的作用域中查找 //(即从当前作用域,因此找到 ::I2) AB x; x.AB::~AB(); // ~ 后的名称 AB 在与 :: 前的 AB 相同的作用域中查找 //(即从当前作用域,因此找到 ::AB) }
枚举项若对左侧名称的查找得到一个 枚举 (无论是作用域枚举还是非作用域枚举),则对右侧的查找必须得到属于该枚举的枚举项,否则程序非良构。 |
(自 C++11 起) |
类成员
如果左侧名称查找得到类/结构体或联合体名称,则
::
右侧的名称将在该类的范围内进行查找(因此可能找到该类或其基类的成员声明),但存在以下例外情况:
- 析构函数的查找方式如上所述(在 :: 左侧名称的作用域中查找)。
- 用户定义转换 函数名中的转换类型标识符首先在类作用域中查找。若未找到,则在当前作用域中继续查找该名称。
- 模板实参中使用的名称在当前作用域中查找(而非在模板名称的作用域中查找)。
- using声明 中的名称也会考虑被同一作用域中声明的变量、数据成员、函数或枚举项名称所隐藏的类/枚举名称。
|
本节内容不完整
原因:缺少上述内容的微型示例 |
如果
::
右侧的名称与左侧的类名相同,该名称指代该类的
构造函数
。此类限定名称仅能用于构造函数的声明以及在
using声明
中声明
继承构造函数
。在忽略函数名的查找中(即当查找
::
左侧名称时,或在
详细类型说明符
或
基类说明符
中查找名称时),相同语法会解析为注入类名:
struct A { A(); }; struct B : A { B(); }; A::A() {} // A::A 命名构造函数,用于声明 B::B() {} // B::B 命名构造函数,用于声明 B::A ba; // B::A 命名类型 A(在 B 的作用域中查找) A::A a; // 错误:A::A 不命名类型 struct A::A a2; // 正确:详细类型说明符中的查找忽略函数 // 因此 A::A 仅命名从 A 作用域内可见的类 A //(即注入类名)
限定名称查找可用于访问被嵌套声明或派生类隐藏的类成员。对限定成员函数的调用永远不会是虚函数:
struct B { virtual void foo(); }; struct D : B { void foo() override; }; int main() { D x; B& b = x; b.foo(); // 调用 D::foo(动态绑定) b.B::foo(); // 调用 B::foo(静态绑定) }
命名空间成员
如果
::
左侧的名称指向某个命名空间,或者
::
左侧为空(此时指向全局命名空间),则出现在
::
右侧的名称将在该命名空间的作用域中进行查找,但以下情况除外:
- 模板参数中使用的名称在当前作用域内查找:
namespace N { template<typename T> struct foo {}; struct X {}; } N::foo<X> x; // 错误:X 被查找为 ::X,而非 N::X
在命名空间
N
作用域内的限定查找首先考虑位于
N
中的所有声明,以及位于
N
的
内联命名空间成员
中的所有声明(并递归地包括它们的内联命名空间成员)。如果该集合中没有声明,则会考虑在
N
及其所有递归内联命名空间成员中通过
using 指令
引入的所有命名空间中的声明。这些规则会递归应用:
int x; namespace Y { void f(float); void h(int); } namespace Z { void h(double); } namespace A { using namespace Y; void f(int); void g(int); int i; } namespace B { using namespace Z; void f(char); int i; } namespace AB { using namespace A; using namespace B; void g(); } void h() { AB::g(); // 首先搜索AB,通过查找找到AB::g并选择AB::g(void) //(不搜索A和B) AB::f(1); // 首先搜索AB,未找到f // 然后搜索A和B // 通过查找找到A::f和B::f //(不搜索Y,因此不考虑Y::f) // 重载决议选择A::f(int) AB::x++; // 首先搜索AB,未找到x // 然后搜索A和B,未找到x // 接着搜索Y和Z,仍未找到x:此处产生错误 AB::i++; // 搜索AB,未找到i // 然后搜索A和B,通过查找找到A::i和B::i:此处产生歧义错误 AB::h(16.8); // 首先搜索AB,未找到h // 然后搜索A和B,未找到h // 接着搜索Y和Z // 查找找到Y::h和Z::h,重载决议选择Z::h(double) }
允许同一个声明被多次找到:
namespace A { int a; } namespace B { using namespace A; } namespace D { using A::a; } namespace BD { using namespace B; using namespace D; } void g() { BD::a++; // 正确:通过 B 和 D 都能找到同一个 A::a }
|
本节内容不完整
原因:N4861 6.5.3.2[namespace.qual]的剩余部分,尝试精简其示例 |
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 已发布行为 | 正确行为 |
|---|---|---|---|
| CWG 215 | C++98 |
::
前的名称必须是类名或命名空间名,
因此不允许在此处使用模板参数 |
该名称必须指定类、
命名空间或依赖类型 |
| CWG 318 | C++98 |
若
::
右侧名称与左侧类名相同,
该限定名称始终被视为该类的构造函数 |
仅在可接受时命名构造函数
(例如不在详细类型说明符中) |