参数依赖搜索(ADL)规则,就是Argument-Dependet Lookup,它是一套针对没有限定范围的函数的搜索定位规则。
另一个定义:
如果一个或者多个函数参数类型是在和函数同样命名空间内被定义的话,你就没有必要在函数调用的时候注明函数的命名空间
1. 没有限定范围的函数,编译器会额外地在他们的参数的命名空间中进行搜索和匹配。
没有限定范围的函数定义为函数没有使用范围符号(::),在这种情况下,ADL 就会被采用
举个例子
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass) {}
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
在上面的例子中,既没有使用using 来声明或者using来直接引用doSomething()函数,而且doSomething()函数也没有空间限定符(::),但是doSomething仍能正常使用。
背后是如何工作的?
编译器不仅仅查看局部范围,也查看包含参数类型的命名空间,上述代码中编译器发现object obj
, 作为函数 doSomething()的参数
, 属于命名空间 MyNamespace
. 于是编译器也在这个命令空间中搜索和定位doSomething()
.
另一个例子
#include <iostream>
int main(){
std::cout << "Argument-dependent lookup";
}
上述代码可以正常运行,让我们对操作符<<进行抽丝剥茧,如下:
#include <iostream>
int main(){
operator<<(std::cout, "Argument-dependent lookup");
}
重载运算符<< 被调用的时候带有两个参数: std::cout 和一个 C-string,那么编译器去那里赵这个<<的定义呢?
A. 首先<<没有在全局命名空间中定义
B. <<是一个没有带限定词的函数名称(::)
C. 在函数定位时ADL 就会被使用
在上述例子中,由于std::cout是第一个参数,于是ADL就在std中发现了运算符重载函数:std::operator<<(std::ostream&, const char*)
2. 避免在高可见并且无限制的模板中使用常见名字
在表达式std::cout << "Argument-dependent lookup"中,<<操作符是在std中定义,因此是一个高可见的通用名字可。下面的例子,将很好地解释这条规则。
#include <iostream>
#include <vector>
namespace Bad{
struct Number{
int m;
};
template<typename T1, typename T2> // generic equality (5)
bool operator==(T1, T2){
return false;
}
}
namespace Util{
bool operator==(int, Bad::Number){ // equality to int (4)
return true;
}
void compareSize(){
Bad::Number badNumber{5}; // (1)
std::vector<int> vec{1, 2, 3, 4, 5};
std::cout << std::boolalpha << std::endl;
std::cout << "5 == badNumber: " <<
(5 == badNumber) << std::endl; // (2)
std::cout << "vec.size() == badNumber: " <<
(vec.size() == badNumber) << std::endl; // (3)
std::cout << std::endl;
}
}
int main(){
Util::compareSize();
}
我们期待着2处和3处,由于他们都有一个参数类型Bad::Number (1), 因此都会调用4处定义的重载运算符==,我们期待得到两个false/false的结果。但实际我们得到true/false的结果。
也就是说,3处的函数其实调用的是5处的函数定义,why?这是因为3处的vec.size()返回一个类型为std::size_type的值,这是一个无符号整数类型。如果匹配4处的函数定义的话,需要有一个无符号整数到整数的转换,而5处的函数定义则完全匹配,不需要作任何转换,于是由于ADL,5处的函数定义被选中。
假设我们遵守“避免在高可见并且无限制的模板中使用常见名字”这条规则,我们把5处的定义注释掉,我们就会得到true/true的结果
3. 嵌套重载函数的定位
a. 来自不同基类的函数之间的定位不能依赖参数类型来确定。
b.一个派生类的成员函数与基类中同名的成员函数的范围不同。
c. 重载函数的定位遵循基于同一范围的搜索和定位。
比如:
struct Base {
void func(double);
};
struct Derived : Base {
void func(int);
};
Derived d;
d.func(3.14);