以下代码无法编译,因为未找到比较运算符。
#include <vector>
#include <iostream>
#include <string>
namespace Cool {
struct Person {
std::string name;
};
}
bool operator==(const Cool::Person& p1, const Cool::Person& p2) {
return p1.name == p2.name;
}
int main(int, char *[])
{
std::vector<Cool::Person> a{ {"test"} };
std::vector<Cool::Person> b{ {"test"} };
bool ok = a == b;
std::cout << ok << std::endl;
}
经过一些实验,我发现以下代码可以完美编译:
#include <vector>
#include <iostream>
#include <string>
namespace Cool {
struct Person {
std::string name;
};
bool operator==(const Person& p1, const Person& p2) {
return p1.name == p2.name;
}
}
int main(int, char *[])
{
std::vector<Cool::Person> a{ {"test"} };
std::vector<Cool::Person> b{ {"test"} };
bool ok = a == b;
std::cout << ok << std::endl;
}
有人可以解释这种行为背后的原因吗?
答案 0 :(得分:7)
这称为ADL,即与参数相关的查找。
对于运算符,编译器不仅会在当前名称空间中搜索合适的函数,还会在参数的名称空间中搜索。
例如:
int main() {
int arr[3] = {};
std::vector<int> vec(3);
auto b_vec = begin(vec); // std::begin
auto b_arr = begin(arr); // Error!
}
使用vec调用begin
时,它将在std
名称空间中搜索,因为std::vector
在该名称空间中。对于原始数组,找不到该函数,因为它没有与该类型关联的名称空间。
关闭ADL的一种方法是简单地限定功能:
// calls the std:: one, not the boost:: one
std::begin(some_boost_container);
这也是朋友功能的工作方式:
struct test {
friend void find_me(int) {}
};
find_me(3); // uh? no matching function?
即使功能很好,也找不到。
它需要在其参数中包含类的名称,以便ADL进入并在类范围内找到它:
struct test {
friend void find_me(test const&) {}
};
find_me(test{}); // works!
那么...对运营商来说呢?为什么行得通?
因为调用用户定义的运算符大致等效于:
// arg1 == arg2;
operator==(arg1, arg2);
由于函数名称不合格,因此将使用ADL。然后在正确的命名空间中找到运算符,还可以找到朋友函数。
这既允许使用漂亮的语法,也允许泛型函数在名称空间内调用函数。
那为什么矢量在全局名称空间中找不到那个?
这是因为ADL是如何工作的。在带有名称的函数的名称空间内,ADL将仅在参数的名称空间内搜索。但是,如果找不到,它将退回到正常查找。
namespace Cool {
struct Person {
std::string name;
};
}
bool cant_you_find_me(Cool::Person const& p);
namespace test {
void cant_you_find_me();
template<typename T>
void test_template(T const& p) {
cant_you_find_me(p); // ADL?
}
}
在此示例中,ADL将搜索函数cant_you_find_me
,但是如果无法通过ADL找到一个函数,则将不考虑全局名称空间,因为正常的查找将改为查找最接近的一个:不带参数的函数
std
名称空间就是这种情况。其中定义了许多operator==
。如果ADL找不到合适的名称空间,则将不考虑全局名称空间,而是使用std
中的名称空间。