从 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 查找。