什么是SFINAE

什么是SFINAE

什么是SFINAE #

替换失败非错(substitution failure is not an error),英文简称为 SFINAE。

函数模板的重载决议 #

我们之前已经讨论了不少模板特化。我们今天来着重看一个函数模板的情况。当一个函数名称和某个函数模板名称匹配时,重载决议过程大致如下:

  1. 根据名称找出所有适用的函数和函数模板
  2. 对于适用的函数模板,要根据实际情况对模板形参进行替换;替换过程中如果发生错误,这个模板会被丢弃
  3. 在上面两步生成的可行函数集合中,编译器会寻找一个最佳匹配,产生对该函数的调用
  4. 如果没有找到最佳匹配,或者找到多个匹配程度相当的函数,则编译器需要报错

我们还是来看一个具体的例子(改编自参考资料 [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); 的情况:

  1. 我们有两个模板符合名字 f
  2. 替换结果为 f(Test::foo) 和 f(Test)
  3. 使用参数 10 去匹配,只有前者参数可以匹配,因而第一个模板被选择

再看一下 f<int>(10) 的情况:

  1. 还是两个模板符合名字 f
  2. 替换结果为 f(int::foo) 和 f(int);显然前者不是个合法的类型,被抛弃
  3. 使用参数 10 去匹配 f(int),没有问题,那就使用这个模板实例了

在这儿,体现的是 SFINAE 设计的最初用法:如果模板实例化中发生了失败,没有理由编译就此出错终止,因为还是可能有其他可用的函数重载的。

这儿的失败仅指函数模板的原型声明,即参数和返回值。函数体内的失败不考虑在内。如果重载决议选择了某个函数模板,而函数体在实例化的过程中出错,那我们仍然会得到一个编译错误。

Viewpoint #

From #