Skip to content

Latest commit

 

History

History
74 lines (58 loc) · 3.86 KB

2018-04-05-adl-in-cpp.md

File metadata and controls

74 lines (58 loc) · 3.86 KB

C++中的 Argument-dependent lookup

date: 2018-4-5

C++中有一项很少被提及,也不怎么被主动使用的特性,就是Argument-dependent lookup(ADL),亦即参数依赖的查找。

所谓Argument-dependent lookup,又称为Koenig lookup,是指在函数调用的过程中,对于 unqualified 的函数名称,有一组规则来找寻这个函数名称所对应的实际函数。除去查找了当前的 scope 和 namespace 后,还会查找实参所在的 namespace 来进一步确认将被调用的是哪个函数。

Example

ADL最常见的应用之一就是std::cout了。参考如下代码:

#include <iostream>
int main()
{
    std::cout << "hello world!"
    return 0;
}

作为C++难得的语法糖之一,<<运算符实际调用的是operator <<(arg1, arg2),因此上方的语句等价于

operator <<(std::cout, "hello world!");

而此处就产生了一个疑问,operator<<并未存在于当前 scope 或者 global namespace 中,如果按照严格的语法规则,应该调用的语句是

std::operator <<(std::cout, "hello world!");

事实上,如上语句不但编译通过了,还工作得非常好,成为了无数 C++ 学习者第一次写下的代码。这其中起到助力的就是ADL 。通过ADL的规则,C++ 在查找当前 scope 和 global namespace 无果后,又查找了std::cout所在的namespace std,在其中找到并成功调用了std::operator <<

Back Fire

C++ 规定了ADL的应用场景,仅限 usual unqualified lookup 没有查找成功时才会使用ADL。看起来似乎只要在当前的 scope 或 namespace 能够找到对应的函数,ADL就不会启用,也不会造成我们调用其它 namespace 的函数,如此我们就可以高枕无忧了?其实不然,考虑下面的代码:

namespace aa {
	struct a_t {};
	void trap(a_t) {
		std::cout << "in namespace aa" << std::endl;
	}
}

void trap(...) {
	std::cout << "in namespace bb" << std::endl;
}

int main()
{
	trap(42);
	trap(aa::a_t());

	return 0;
}

输出的结果为:

in namespace bb
in namespace aa

显然,如上的例子看起来像是ADL在某些情况下被触发了,造成我们的代码调用了非预期的函数。

在 global namespace 中,我们有函数trap,参数不定。在namespace aa中,我们有同名函数trap,接受类型为a_t的参数。当传入实参满足了ADL的条件,namespace aa中的trap函数被“意外”调用了。当然,这样的场景条件比较严苛。首先需要两个namespace中有同名函数,其次需要这两个同名函数的参数是可“兼容”的,即同样的参数对两个函数都合法。实际上能满足这样条件的参数定义也只有可变参数了。

问题是,上述的例子是否真的是“非预期”的?

事实上 C++ 标准对于上面的样例有着明确的定义。例中是场景实际上是两个函数名称相同参数不同的重载情况,编译器在此需要判断的是,对于这两个重载函数,应该选用哪一个作为实际被调用的函数。

这两个候选函数,一个的参数是有明确类型的,另一个是variadic arguments。对于 C 时代沿袭下来的variadic arguments,文档中已有明确说明参数为variadic arguments的候选函数在重载选择中优先级是最低的。

variadic parameters have the lowest rank for the purpose of overload resolution

可见,在上述的例子中,并非ADL被非预期的调用了。而是 C++ 认为这两个同名函数是一种重载,因为variadic arguments的重载优先级最低,所以优先调用了另一版本的重载,给我们造成了一种非预期调用的错觉。

References