跟我学c++高级篇——模板的ADL

一、ADL

ADL,argument-dependent lookup,早期也叫做Koenig查找。它是用于在函数模板调用时确定模板参数的类型的查找规则,所以它不适合类模板、成员函数模板以及其它类型的模板。另外也不适合于块作用域(除非using声明)的函数以及任何非函数或函数模板的声明(函数对象名称和变量名称不能与之冲突)。否则就需要对函数调用表达式中的实参检验其类型以确定其指向的命名空间与类的关联集。
这话说的有点绕口,其实就是对调用实参的类型查找其名空间和相关的作用域集合,来查找函数。举一个例子:
编译器在查找函数时,首先在函数模板所在空间查找;找不到在函数调用作用域内查找,如果还找不到,则到实参的名空间内查找;再找不到,就报错。这样就明白了吧。
在这些调用中,包含类自身,基类,外围类和最内层类的外层命名空间(当然,对于基础类型,如int之类其关联集为空);如果类模板已经特化实参,则还增加对实参类的命名空间及以其为成员的类。
需要注意是:
1、关联命名空间内的using指令会被忽略
2、忽略函数与函数模板外的所有名字(防止与变量冲突)
3、可以ADL相关友元函数
4、c++11后增加了对内联空间相关的处理
更详细的可以查看一下标准文档,其中对枚举和指针以及其它一些细节等都有更详细的描述。

二、ADL作用

ADL的主要作用就是方便查找函数模板,也就是说,允许函数模板在不同的命名空间进行定义,而不需要显示的指定作用域或者全局空间限定。看一个简单的例子:

namespace A {
    class A1 {};
    void foo(A1 a1) { std::cout << "call A::foo "<<std::endl; }
}

namespace B {
    void foo(A::A1 a1) { std::cout << "call B::foo"<<std::endl; }
}

int main() {
    A::A1 a;//
    foo(a);
    return 0;
}

运行结果:

call A::foo

可以看到a属于名空间A,所以去其中去找,发现了foo函数。

三、例程

在掌握了ADL的作用后,可以针对具体的例程继续熟悉一下相关的规则:

namespace N1
{
    struct S {};

    template<int X>
    void f(S);
}

namespace N2
{
    template<class T>
    void f(T t);
}

void g(N1::S s)
{
    f<3>(s);     // C++20 前是语法错误(无限定查找找不到 f)
    N1::f<3>(s); // OK,有限定查找找到模板 'f'
    N2::f<3>(s); // 错误: N2::f 不接收非类型模板形参
                 //       N1::f 不能被找到,因为 ADL 仅适用于无限定名

    using N2::f;
    f<3>(s); // OK:无限定查找现在找到 N2::f,
             //     然后因为此名无限定所以 ADL 表态并找到 N1::f
}

再看一个简单例程:

namespace A
{
    struct X;
    struct Y;

    void f(int);
    void g(X);
}

namespace B
{
    void f(int i)
    {
        f(i); // 调用 B::f(无限递归)
    }

    void g(A::X x)
    {
        g(x); // 错误:在 B::g(常规查找)与 A::g(实参依赖查找)间有歧义
    }

    void h(A::Y y)
    {
        h(y); // 调用 B::h(无限递归):ADL 检验命名空间 A
              // 但是找不到 A::h,所以只采用来自常规查找的 B::h
    }
}

通过上面找的几个例程,基本就可以掌握ADL的知识了。在前面的文章《模板友元的应用》中在写一个例程时出现了swap的名称污染的问题,就是这么一个意思。编译器在ADL的过程中找到了两个swap,让它无法确定程序到底想调用哪个,所以只能抛出一个错误。另外,如果恰恰有名称空间内定义swap则有可能会产生调用的非想到的swap。这恰恰绕开了名空间对调用的限制的本意,虽然这种情况很少见,而正是少见,一旦出现问题,才更难发现问题所在。

四、总结

一个问题的解决,可能就代表着另外一个新的问题的出现。这是一种常见的现象,只是看成本的大小罢了。写程序越多,发现这玩意儿真得上升到了哲学的角度上了。越是深入对标准的审视,就越是明白c++的难度所在。
那动辄一千多页的标准文档制定起来,确实不容易啊。

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