Argument-dependent lookup
实参依赖查找(ADL),又称柯尼希查找 [1] ,是一组用于在 函数调用表达式 中查找非限定函数名的规则集,包括对 重载运算符 的隐式函数调用。除了常规 非限定名称查找 所考虑的作用域和命名空间外,这些函数名还会在其参数所属的命名空间中进行查找。
实参依赖查找使得使用其他命名空间中定义的运算符成为可能。示例:
#include <iostream> int main() { std::cout << "Test\n"; // 全局命名空间中无 operator<<,但 ADL // 会检查 std 命名空间,因为左实参属于 // std 命名空间,从而找到 std::operator<<(std::ostream&, const char*) operator<<(std::cout, "Test\n"); // 同上,使用函数调用表示法 // 然而: std::cout << endl; // 错误:“endl”未在此命名空间声明 // 这不是对 endl() 的函数调用,故 ADL 不适用 endl(std::cout); // 正确:此为函数调用,ADL 检查 std 命名空间 // 因为 endl 的实参属于 std,从而找到 std::endl (endl)(std::cout); // 错误:“endl”未在此命名空间声明 // 子表达式 (endl) 不是非限定标识符 }
目录 |
详情
首先,若由常规 非限定查找 产生的查找集合包含以下任意元素,则不会进行实参依赖查找:
否则,对于函数调用表达式中的每个实参,将检查其类型以确定它将在查找过程中添加的 关联命名空间和类集合 。
T
的指针或指向
T
数组的指针类型的参数,将检查类型
T
并将其关联的类和命名空间集合添加到该集合中。
X
成员函数
F
的指针类型参数,将检查函数参数类型、函数返回类型以及类
X
,并将其关联的类和命名空间集合添加到该集合中。
X
的数据成员
T
的指针类型参数,将同时检查成员类型和类型
X
,并将它们关联的类和命名空间集合添加到当前集合中。
- 此外,若该重载集通过 模板标识符 指定,则其所有类型模板参数与模板模板参数(非常量模板参数除外)都会被检查,其关联的类与命名空间集合也会被加入该集合。
|
如果关联类和命名空间集合中的任何命名空间是 内联命名空间 ,则其外围命名空间也会被加入该集合。 如果关联类和命名空间集合中的任何命名空间直接包含内联命名空间,则该内联命名空间会被加入集合。 |
(since C++11) |
在确定关联的类和命名空间集合后,该集合中类内发现的所有声明将在后续ADL处理过程中被舍弃,但如下文第2点所述,命名空间作用域的友元函数和函数模板除外。
通过常规 非限定查找 找到的声明集合,与通过ADL在关联集合的所有元素中找到的声明集合,将按照以下特殊规则进行合并:
注释
由于实参依赖查找,在类同一命名空间中定义的非成员函数和非成员运算符被视为该类公共接口的组成部分(若通过ADL查找到) [2] 。
ADL 是泛型代码中交换两个对象既定惯用法的原因:
using
std::
swap
;
swap
(
obj1, obj2
)
;
,因为直接调用
std::
swap
(
obj1, obj2
)
不会考虑可能在
obj1
或
obj2
类型所在命名空间中定义的用户自定义
swap()
函数,而仅调用非限定版本的
swap
(
obj1, obj2
)
在未提供用户自定义重载时将无法调用任何函数。特别地,
std::iter_swap
及所有其他标准库算法在处理
Swappable
类型时均采用此方法。
名称查找规则使得在全局或用户定义命名空间中声明作用于
std
命名空间类型的运算符变得不切实际,例如为
std::vector
或
std::pair
自定义
operator
>>
或
operator
+
(除非 vector/pair 的元素类型是用户定义类型,这会将其命名空间加入 ADL)。此类运算符在模板实例化时不会被查找,例如标准库算法中。详见
依赖名
获取更多信息。
ADL 能够找到完全在类或类模板内部定义的 友元函数 (通常是重载运算符),即使该函数从未在命名空间级别进行过声明。
template<typename T> struct number { number(int); friend number gcd(number x, number y) { return 0; }; // 在类模板内部定义 // 的友元函数 }; // 除非提供匹配的声明,否则gcd是该命名空间中 // 不可见的成员(仅能通过ADL访问) void g() { number<double> a(3), b(4); a = gcd(a, b); // 因number<double>是关联类而找到gcd, // 使其在其命名空间(全局作用域)中可见 // b = gcd(3, 4); // 错误;gcd不可见 }
|
尽管即使普通查找未找到任何结果时函数调用仍可通过ADL解析,但对带有显式指定模板实参的 函数模板 进行函数调用时,要求通过普通查找能找到该模板的声明(否则遇到未知名称后接小于号将构成语法错误)。 namespace N1 { struct S {}; template<int X> void f(S); } namespace N2 { template<class T> void f(T t); } void g(N1::S s) { f<3>(s); // C++20前为语法错误(非限定查找找不到f) N1::f<3>(s); // 正确:限定查找找到模板'f' N2::f<3>(s); // 错误:N2::f不接受常量参数 // N1::f未被查找,因为ADL仅对非限定名称生效 using N2::f; f<3>(s); // 正确:非限定查找现在找到N2::f // 随后ADL因该名称为非限定而生效,并找到N1::f } |
(until C++20) |
在以下语境中仅发生 ADL 查找(即仅在关联命名空间中查找):
|
(since C++11) |
- 从模板实例化点进行的 依赖名称查找 。
|
(since C++17) |
示例
|
本节内容不完整
原因:需要更多示例 |
示例来自 http://www.gotw.ca/gotw/030.htm
namespace A { struct X; struct Y; void f(int); void g(X); } namespace B { void f(int i) { f(i); // 调用 B::f(无限递归) } void g(A::X x) { g(x); // 错误:在 B::g(普通查找)和 A::g(实参依赖查找)之间存在歧义 } void h(A::Y y) { h(y); // 调用 B::h(无限递归):ADL 检查了 A 命名空间 // 但未找到 A::h,因此仅使用普通查找找到的 B::h } }
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 33 | C++98 |
若用于查找的实参是一组重载函数或函数模板的地址,
则其关联命名空间或类未作规定 |
已作规定 |
| CWG 90 | C++98 |
嵌套非联合类的关联类不包含其外围类,
但嵌套联合类与其外围类关联 |
非联合类也建立关联 |
| CWG 239 | C++98 |
通过普通非限定查找找到的块作用域函数声明
未能阻止ADL查找 |
除
using
声明外
不考虑ADL |
| CWG 997 | C++98 |
在确定函数模板的关联类和命名空间时,
未考虑依赖参数类型和返回类型 |
已纳入考虑 |
| CWG 1690 |
C++98
C++11 |
ADL无法找到返回的lambda表达式(C++11)
或局部类类型对象(C++98) |
可被找到 |
| CWG 1691 | C++11 | ADL对不透明枚举声明存在意外行为 | 已修复 |
| CWG 1692 | C++98 |
双重嵌套类没有关联命名空间
(其外围类不属于任何命名空间) |
关联命名空间扩展至
最内层外围命名空间 |
| CWG 2857 | C++98 | 不完整类类型的关联类包含其基类 | 不包含基类 |
参见
外部链接
|