什么是SFINAE #
替换失败非错(substitution failure is not an error),英文简称为 SFINAE。
函数模板的重载决议 #
我们之前已经讨论了不少模板特化。我们今天来着重看一个函数模板的情况。当一个函数名称和某个函数模板名称匹配时,重载决议过程大致如下:
- 根据名称找出所有适用的函数和函数模板
- 对于适用的函数模板,要根据实际情况对模板形参进行替换;替换过程中如果发生错误,这个模板会被丢弃
- 在上面两步生成的可行函数集合中,编译器会寻找一个最佳匹配,产生对该函数的调用
- 如果没有找到最佳匹配,或者找到多个匹配程度相当的函数,则编译器需要报错
我们还是来看一个具体的例子(改编自参考资料 [1])。虽然这例子不那么实用,但还是比较简单,能够初步说明一下。
#include <stdio.h>
struct Test {
typedef int foo;
};
template <typename T>
void f(typename T::foo)
{
puts("1");
}
template <typename T>
void f(T)
{
puts("2");
}
int main()
{
f<Test>(10);
f<int>(10);
}
输出为: 12
我们来分析一下。首先看 f<Test>(10); 的情况:
- 我们有两个模板符合名字 f
- 替换结果为 f(Test::foo) 和 f(Test)
- 使用参数 10 去匹配,只有前者参数可以匹配,因而第一个模板被选择
再看一下 f<int>(10) 的情况:
- 还是两个模板符合名字 f
- 替换结果为 f(int::foo) 和 f(int);显然前者不是个合法的类型,被抛弃
- 使用参数 10 去匹配 f(int),没有问题,那就使用这个模板实例了
在这儿,体现的是 SFINAE 设计的最初用法:如果模板实例化中发生了失败,没有理由编译就此出错终止,因为还是可能有其他可用的函数重载的。
这儿的失败仅指函数模板的原型声明,即参数和返回值。函数体内的失败不考虑在内。如果重载决议选择了某个函数模板,而函数体在实例化的过程中出错,那我们仍然会得到一个编译错误。