C++ 模板 two-phase name lookup

从 C++ 模板函数中调用函数时,对被动函数重载的匹配有两个阶段,称为 two-phase name lookup,这对于开发模板类库的程序员是特别需要注意的一个规则。

先看一段代码:

// two_phase_name_lookup.cpp

#include <stdio.h>

namespace A {
    template <typename T>
    void f(T t) { puts("A::f<T>(T)"); }
}
namespace B {
    struct S {};
    void f(S) { put("B::f(B::S)"); }
}
namespace A {
    void f(B::S) { puts("A::f(B::S)"); }
}

void f(B::S) { put("f(B::S)"); }
                
namespace A {
    template <typename T>
    void g(T t) {
        f(t); // call which 'f' ?? 
    }
}
int main()      
{       
    B::S s;
    A::g(s);
    return 0;
}

上面 从 A::g<T>(T) 中调用 f(t),会有 4 个可以匹配的版本:

  • A::f<T>(T) [ T = B::S ]
  • B::f(B::S)
  • A::f(B::S)
  • ::f(B::S)

问题是会匹配哪个版本的 f 呢?

如果把匹配的版本去掉,剩下的版本中又会匹配哪个版本呢?

如果列一个优先匹配顺序,顺序是怎样的?

答案是:

  • 首先 B::f(B::S) 与 A::f(B::S) 优先级相同,但是都比其他版本高,他们同时存在时会冲突
  • 其次是 A::f<T>(T)
  • 最后是 ::f(B::S)

接着,我们进行第二组实验,将所有版本的 f 定义都移动到调用函数 A::g<T>(T) 的后面,上面的同样的问题会是什么答案。

答案是:与上面的一样。

 

也许上面的结果都在你的意料之中,那么接下来,我们将一部分 f 定义放在调用函数前面,另一部分放在后面,会是什么结果?

如果你勤于动手,把各种组合都试一遍,你应该会惊讶于你的发现,下面是一些可能出现的情形:

  • 全局 ::f(B::S) 与 B::f(B::S) 和 A::f(B::S) 冲突
  • 某些在调用函数后面的 f 版本,编译器会视而不见

其实,这些都是 C++ 模板中调用函数的两次名字查找,第一遍查找是在解析模板定义时,第二遍是在模板实例化时(main 中调用 A::g(B::S) )时。

  • 第一遍没有找到,第二遍会完整查找,否则只通过参数类型名字空间查找(ADL)
  • 两遍查找的结果合并,然后确定优先级和匹配版本。

如果你发现上面的规则不成立,与你试验的结果不一致,那么你很可能使用的较高版本的 g++。从 gcc 4.7 版本开始,上面规则有修改,不管第一遍有没有找到可能匹配版本,第二遍都只通过 ADL 查找。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值