名称查找中的几个概念
限定作用符 ::
限定名值指出现在::(限定作用符)右侧的名字,他可以是
- 类
- 命名空间
- 枚举
无限定作用域及,没有限定作用符在左侧的名字
有限定名查找,无限定名查找的一些规则
- 如果::限定作用符左侧留空,只会在全局命名空间查找;
- 存在多级限定作用符级联时,只有对左侧名查找完了,再在找到的作用域里对右侧进行查找;有限定和无限定和可以混用
- 对::左侧的符号进行查找,会忽略变量、函数、枚举项的声明
- 当在声明符中使用限定名,该声明符中的非限定名查找在限定名的类或者命名空间中查找
- 无限定查找,查找之前声明的部分,如果存在命名空间嵌套则会一级一级往外查找
- 友元声明的无限定名查找(非模板实参),先在友元的限定域里面查找,再在友元的声明所在域查找
- 模板非待决名,立即绑定,并且模板的非限定名查找只可见模板定义点前的命名
- 如果某个基类取决于某个模板形参,那么无限定名字查找不会检查它的作用域
- 非限定名的ADL
struct A
{
static int n;
};
int n = 1; // 声明
const int DD = 100;
class N{
static const int DD = 50;
static int S[DD];
int m = 2;
namespace Y
{
int x = n; // 规则5 找到 ::n
int y = m; // 规则5,找到 ::N::m
}
};
int N::S[DD]{};// 规则4,DD为N::DD,size 50
struct A
{
typedef int AT;
void f1(AT);
void f2(float);
template<class T>
void f3();
void f4(S<AT>);
};
// 这个类为 f1,f2 和 f3 授予友元关系
struct B
{
typedef char AT;
typedef float BT;
friend void A::f1(AT); // 规则6 对 AT 的查找找到的是 A::AT(在 A 中找到 AT)
friend void A::f2(BT); // 规则6 对 BT 的查找找到的是 B::BT(在 A 中找不到 AT)
friend void A::f3<AT>(); // 规则6 对 AT 的查找找到的是 B::AT (不在 A 中进行查找,
// 因为 AT 在声明符中的标识符 A::f3<AT> 中)
};
// 这个类模板为 f4 授予友元关系
template<class AT>
struct C
{
friend void A::f4(S<AT>); //规则6 对 AT 的查找找到的是 A::AT
// (AT 不在声明符中的标识符 A::f4 中)
};
void f(char); // f 的第一个声明
template<class T>
void g(T t)
{
f(1); // 规则7 非待决名:名字查找找到了 ::f(char) 并在此时绑定
f(t); // 待决名:查找推迟
// dd++; // 非待决名:名字查找未找到声明
}
void f(int); // 模板不可见
double dd;
void h()
{
g(32); // 实例化 g<int>,此处
// 对 'f' 的第二次使用
// 进行了查找仅找到了 ::f(char)
// 然后重载解析选择了 ::f(char)
// 这两次调用了 f(char),规则7 ,不可见f(int)
}
typedef double A;
template<class T>
class B
{
typedef int A;
};
template<class T>
struct X : B<T>
{
A a; // 规则8 对 A 的查找找到了 ::A (double),而不是 B<T>::A
};
int main()
{
struct std {};
::std::cout << "\n"; // 规则1,忽略std{}, 规则2 多级限定
int A;
A b; // 查找,找到了变量 A,报错
A::n = 42; // 规则3, A是无限定名,忽略变量A的定义,n是限定名
}
ADL
ADL拉出来单列,稍微特殊一点,相比非限定名的其他查找叫ordinary lookup(规则5逐层往外查找)
参数依赖查找,顾名思义,根据参数查找函数实现,是一种对无限定名的函数调用附加查找规则。
#include <iostream>
int main()
{
std::cout << "测试\n"; // 全局命名空间中没有 operator<<,但 ADL 检验 std 命名空间,
// 因为左实参在 std 命名空间中
// 并找到 std::operator<<(std::ostream&, const char*)
operator<<(std::cout, "测试\n"); // 同上,用函数调用记法
// 然而,
std::cout << endl; // 错误:'endl' 未在此命名空间中声明。
// 这不是对 endl() 的函数调用,所以不适用 ADL
endl(std::cout); // OK:这是函数调用:ADL 检验 std 命名空间,
// 因为 endl 的实参在 std 中,并找到了 std::endl
(endl)(std::cout); // 错误:'endl' 未在此命名空间声明。
// 子表达式 (endl) 不是函数调用表达式
}
因为规则7的缘由,没有ADL,模板的适用范围会极大受限(其实ADL用的最多的还是模板,具体实际使用案例可参照nlohmann json 库,其中大量使用的to_json 和 from_json 函数,原理就是ADL,通过ADL查找找到对应的用户自定义实现的序列反序列函数。)
template<typename T>
T max (T a, T b) {
return b < a ? a : b;
}
namespace BigMath {
class BigNumber {
...
};
bool operator < (BigNumber const&, BigNumber const&); //对模板 MAX 不可见,若无ADL
...
}
using BigMath::BigNumber;
void g (BigNumber const& a, BigNumber const& b) {
...
BigNumber x = ::max(a,b);
...
}
忽略ADL的几种情况
如果未限定名查找所产生的候选集存在下述情形,则不会启动依赖于实参的名字查找:
- 成员函数声明
class MyClass {
public:
void foo(int) {
// 不考虑参数类型的命名空间,直接在类的作用域中查找
std::cout << "MyClass::foo" << std::endl;
}
};
int main() {
NS1::A a;
MyClass obj;
obj.foo(a); // 不会触发ADL,直接在 MyClass 的作用域中查找 foo
}
- 块作用域中的函数声明(非using-declaration)
namespace NS1 {
void foo() {
std::cout << "NS1::foo" << std::endl;
}
}
int main() {
void foo(); // 在 main 函数作用域内声明一个函数
foo(); // 不会触发ADL,直接在 main 函数作用域内查找 foo
}
- 非函数、函数模板的声明
namespace NS1 {
struct MyStruct {
void operator()() {
std::cout << "MyStruct operator()" << std::endl;
}
};
}
int main() {
NS1::MyStruct foo; // 函数对象
foo(); // 不会触发ADL,直接在当前作用域内查找 foo
}