如果 (b) 不存在,那么 (c) 确实是 (a) 的有效特化。事实上,只要改变源代码行的顺序,让 (c) 出现在编译器看到 (b) 之前,就会使它成为 (a) 的特化!
考虑以下代码:
int main()
{
int a;
f(&a);
return 0;
}
现在设身处地为编译器着想。您需要使用 int* 参数找到匹配的函数 f。你是做什么的?
- 首先,您尝试所有您知道的名为
f 的非模板函数,看看是否有任何与参数类型匹配的函数(在本例中为int*)。
- 如果您无法获得完美匹配,请查看您所知道的所有基本模板。在这种情况下,有两个:
f<T> 和 f<T*>。请注意,与类不同,函数模板不能部分特化,因此就编译器而言,这些是完全独立的重载。
- 显然
f<T*> 基本模板是更好的匹配,所以你用T=int 实例化它。现在,如果您已经看到 f<int*> 的特化,那么您可以使用它,否则您将生成函数。
现在有趣的事情来了。如果我们将您的原始代码的顺序更改为
template<class T> void f( T ); // (i)
template<> void f<>(int*); // (ii)
template<class T> void f( T* ); // (iii)
那么编译器现在将 (ii) 视为 (i) 的特化——因为它按顺序处理事物,并且在它到达 (ii) 的点上它还不知道 (iii) 存在!但由于它只匹配基本模板,它决定 (iii) 比 (i) 更好 - 现在 (iii) 没有任何特化,所以你得到默认实例化。
这一切都非常令人困惑,有时甚至会让最有经验的 C++ 程序员感到困惑。所以基本规则是这样的:不要专门化函数模板,而是使用正常的函数重载。一个普通的旧的非模板
void f(int*);
将在其他任何事情之前进行匹配,并避免这整个混乱。
编辑: n.m.要求在 cmets 中参考标准。恐怕我手头只有 C++03 版本,但这里是:
第 4.7.3.3 段:“显式特化的函数模板或类模板的声明应在显式特化声明点的范围内。”。
这就是为什么在上面的示例中,(ii) 不能被视为 (iii) 的明确特化,因为 (iii) 尚未在范围内。
第 4.8.3 节:“当编写对该 [函数] 名称的调用时...为 执行模板参数推导 (14.8.2) 并检查任何显式模板参数 (14.3)每个函数模板查找可与该函数模板一起使用的模板参数值(如果有),以实例化可通过调用参数调用的函数模板特化。"
换句话说,(无论如何,正如我所读的那样)它总是查看每个基本模板,无论如何——提供明确的专业化没有区别。
同一段继续:“对于每个函数模板,如果参数推导和检查成功,则模板参数(推导和/或显式)用于实例化单个函数模板特化,该特化被添加到在重载决议中使用的候选函数集。"
所以只有在这一点上(正如我所读的),明确的专业化才被考虑在内。
最后,也许在这种情况下最重要的是,第 13.3.3 节处理在重载集中选择“最佳可行函数”。有两个项目是相关的:
F1 优于 F2 如果:"F1 和 F2 是函数模板特化,根据 14.5.5.2 中描述的偏序规则,F1 的函数模板比 F2 的模板更特化"。这就是为什么在尝试匹配 f(int*) 时,f<T*> 版本会在 f<T> 版本之前被选中——因为它是一个“更专业”的模板
F1 优于 F2 如果:“F1 是非模板函数,F2 是函数模板特化”,这是我在原文结尾处提出建议的基础回答。
呼!