热门问题
时间线
聊天
视角
依赖于实参的名字查找
来自维基百科,自由的百科全书
Remove ads
依赖于实参的名字查找是C++程序设计语言中的名字查找机制之一。英文为“argument-dependent lookup”,因此缩写为ADL。[1]ADL依据函数调用中的实参的数据类型查找未限定(unqualified)的函数名(或者函数模板名)。这也被称作“克尼格查找”(Koenig lookup),虽然安德鲁·克尼格并不是它的发明者。[2]
语义
如果通常的未限定(unqualified)名字查找所产生的候选集包括下述情形,则不会启动依赖于实参的名字查找:
- 类成员声明(此种情形仅指普通的类成员函数,不指类成员运算符函数)
- 块作用域内的函数的声明,不含(using-declaration)
- 任何不是函数或者函数模板的声明(例如函数对象或者另一个变量其名字与被查询的函数名字冲突)
函数调用表达式的每个实参的类型用于确定命名空间与类的相关集合(associated set of namespaces and classes)并用于函数名字查找:[3]
- 基本类型(fundamental type)实参的命名空间与类的相关集合为空。
- 类类型(class type,指struct, class, union类型), 相关集合包括
- 类类型自身
- 如果类类型是另一个类的成员,则那个包含了类类型的类;
- 该类型的所有的直接或间接基类;
- 该类类型的所有相关类的最内部包含命名空间。
- 如果实参是类模板特化后得到的类型,除了应用类类型规则,还要包括下述规则提及的类型的相关命名空间与类的集合:
- 对于枚举类型的实参,枚举类型所在的命名空间;如果枚举类型是一个类的成员类型,则该类加入相关集合。
- 如果实参是类型T的指针或者是类型T的数组的指针,则类型T的相关集合被加入该实参类型的相关集合。
- 如果实参是函数类型,那么该函数的形参类型与函数返回值的类型的相关集合被加入到该实参类型的相关集合。
- 如果实参是类X的成员函数F的类成员函数指针,那么该成员函数的形参类型、该成员函数返回值的类型、该成员函数所属类X的相关集合都被加入到该实参类型的相关集合。
- 如果实参是类X的数据成员T的类数据成员指针,那么该成员类型、该数据成员所属类X的相关集合都被加入到该实参类型的相关集合。
- 如果实参是一套重载函数(或者函数模板)的名字或者取地址表达式(address-of expression),那么重载集中的每个函数的相关集合都被加入到该实参类型的相关集合。
- 此外,如果重载集是template-id (模板名字与模板实参), 则所有的类型模板实参与模板的模板实参(不含非类型模板实参)的相关集合都被加入到该实参类型的相关集合。
- 如果相关集合中的任何命名空间是内联命名空间(inline namespace), 则包含它的命名空间被增加到相关集合中。
- 如果相关集合中的一个命名空间直接包含了内联命名空间,则内联命名空间被增加到相关集合中。
- 相关命名空间中的using-directives被忽略
通常的未限定查找发现的结果与ADL查找发现的结果应该合并,并遵从如下特别规则:
- 通过ADL查找到的相关类内的友函数与函数模版是可见的
- 所有的名字,如果不是函数或者函数模版,将被忽略(不与变量名字冲突)
例子
一个简单示例:
namespace NS {
class A {};
void f(A *&, int) {}
}
int main()
{
NS::A *a;
f(a, 0); //calls NS::f
(f)(a,0); //error: NS::f not considered; parentheses prevent argument-dependent lookup
}
更为复杂、精致的例子:
namespace N2 { struct DS; }
namespace N1 {
struct S {
void operator+(S) {}
};
template<int X> void f(S) {}
void fDS(N2::DS* v) {}
}
namespace N2 {
struct DS :N1::S {};
template<class T> void f(T t) {}
}
void g() {
N2::DS sv;
fDS(&sv); // sv的类型N2::DS的基类型N1::S所在的命名空间N1的函数N1::fDS
sv+sv; // 调用N1::S::operator+(S)运算符成员函数
}
另一个示例:
namespace N1 {
struct S {};
template<int X> void f(S) {}
void bar(S) {}
}
namespace N2 {
template<class T> void f(T t) {}
}
void g(N1::S s) {
bar(s); // lookup N1::bar
// f<3>(s); // Syntax error: unqualified lookup finds no f, so it understands as arithematic expression " f < 3 "
N1::f<3>(s); // OK, qualified lookup finds the template 'f'
// N2::f<3>(s); // Error: N2::f does not take a non-type parameter
// N1::f is not looked up because ADL only works with unqualified names
using N2::f;
f<3>(s); // OK: Unqualified lookup now finds N2::f
// then ADL kicks in because this name is unqualified and finds N1::f
}
关于内联命名空间的示例:
namespace ADL{
inline namespace v101 { // 下述代码中,命名空间名字v101都可以忽略
class foo {
public:
class bar { };
};
}
void func1(foo::bar v) {
int i = 101;
}
}
int main()
{
ADL::foo::bar v1;
func1(v1); // lookup to ADL::func1
}
Remove ads
接口
ADL能查询到的函数被认为是类的接口之一。标准模板库的几个std
命名空间的算法使用未限定的swap
调用。使用时,如果名字查找没找到其它结果,则使用std::swap
函数;如果这些算法使用第三方类,如Foo
,在另一个命名空间中发现包含了swap(Foo&, Foo&)
, 则重载版的swap
被优先使用。
C++标准程式库常见模式是用ADL查找程序声明的重载运算符。例如,下述程序如果没有ADL将无法编译通过:[2]
#include<iostream>
int main()
{
string hello = "Hello World, where did operator<<() come from?";
std::cout << hello << std::endl;
}
运算符<<
等价于调用函数operator<<
,但没有给出std
限定符。通过ADL查找到函数std::ostream& std::operator<<(std::ostream&, const char*)
。
Remove ads
批评
ADL使得类之外定义的函数就如同类的接口一样被调用,这使得命名空间不是过于严格。例如,C++标准模板库使用未限定的swap
调用,允许用户定义自己的swap
供标准模板库的算法使用。换句话说,
std::swap(a, b);
的效果可能相同与或不同于
using std::swap;
swap(a, b);
(其中a
与b
的类型是N::A
)。因为如果N::swap(N::A&, N::A&)
存在,上述第二段代码将调用它,而上述第一段代码将不调用它。
参考文献
外部链接
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads