C++ 名称查找(Name lookup)与参数依赖查找ADL(Argument-dependent lookup)

名称查找中的几个概念

限定作用符 ::

限定名值指出现在::(限定作用符)右侧的名字,他可以是

  1. 命名空间
  2. 枚举

无限定作用域及,没有限定作用符在左侧的名字

有限定名查找,无限定名查找的一些规则

  1. 如果::限定作用符左侧留空,只会在全局命名空间查找;
  2. 存在多级限定作用符级联时,只有对左侧名查找完了,再在找到的作用域里对右侧进行查找;有限定和无限定和可以混用
  3. 对::左侧的符号进行查找,会忽略变量、函数、枚举项的声明
  4. 当在声明符中使用限定名,该声明符中的非限定名查找在限定名的类或者命名空间中查找
  5. 无限定查找,查找之前声明的部分,如果存在命名空间嵌套则会一级一级往外查找
  6. 友元声明的无限定名查找(非模板实参),先在友元的限定域里面查找,再在友元的声明所在域查找
  7. 模板非依赖名,立即绑定,并且模板的非限定名查找只可见模板定义点前的命名
  8. 如果某个基类取决于某个模板形参,那么无限定名字查找不会检查它的作用域
  9. 非限定名的ADL
struct A
{
    static int n;
};
int n = 1; // 声明
const int DD = 100;
class N{
    static const int DD = 50;
    static int S[DD];
     int m = 2;
 
    namespace Y
    {
        int x = n; // 规则5  找到 ::n
        int y = m; // 规则5,找到 ::N::m
    }
};
int N::S[DD]{};// 规则4,DD为N::DD,size 50

struct A
{
    typedef int AT;
 
    void f1(AT);
    void f2(float);
 
    template<class T>
    void f3();
 
    void f4(S<AT>);
};
 
// 这个类为 f1,f2 和 f3 授予友元关系
struct B
{
    typedef char AT;
    typedef float BT;
 
    friend void A::f1(AT);    // 规则6 对 AT 的查找找到的是 A::AT(在 A 中找到 AT)
    friend void A::f2(BT);    // 规则6 对 BT 的查找找到的是 B::BT(在 A 中找不到 AT)
    friend void A::f3<AT>();  // 规则6 对 AT 的查找找到的是 B::AT (不在 A 中进行查找,
                              //     因为 AT 在声明符中的标识符 A::f3 中)
};
 // 这个类模板为 f4 授予友元关系
template<class AT>
struct C
{
    friend void A::f4(S<AT>); //规则6 对 AT 的查找找到的是 A::AT 
                              // (AT 不在声明符中的标识符 A::f4 中)
};

void f(char); // f 的第一个声明
 
template<class T> 
void g(T t)
{
    f(1);    //  规则7 非待决名:名字查找找到了 ::f(char) 并在此时绑定
    f(t);    // 待决名:查找推迟
//  dd++;    // 非待决名:名字查找未找到声明
}
 
void f(int); // 模板不可见
double dd;
 
void h()
{
 
    g(32); // 实例化 g,此处
           // 对 'f' 的第二次和第三次使用
           // 进行了查找仅找到了 ::f(char)
           // 然后重载解析选择了 ::f(char)
           // 这三次调用了 f(char),规则7 ,不可见f(int)
           
}

typedef double A;
 
template<class T>
class B
{
    typedef int A;
};
 
template<class T>
struct X : B<T>
{
    A a; //  规则8  对 A 的查找找到了 ::A (double),而不是 B::A
};
 
int main()
{
	struct std {};
	::std::cout << "\n"; //  规则1,忽略std{}, 规则2 多级限定
    int A;
    A b;       //  查找,找到了变量 A,报错
    A::n = 42; // 规则3, A是无限定名,忽略变量A的定义,n是限定名
}

ADL

ADL拉出来单列,稍微特殊一点,相比非限定名的其他查找叫ordinary lookup(规则5逐层往外查找)
参数依赖查找,顾名思义,根据参数查找函数实现,是一种对无限定名的函数调用附加查找规则。

#include 
int main()
{
    std::cout << "测试\n"; // 全局命名空间中没有 operator<<,但 ADL 检验 std 命名空间,
                           // 因为左实参在 std 命名空间中
                           // 并找到 std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "测试\n"); // 同上,用函数调用记法
 
    // 然而,
    std::cout << endl; // 错误:'endl' 未在此命名空间中声明。
                       // 这不是对 endl() 的函数调用,所以不适用 ADL
 
    endl(std::cout); // OK:这是函数调用:ADL 检验 std 命名空间,
                     // 因为 endl 的实参在 std 中,并找到了 std::endl
 
    (endl)(std::cout); // 错误:'endl' 未在此命名空间声明。
                       // 子表达式 (endl) 不是函数调用表达式
}

因为规则7的缘由,没有ADL,模板的适用范围会极大受限(其实ADL用的最多的还是模板,具体实际使用案例可参照nlohmann json 库,其中大量使用的to_json 和 from_json 函数,原理就是ADL,通过ADL查找找到对应的用户自定义实现的序列反序列函数。)

template<typename T>
T max (T a, T b) {
    return b < a ? a : b;
}

namespace BigMath {
  class BigNumber {
    ...
};

  bool operator < (BigNumber const&, BigNumber const&);  //对模板 MAX 不可见,若无ADL
  ...
}

using BigMath::BigNumber;

void g (BigNumber const& a, BigNumber const& b) {
  ...
  BigNumber x = ::max(a,b);
  ...
}

忽略ADL的几种情况

如果未限定名查找所产生的候选集存在下述情形,则不会启动依赖于实参的名字查找:

  1. 成员函数声明
class MyClass {
public:
    void foo(int) {
        // 不考虑参数类型的命名空间,直接在类的作用域中查找
        std::cout << "MyClass::foo" << std::endl;
    }
};

int main() {
    NS1::A a;
    MyClass obj;
    
    obj.foo(a); // 不会触发ADL,直接在 MyClass 的作用域中查找 foo
}

  1. 块作用域中的函数声明(非using-declaration)
namespace NS1 {
    void foo() {
        std::cout << "NS1::foo" << std::endl;
    }
}

int main() {
    void foo(); // 在 main 函数作用域内声明一个函数

    foo(); // 不会触发ADL,直接在 main 函数作用域内查找 foo
}

  1. 非函数、函数模板的声明
namespace NS1 {
    struct MyStruct {
        void operator()() {
            std::cout << "MyStruct operator()" << std::endl;
        }
    };
}

int main() {
    NS1::MyStruct foo; // 函数对象

    foo(); // 不会触发ADL,直接在当前作用域内查找 foo
}

你可能感兴趣的:(C\C++,杂谈,c++)