什么是 SFINAE? SFINAE 是 “Substitution Failure Is Not An Error” 的缩写。这是 C++ 模板元编程中的一个重要概念,它允许在模板类型推导过程中,如果某个替换失败,这个失败不会立即导致编译错误。而是使得编译器简单地放弃这次替换,并尝试其他重载或模板特化。
SFINAE 的用途 SFINAE 最常见的用途包括:
编写类型特征 :检查类型是否具有某个成员函数、成员类型或操作符。
函数模板重载决策 :在多个函数模板重载之间选择最适合的一个。
类模板特化 :在多个类模板特化之间选择最适合的一个。
如何使用 SFINAE 在 C++11 及其之后的版本中,SFINAE 最常见的实现方式是使用 decltype
、std::enable_if
和类型特征(如 std::is_integral
等)。下面是一些使用 SFINAE 的例子:
例子 1:使用 std::enable_if
禁用某些函数重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <type_traits> #include <iostream> template <typename T>typename std::enable_if<std::is_integral<T>::value, T>::type foo (T t) { std::cout << "Integral version: " << t << std::endl; return t; } template <typename T>typename std::enable_if<std::is_floating_point<T>::value, T>::type foo (T t) { std::cout << "Floating point version: " << t << std::endl; return t; } int main () { foo (10 ); foo (3.14 ); }
在这个例子中,foo
函数模板有两个重载。通过 std::enable_if
,我们能够让编译器在模板类型是整型时选择第一个重载,在模板类型是浮点型时选择第二个重载。
例子 2:检查类型是否有成员函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <type_traits> #include <iostream> template <typename , typename T>struct has_serialize { }; template <typename C, typename Ret, typename ... Args>struct has_serialize <C, Ret (Args...)> {private : template <typename T> static constexpr auto check (T*) -> typename std::is_same< decltype (std::declval<T>().serialize(std::declval<Args>()...)) , Ret >::type ; template <typename > static constexpr std::false_type check (...) ; typedef decltype (check<C>(0 )) type ; public : static constexpr bool value = type::value; }; struct MyType { void serialize (int ) {} }; int main () { std::cout << has_serialize<MyType, void (int )>::value << std::endl; }
在这个例子中,我们使用模板特化和 decltype
来检查 MyType
是否有一个接受 int
参数的 serialize
成员函数,decltype
检测函数返回类型时,函数只需声明即可,无需实现。
C++17 中的 SFINAE 友好的特性 C++17 引入了 if constexpr
和 auto
类型推导的新特性,让 SFINAE 使用起来更为方便。
例子 3:使用 if constexpr
1 2 3 4 5 6 7 8 9 10 template <typename T>auto foo (T t) { if constexpr (std::is_integral_v<T>) { std::cout << "Integral version: " << t << std::endl; } else if constexpr (std::is_floating_point_v<T>) { std::cout << "Floating point version: " << t << std::endl; } else { static_assert (false , "Unsupported type" ); } }
这个例子演示了如何在函数模板内部使用 `
if constexpr` 来在编译时选择执行路径,这样可以在不生成错误的情况下根据类型条件编译代码。
小结 SFINAE 是 C++ 模板编程中一项强大的技术,可以用来在编译时根据类型特性来选择不同的代码路径。虽然 SFINAE 可以使代码变得非常灵活,但它也可能使得代码难以阅读和维护。随着 C++ 标准的发展,引入了更简洁的特性如 if constexpr
和概念(C++20 中的 concepts),它们可以使得之前需要 SFINAE 来实现的功能变得更简单和直观。