红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【C++】Niebloid & CPO

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不起作用,自定义函数不会被调用