[C++] 依赖于实参的名字查找(ADL)

陪她去流浪 桃子 2018年03月28日 阅读次数:3580

概念

依赖于实参的名字查找是C++中名字查找的机制之一。英文为:Argument-Dependent name lookup,ADL。 它被用来根据函数调用中的实参的数据类型查找**未限定(unqualified)**的函数名。 它还有其它的名字:参数依赖查找(感觉翻译得不好),克尼格查找(Koenig lookup)。

几个例子

第1个例子

上面这段话可以有点不好理解,不够直观。不防举个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace my {
    class A {};
    void f(const A&){}
}

int main()
{
    my::A a;
    f(a);
}

亲爱的朋友们,告诉我你的答案:上面的代码编译能通过么?

如果你的答案是,那么恭喜,答对了!

答案不是的那些朋友,他们的理由应该是:f(a); 语句找不到全局f(const my::A&)函数,所以无法通过编译。 他们可能认为应该像下面这面写,定义一个全局的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
namespace my {
    class A {};
    void f(const A&){}
}

void f(const my::A&) {}

int main()
{
    my::A a;
    f(a);
}

实际上,这样写会报类似下面的有歧义错误:

bogon:tmp tao$ g++ a.cc
a.cc:11:5: error: call to 'f' is ambiguous
    f(a);
    ^
a.cc:3:10: note: candidate function
    void f(const A&){}
         ^
a.cc:6:6: note: candidate function
void f(const my::A&) {}
     ^
1 error generated.

也就是:有多个名为 f 的函数原型可以满足上述调用。

第2个例子

这个例子比前面那个简单。

1
2
3
4
5
6
#include <iostream>

int main()
{
    std::cout << "hello";
}

这段代码很简单,理所当然地编译运行都正确。

按照运算符的重载规则,上面代码main中的那条语句可以等价转换成如下:

1
operator<<(std::cout, "hello");

但是,全局空间定义了上面这个operator<<重载函数吗?没有!,它省略返回类型的函数原型实际上是下面这个:

1
std::operator<<(std::ostream&, const char*);

但是这样的话,还能写成std::cout << "hello";的形式吗?不能!

std::cout std::operator<< "hello";

抱歉,这是错误的语法!

第3个例子

这个例子来看常规简写版非简写版的 hello,world 的区别。

常规简写版

1
std::cout << "hello, world" << "\n";

非简写版

1
std::operator<<(std::operator<<(std::cout, "hello, world"), "\n");

这代码真的真的没法写、没法看!

具体的原则

可以简单地说:如果一个函数没有被名字空间修饰,那么可以把它称作是未限定的(unqualified)

当一个函数是未限定的的时候,编译器会根据实参的类型去其所在的名字空间里面去找对应的函数原型!

当然,这条名字查找规则适用于所有未限定的函数!

这样一来,就不难解释第1个例子的代码为什么能编译并正确运行了(在 my::A 的名字空间 my 内找到了 f)。

参考链接

标签:C++