限定名,是出现在作用域解析操作符 :: 右边的名字(参阅有限定的标识符)。限定名可能代表的是:

  • 类的成员(包括静态和非静态函数、类型和模板等)
  • 命名空间的成员(包括其它的命名空间)
  • 枚举项

:: 左边为空,则查找过程仅会考虑全局命名空间作用域中作出(或通过 using 声明引入到全局命名空间中)的声明。这样一来,即使局部声明隐藏了该名字,也能够访问它:

  1. #include <iostream>
  2. int main() {
  3. struct std{};
  4. std::cout << "fail\n"; // 错误:对 'std' 的无限定查找找到 struct
  5. ::std::cout << "ok\n"; // 正确: ::std 找到命名空间 std
  6. }

在能对 :: 右边的名字进行名字查找之前,必须完成对其左边的名字的查找(除非左边所用的是 decltype 表达式或左边为空)。对左边的名字所进行的查找,根据这个名字左边是否有另一个 :: 可以是有限定或无限定的,但其仅考虑命名空间、类类型、枚举和能特化为类型的模板:

  1. struct A {
  2. static int n;
  3. };
  4. int main() {
  5. int A;
  6. A::n = 42; // 正确:对 :: 左边的 A 的无限定查找忽略变量
  7. A b; // 错误:对 A 的无限定查找找到了变量 A
  8. }

以限定名为声明符时,对同一声明符中随该限定名之后,而非其之前的名字的无限定查找,是在成员的所在类或命名空间的作用域中进行的:

  1. class X { };
  2. constexpr int number = 100;
  3. struct C {
  4. class X { };
  5. static const int number = 50;
  6. static X arr[number];
  7. };
  8. X C::arr[number], brr[number]; // 错误:对 X 的查找找到 ::X 而不是 C::X
  9. C::X C::arr[number], brr[number]; // OK : arr 的大小是 50 , brr 的大小是 100

:: 后跟字符 ~ 再跟着一个标识符(也就是说指定了析构函数或伪析构函数),那么该标识符将在 :: 左边的名字相同的作用域中查找。

  1. struct C { typedef int I; };
  2. typedef int I1, I2;
  3. extern int *p, *q;
  4. struct A { ~A(); };
  5. typedef A AB;
  6. int main() {
  7. p->C::I::~I(); // ~ 之后的名字 I 在 :: 前面的 I 的同一个作用域中查找
  8. //(也就是说,在 C 的作用域中查找,因此查找结果是 C::I )
  9. q->I1::~I2(); // 名字 I2 在 I1 的同一个作用域中查找,
  10. // 也就是说从当前的作用域中查找,因此查找结果是 ::I2
  11. AB x;
  12. x.AB::~AB(); // ~ 之后的名字 AB 在 :: 前面的 AB 的同一个作用域中查找
  13. // 也就是说从当前的作用域中查找,因此查找结果是 ::AB
  14. }


#### 枚举项


若对左边的名字的查找结果是枚举(无论是有作用域还是无作用域),右边名字的查找结果必须是属于该枚举的一个枚举项,否则程序非良构。
(C++11 起)

类成员

若对左边的名字的查找结果是某个类、结构体或联合体的名字,则 :: 右边的名字在该类、结构体或联合体的作用域中进行查找(因此可能找到该类或其基类的成员的声明),但有以下例外情况:

  • 析构函数按如上所述进行查找(即在 :: 左边的名字的作用域中查找)
  • 用户定义转换函数名中的转换类型标识( conversion-type-id ),首先在该类类型的作用域中查找。若未找到,则在当前作用域中查找该名字。
  • 模板实参中使用的名字,在当前作用域中查找(而非在模板名的作用域中查找)
  • using 声明中的名字,还考虑在当前作用域中声明的变量、数据成员、函数或枚举项所隐藏的类或枚举名
本节未完成原因:上述内容的小示例

