参数依赖查找(Argument-dependent lookup),又称 ADL 或 Koenig 查找,是一组于函数调用表达式查找非限定函数名的规则,包含对重载运算符的隐式函数调用。在通常非限定名称查找所考虑的作用域和命名空间之外,还在其参数的命名空间中查找这些函数。
参数依赖查找使使用定义于不同命名空间的运算符可行。例如:
1 #include <iostream> 2 int main() 3 { 4 std::cout << "Test "; // 全局命名空间无 operator<< ,但 ADL 检验 std 命名空间, 5 // 因为左参数在 std 命名空间中 6 // 并找到 std::operator<<(std::ostream&, const char*) 7 operator<<(std::cout, "Test "); // 同上,用函数调用记法 8 9 // 然而, 10 std::cout << endl; // 错误: 'endl' 不声明于此命名空间。 11 // 此非对 endl() 的函数调用,故不应用 ADL 12 13 endl(std::cout); // OK :这是函数调用: ADL 检验 std 命名空间, 14 // 因为 endl 的参数在 std ,并找到 std::endl 15 16 (endl)(std::cout); // 错误: 'endl' 不声明于此命名空间。 17 // 子表达式 (endl) 不是函数调用表达式 18 }
细节
首先,若通常非限定查找所生成的集合含有下列任何内容,则不考虑参数依赖查找:
否则,对于每个函数调用表达式中的参数,检验其类型,以确定它将添加到查找的命名空间与类的关联集。
若类与命名空间的关联集中的任何命名空间是内联命名空间,则添加其外围命名空间到集合。
若类与命名空间的关联集中的任何命名空间直接含有内联命名空间,则添加该内联命名空间到集合。
在确定命名空间与类的关联集后,为了进一步的 ADL 处理,忽略此集中所有于类中找到的声明,除了命名空间作用域的友元函数及函数模板,陈述于后述点 2 。
以下列特殊规则,合并普通非限定查找找到的声明集合,与在 ADL 所生成关联集的所有元素中找到的声明集合
注意
因为参数依赖查找,定义于相同命名空间的非成员函数和非成员运算符被认为是该类公开接口的一部分(若它们为 ADL 所找到)[1]。
ADL 是为于泛型代码交换二个对象而建立的手法的背后理由:
using std::swap; swap(obj1, obj2);
因为直接调用 std::swap(obj1, obj2) 不会考虑用户定义的 swap() 函数,它可能定义于与 obj1 或 obj2 类型之定义相同的空间,而仅调用非限定的 swap(obj1, obj2) 会无法调用任何函数,若不提供用户定义重载。特别是 std::iter_swap 与所有其他标准库算法在处理可交换 (Swappable
) 类型时使用此手段。
名称查找规则使得在来自 std 命名空间的类型上声明运算符于全局或用户定义命名空间,例如对于 std::vector 或 std::pair 的自定义 operator+ 或 operator>> 不适于实践(除非 vector/pair 的元素类型是用户定义类型,这会添加其命名空间到 ADL )。这种运算符不会从诸如标准库算法的模板实例化查找。进一步细节见依赖名。
ADL 能找到全体定义于类或类模板内的友元函数(典型地是重载的运算符),即使它完全不在命名空间层次声明。
1 template<typename T> 2 struct number 3 { 4 number(int); 5 friend number gcd(number x, number y) { return 0; }; // 类模板内的定义 6 }; 7 // 除非提供匹配声明,否则 gcd 是此命名空间的不可见成员(除非通过 ADL ) 8 void g() { 9 number<double> a(3), b(4); 10 a = gcd(a,b); // 找到 gcd ,因为 number<double> 是关联类, 11 // 令 gcd 于其命名空间(全局命名空间)可见 12 // b = gcd(3,4); // 错误: gcd 不可见 13 }
尽管即使普通查找找不到结果,函数调用也能通过 ADL 解决,对带显示指定模板实参的函数模板调用还是要求有普通查找所能找到的模板声明(否则,它会是遇到未知名称后随小于号的语法错误)
1 namespace N1 { 2 struct S {}; 3 template<int X> void f(S); 4 } 5 namespace N2 { 6 template<class T> void f(T t); 7 } 8 void g(N1::S s) { 9 f<3>(s); // 语法错误(无限定查找找不到 f ) 10 N1::f<3>(s); // OK ,有限定查找找到模板 'f' 11 N2::f<3>(s); // 错误: N2::f 不接收非类型模板形参 12 // N1::f 不能被找到,因为 ADL 仅适用于非限定名 13 using N2::f; 14 f<3>(s); // OK :无限定查找现在找到 N2::f 然后 ADL 表态, 15 // 因为此名无限定并找到 N1::f 16 }
下列语境发生仅 ADL 的查找(即仅于关联的命名空间查找):
示例
2 struct X; 3 struct Y; 4 void f(int); 5 void g(X); 6 } 7 8 namespace B { 9 void f(int i) { 10 f(i); // 调用 B::f (无限递归) 11 } 12 void g(A::X x) { 13 g(x); // 错误:在 B::g (普通查找)与 A::g (参数依赖查找)间歧义 14 } 15 void h(A::Y y) { 16 h(y); // 调用 B::h (无限递归): ADL 检验 A 命名空间 17 // 但找不到 A::h ,故只用来自通常查找的 B::h 18 } 19 }