跳转到内容

更多 C++ 习语/SFINAE

来自 Wikibooks,开放世界中的开放书籍

从一组重载函数中删除那些无法生成有效模板实例化的函数。

也称为

[编辑 | 编辑源代码]

Substitution Failure Is Not An Error(替换失败并非错误)

动机和解决方案

[编辑 | 编辑源代码]

严格来说,SFINAE 是一个语言特性,而不是一个习语。然而,这个语言特性在使用 enable-if 时被以非常习惯性的方式利用。

在模板参数推导过程中,C++ 编译器会尝试实例化多个候选重载函数的签名,以确保只有一个重载函数可以完美匹配给定的函数调用。如果在函数模板实例化期间形成了无效的参数或返回值类型,则该实例化将从重载解析集中删除,而不是导致编译错误。只要只有一个函数可以调用,编译器就不会发出任何错误。

例如,考虑一个简单的函数 multiply 及其模板化对应物。

long multiply(int i, int j) { return i * j; }

template <class T>
typename T::multiplication_result multiply(T t1, T t2)
{
  return t1 * t2;
}
int main(void)
{
  multiply(4,5);
}

main 中调用函数 multiply 会导致编译器尝试实例化模板化函数的签名,即使第一个 multiply 函数是更好的匹配。在实例化期间,会产生一个无效类型:int::multiplication_result。然而,由于 SFINAE,这种无效实例化会自动被忽略。最终,只有一个 multiply 函数可以被调用,即第一个函数。因此编译成功。

SFINAE 通常被用来在编译时确定类型的属性。例如,考虑以下 is_pointer 元函数,它在编译时确定给定类型是否为某种类型的指针。

template <class T>
struct is_pointer
{
  template <class U>
  static char is_ptr(U *);

  template <class X, class Y>
  static char is_ptr(Y X::*);

  template <class U>
  static char is_ptr(U (*)());

  static double is_ptr(...);

  static T t;
  enum { value = sizeof(is_ptr(t)) == sizeof(char) };
};

struct Foo {
  int bar;
};

int main(void)
{
  typedef int * IntPtr;
  typedef int Foo::* FooMemberPtr;
  typedef int (*FuncPtr)();

  printf("%d\n",is_pointer<IntPtr>::value);        // prints 1
  printf("%d\n",is_pointer<FooMemberPtr>::value);  // prints 1
  printf("%d\n",is_pointer<FuncPtr>::value);       // prints 1
}

上面的 is_pointer 元函数如果没有 SFINAE 就不会起作用。它定义了 4 个重载的 is_ptr 函数,其中 3 个是模板,每个模板都接受一个参数:指向变量的指针、指向成员变量的指针或简单的函数指针。所有三个函数都返回一个 char,这是故意的。最后一个 is_ptr 函数是一个通配函数,使用省略号作为参数。但是,这个函数返回一个 double,它的大小始终大于字符。

is_pointer 传递一个实际上是指针的类型(例如,IntPtr)时,由于两个 sizeof 表达式的比较,value 会被初始化为 true。第一个 sizeof 表达式调用 is_ptr。如果它是一个指针,只有一个重载的模板函数匹配,而不是其他函数。但是,由于 SFINAE,不会引发错误,因为至少找到了一个合适的函数。如果没有任何函数适合,则会使用带有省略号的函数。但是,该函数返回一个 double,它大于字符,因此 sizeof 比较失败,value 被初始化为 false。

请注意,所有 is_ptr 函数都没有定义。只有声明足以触发编译器中的 SFINAE 规则。但是,这些函数本身必须是模板。也就是说,具有常规函数的类模板将不会参与 SFINAE。参与 SFINAE 的函数必须是模板。

已知用法

[编辑 | 编辑源代码]
[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
华夏公益教科书