c++ ADL(Argument-Dependent Lookup)查找

0X00 简述

ADL全称是Argument-Dependent Lookup的简写,作用是扩展命名空间的查找范围,通过函数参数查找函数的命名空间。

0x01 问题引出

我们通过一个例子引出为什么要有这种机制。现在我们有这样一种命名空间f1

namespace f1
{
    struct data {};
    data operator+(const data& A, const data& B)
    {
        data C;
        add(A, B, C);//A+B-->C
        return C;
    }
}

我们想要实现这样的操作

A = B + C + D

如果没有ADL机制的话,我们式子就会变得很复杂

A = f1::operator+(f1::operator+(B, C), D);

而由于出现了ADL这种机制,我们的操作写法就变得很简洁

A = operator+(operator+(B, C), D);

这实际上等价于A = B + C + D;这种写法。我们这里通过ADL机制获取了参数的命名空间。

这个问题看上去很容易,但是一旦我们使用了多重命名空间、name hidingoverload混合,那么这个问题将会变得很复杂。

0x02 问题深入

我们看这样的一个问题

namespace c1
{
    namespace c2
    {
        struct cc{}
        void f(const cc& o){}
    }
    void f(const c2::cc& o){}
}
void f(const c1::c2::cc& o){}
namespace f1
{
    void f(const c1::c2::cc& o){}
    namespace f2
    {
        void f(const c1::c2::cc& o){}
        void g()
        {
            c1::c2::cc o;
            f(o);
        }
    }
}

这个时候我问你,在f1::f2::g中的f(o)使用的是哪个f ?我们分析一下,我们有几个选择

  • 因为我们知道参数o的类型是c1::c2::cc,所以我们通过ADL推测出命名空间为c1::c2,这时候的答案就是c1::c2::f
  • c1::f可以吗?
  • ::f可以吗?
  • f1::f可以吗?
  • f1::f2::f,这个有可能吗?可能啊,因为我们的g函数的命名空间就是f1::f2::g。同一个命名空间下的函数,这样调用也没问题啊。

OK! 这个时候问题就出现了。到底答案是哪个呢?我们加大难度,这个时候我们将全局函数f的参数去除const,变为这样

void f(c1::c2::cc& o){}

如果我们这个时候从函数重载(overload)的角度考虑这个问题,那么现在这个全局的f成为了最佳匹配。但是它被f1::f2::f这个函数给隐藏了(name hiding)。那么同样的道理f1::fc1::f也就不会被调用了。那么我们一定要使用这个全局函数怎么做呢?可以这样子做

void f(c1::c2::cc& o){}
namespace f1
{
    void f(const c1::c2::cc& o){}
    namespace f2
    {
        void f(const c1::c2::cc& o){}
        using ::f;
        void g()
        {
            c1::c2::cc o;
            f(o);
        }
    }
}

这个时候对于函数g,函数c1::c2::f::f都是可调用的,但是根据参数类型的最佳匹配原则,我们调用了::f

接着回到最初问题,这个时候我们就有了两个答案c1::c2::ff1::f2::f。实际上这个程序就会编译出错。

0x03 问题后续

我们接着看这样的问题

namespace c1
{
    namespace c2
    {
        struct cc{};
        void f(cc& o){}             //#1
    }
}
void f(c1::c2::cc& o){}
namespace f1
{
    namespace f2
    {
        void f(const c1::c2::cc& o){}   //#2
        void g()
        {
            c1::c2::cc o;
            const c1::c2::cc c(o);
            f(o);
            f(c);
        }
        void f(c1::c2::cc& o){}     //#3
    }
}

因为#3是定义于g的后面,所以在g中是不可见的。全局函数::f#2隐藏(name hiding)。因此对于f(o)来说,我们通过使用ADL可以调用#1,我们通过name hiding也可以调用#2,但是我们最后调用最佳匹配#1。而对于f(c)我们通过同样的分析,我们知道调用#2

最后我们总结为以下三个步骤帮助我们判断

  • 找到所有的重载函数
    • 调用函数自己的命名空间中
    • 调用函数的父命名空间中
    • 参数的命名空间中
    • 通过using directive;导入的命名空间中(using namespace std;
    • 通过using declaration;导入的命名空间中(using std::cout;
  • 排除掉所有的name hiding
  • 选择最佳匹配(如果没有最佳匹配,程序编译出错)

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/80007769