:: 右边所指名的是和其左边相同的类,则右边的名字表示的是该类的构造函数。这种限定名仅能用在构造函数的声明以及引入继承构造函数using 声明中。在所有忽略函数名的查找过程中(即在查找 :: 左边的名字,或查找详述类型说明符基类说明符中的名字时),则将同样的语法解释成注入类名( injected-class-name ):

  1. struct A { A(); };
  2. struct B : A { B(); };
  3. A::A() { } // A::A 指定的是构造函数,用于声明
  4. B::B() { } // B::B 指定的是构造函数,用于声明
  5. B::A ba; // B::A(在 B 的作用域中查找)指定的是类型 A
  6. A::A a; // 错误:A::A 不是类型
  7.  
  8. struct A::A a2; // 正确:详述类型说明符中的查找是忽略函数的,
  9. // 因此 A::A 在 A 的作用域中查找,于是指定的是类 A
  10. // (亦即其注入类名)

有限定名字查找可用来访问被嵌套声明或被派生类隐藏了的类成员。对有限定的成员函数的调用将不再是虚调用:

  1. struct B { virtual void foo(); };
  2. struct D : B { void foo() override; };
  3. int main()
  4. {
  5. D x;
  6. B& b = x;
  7. b.foo(); // 调用 D::foo(虚调用派发)
  8. b.B::foo(); // 调用 B::foo(静态的调用派发)
  9. }

命名空间的成员

:: 左边的名字代表的是命名空间,或者 :: 左边为空(这种情况其代表全局命名空间),那么 :: 右边的名字就在这个命名空间的作用域中进行查找,但有以下例外:

  • 在模板实参中使用的名字在当前作用域中查找
  1. namespace N {
  2. template<typename T> struct foo {};
  3. struct X {};
  4. }
  5. N::foo<X> x; // 错误:X 查找结果为 ::X 而不是 N::X

命名空间 N 中进行有限定查找时,首先要考虑处于 N 之中的所有声明,以及处于 N内联命名空间成员(并且传递性地包括它们的内联命名空间成员)之中的所有声明。如果这个集合中没有找到任何声明,则再考虑在 NN 的所有传递性的内联命名空间成员中发现的所有using 指令所指名的命名空间之中的声明。这条规则是递归实施的:

  1. int x;
  2. namespace Y {
  3. void f(float);
  4. void h(int);
  5. }
  6. namespace Z {
  7. void h(double);
  8. }
  9. namespace A {
  10. using namespace Y;
  11. void f(int);
  12. void g(int);
  13. int i;
  14. }
  15. namespace B {
  16. using namespace Z;
  17. void f(char);
  18. int i;
  19. }
  20. namespace AB {
  21. using namespace A;
  22. using namespace B;
  23. void g();
  24. }
  25. void h()
  26. {
  27. AB::g(); // 在 AB 中查找,找到了 AB::g 并且选择了 AB::g(void)
  28. // (并未在 A 和 B 中查找)
  29. AB::f(1); // 首先在 AB 中查找,未能找到 f
  30. // 然后再在 A 和 B 中查找
  31. // 找到了 A::f 和 B::f(但并未在 Y 中查找,因而不考虑 Y::f)
  32. // 重载解析选中 A::f(int)
  33. AB::x++; // 首先在 AB 中查找,未能找到 x
  34. // 然后再在 A 和 B 中查找。未能找到 x
  35. // 然后再在 Y 和 Z 中查找。还是没有 x:这是一个错误
  36. AB::i++; // 在 AB 中查找,未能找到 i
  37. // 然后再在 A 和 B 中查找。找到了 A::i 和 B::i:这是一个错误
  38. AB::h(16.8); // 首先在 AB 中查找:未能找到 h
  39. // 然后再在 A 和 B 中查找。未能找到 h
  40. // 然后再在 Y 和 Z 中查找。
  41. // 找到了 Y::h 和 Z::h。重载解析选中 Z::h(double)
  42. }

同一个声明允许被多次找到:

  1. namespace A { int a; }
  2. namespace B { using namespace A; }
  3. namespace D { using A::a; }
  4. namespace BD {
  5. using namespace B;
  6. using namespace D;
  7. }
  8. void g()
  9. {
  10. BD::a++; // OK : 通过 B 和 D 找到同一个 A::a
  11. }
本节未完成原因:3.4.3.2[namespace.qual] 中的剩余内容,尝试缩短其示例