Niebloid & CPO
实参依赖查找(ADL)
C++中,编译器处理代码中出现的所有标识符时,都需要查找他的出处
为了防止名称冲突,可以使用命名空间来限定作用域,使用时可以使用有限定的标识符:命名空间+作用域解析运算符+标识符,相对应的,没有限定的称为未限定的标识符,编译器对两种标识符进行两种查找
其中,未限定的标识符使用实参依赖查找,除了常规查找范围,编译器还会将函数实参所在的命名空间加入查找范围
ADL只适用于函数与函数模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
template<typename T>
void hello(const T& x){
std::cout<<"[global]hello"<<(const char*)x<<std::endl;
}
namespace cat{
struct A
{
std::string s;
operator const char*() const{return s.c_str();}
};
void hello(const A& x){
std::cout<<"[cat]hello"<<(const char*)x<<std::endl;
}
}
int main(){
::hello("world");
hello(cat::A{std::string("world")});
return 0;
}1
2
[global]helloworld
[cat]helloworld定制点
如标准库中的函数,允许用户在自己的命名空间为某些类型进行函数重载,并能通过ADL找到这个函数的函数称为定制点,如swap、begin、end
标准库中其他函数在使用到定制点函数时,也会通过ADL找到自定义的定制点函数
如reverse函数中用到了swap函数,若此时的类型是我们自定义的swap函数的重载类型,reverse的时候就会使用我们自定义的swap函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
namespace cat{
struct MyType{
};
struct MyType2{
};
void swap(MyType& a, MyType& b){
std::cout <<"Custom Swap"<<std::endl;
}
}
template<typename T1, typename T2>
void do_sth(T1 a, T2 b){
using std::swap;
swap(a,b);
}
int main(){
cat::MyType a;
cat::MyType b;
do_sth(a,b);
std::string s1{"hello"}, s2{"world"};
do_sth(s1,s2);
cat::MyType2 c;
cat::MyType2 d;
do_sth(c,d);
return 0;
}例如该函数中,
当实参类型是MyType时,进入do_sth时,由于using声明,首先找到了std::swap,后面编译器通过ADL找到了cat::swap,而后者优先级更高,故采用后者
优先级的基本原则为越专用越优先
当实参类型为基本类型或MyType2时,由于cat中没有对应的重载函数,故使用std的swap函数,但若没有using声明,这两个在cat命名空间,全局命名空间都找不到swap函数,会报错
定制点对象(CPO)
C++20后,要对定制点函数进行类型检查,而ADL下直接使用用户函数重载则绕过了检查,因此,C++20中提出了定制点对象
将do_sth函数中的swap进行修改
1
2
3
void do_sth(T1 a, T2 b){
std::ranges::swap(a,b);
}此时std::ranges::swap不是一个函数,而是一个函数对象,其中封装了未限定名的调用
由于是个对象,因此不会进行ADL,也就不会直接调用用户自定义的重载函数,而是经过定制点对象进行实参类型检查,再调用真swap函数进行ADL查找
Neibloid
ranges库中称为算法函数对象(AFO),是一种CPO
为了避免使用非限定名算法函数时由于ADL无法调用ranges库,会调用其他命名空间的同名函数,使用CPO对ADL进行屏蔽
Neibloid是用于实现ranges的函数对象,并且为了保证调用,它使用CPO来实现了对ADL的屏蔽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
#include<vector>
namespace cat{
struct MyType{
int v;
bool operator==(const MyType& other) const{
return v == other.v;
}
};
decltype(auto) find(const std::vector<MyType>::iterator& first, const std::vector<MyType>::iterator & last, const MyType &value ){
std::cout <<"Custom"<<std::endl;
return std::find(first, last, value);
}
}
int main(){
std::vector<cat::MyType> v1{{1},{2},{3},{4}};
cat::MyType value{3};
using namespace std::ranges;
auto it = find(v1.begin(), v1.end(),value);
if (it!=v1.end()){
std::cout<<"Found value: "<<it->v<<std::endl;
}
else{
std::cout <<"Value not found"<<std::endl;
}
}我们在这定义了自己的find函数,又在主函数中使用using namespace并调用find
由于ranges库中算法函数对象的存在,ADL不起作用,自定义函数不会被调用