C++有两种函数查找方式:
- 普通查找:从当前命名空间开始,逐层往上一层命名空间进行查找,直到最外层命名空间
- 参数依赖查找(ADL):又名 Koenig 查找,用于函数查找,支持在函数的参数所在的命名空间中进行查找
C++20 对参数依赖查找进行进一步的增强,支持对模板函数的参数依赖查找。下面先回顾一下普通的参数依赖查找,再看新的模板函数的参数依赖查找是如何进行的,以及由此引起的副作用和规避方法。
普通参数依赖查找,ADL
普通参数依赖查找,是根据参数类型的命名空间进行查找,只会查找参数类型所在的命名空间,不会再往上一级命名空间进行查找。例如:
#include using std::cout, std::endl;namespace A1{ namespace A2 { struct X { int m_a { 5 }; }; int func_a( struct X a ) { cout << "A1::A2::func_a()" << endl; return a.m_a + 1; } }; int func_b( struct A2::X a ) { cout << "A1::func_b()" << endl; return a.m_a + 1; }};int main( int argc, char * argv[] ){ struct A1::A2::X a { 7 }; func_a( a ); // <1> ADL 查找,根据参数 a 所在命名空间 A1::A2 ,找到函数 A1:A2::func_a()// func_b( a ); // <2> Error,因为 ADL 查找只会在参数所在的命名空间进行查找,不会再往上一级命名空间查找 A1::func_b( a ); return 0;}
另外,参数依赖查找和普通查找是并列的,同时都会去找,并不是说普通查找更优先。例如上面的例子,如果最外层的全局命名空间下也定义了函数: int func_a( struct A1::A2::X a ); 那么 <1> 语句会编译错误,编译器无法区分是使用全局命名空间下的 func_a 函数还是 A1::A2 命名空间下面的 func_a 函数,此时需要使用 ::func_a 或者 A1::A2::func_a 的方式来指定调用哪一个。
模板函数的参数依赖查找
模板函数的参数依赖查找和普通函数的参数依赖查找一样,都是根据调用时的参数,在函数参数类型所在的命名空间中查找。
但模板函数和普通函数的一个差别,是调用模板函数时,可以通过 func( ... ) 的形式在模板函数名称后面加上类型,表示具体的特化。但这个模板参数类型,是不会进行参数依赖查找的。
下面是一个例子代码及说明:
#include #include using std::cout, std::endl;int h = 0;int g ( ) { return 3; }namespace N{ struct A { int m_a { 4 }; }; template < typename T > int f ( const T & a ) { return 4; } template < typename T > int g ( const T & a ) { return 5; } template < typename T > int h ( const T & a ) { return 6; } template < typename T > int k ( const T & a ) { return 7; } int k ( const struct A & a ) { return 8; } template < typename T > int k () { return 9; }};int main( int argc, char * argv[] ){ int a1 = f< N::A > ( N::A() ); // <1> 参数依赖查找,从命名空间 N 中进行查找 int a2 = g< N::A > ( N::A() ); // <2> 虽然 g 也有全局函数,但类型不对,所以会根据参数继续再命名空间 N 中查找// int a3 = h< N::A > ( N::A() ); // <3> Error,对 h 的查找首先命中最外层的全局变量 h ,就不会再去查找函数, int a4 = k( N::A() ); // <4> 普通的参数依赖查找,优先匹配非模块函数 int a5 = k< N::A > ( N::A() ); // <5> 指定使用模板函数// int a6 = k< N::A > ( ); // <6> Error,模板参数本身不进行参数依赖查找,因此不会找命名空间 N 中的函数 cout << a1 << " " << a2 << " " << a4 << " " << a5 << endl; // 输出 4 5 8 7 return 0;}
副作用和规避方法
由于模板的尖括号同时也是小于运算符,因此会出现二义性。因此在C++标准中,规定了如果一个名称被认为是函数名称,那么他后面的 '
下面是一个例子说明:
struct B{ int m_b { 5 };};int g ( ) { return 3; }bool operator < ( int (*fp)(), B b ){ return b.m_b > 0;} B b1;// bool c1 = g < b1; // <1> Error,g 是函数名,后面的 < 符号优先解析成表示模板bool c2 = ( g ) < b1; // <2> 加一个括号可以规避,这时候 < 符号解析成小于运算符
【往期回顾】
C++20 新特性(22):C++ attribute的改进
C++20 新特性(21):其他const相关的改进