SFINAE,即替换失败不是错误(Substitution Failure Is Not An Error)。
它指的是在模板实例化过程中,如果某个替换导致了无效代码,这种情况不会被视为编译错误,编译器会忽略这个候选项,继续寻找其他的模板重载或特化版本。
基本原理
当编译器尝试实例化一个模板时,它会进行参数替换。如果这个替换过程失败了(比如因为某个类型不支持某个操作),这种失败并不会直接导致编译错误。相反,编译器会将这个模板实例从候选列表中移除。这就是SFINAE的基本原理。
最常见的应用场景是函数模板的重载解析和类模板的特化选择。
模板函数重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> #include <type_traits>
template<typename T> auto add(T a, T b) -> decltype(a + b) { return a + b; }
template<typename T> bool add(...) { return false; } int main() { std::cout << add(1, 2) << std::endl; std::cout << std::boolalpha << add(std::string("a"), std::string("b")) << std::endl; }
|
如果传入的参数类型T不支持+操作,当编译器用这些参数类型替换第一个add函数时就会失败,触发SFINAE,转而去尝试其他的重载或模板。
类模板特化
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
| #include <iostream> #include <type_traits> template<typename T, typename Enable = void> class MyClass;
template<typename T> class MyClass<T, typename std::enable_if<std::is_arithmetic<T>::value>::type> { public: static void print(T a, T b) { std::cout << "Arithmetic type: " << a + b << std::endl; } };
template<typename T> class MyClass<T, typename std::enable_if<!std::is_arithmetic<T>::value>::type> { public: static void print(...) { std::cout << "Not an arithmetic type." << std::endl; } }; int main() { MyClass<int>::print(1, 2); MyClass<std::string>::print(); }
|
这里则是对类模板进行了特化,根据类型是否是算数类型来选择不同的模板。
模板匹配规则
当存在多个匹配的模板时,编译器会根据几个基本规则来选择最优匹配:
- 更特化的模板优先:如果一个模板是另一个模板的更特化版本,那么选择更特化的模板。
- 非模板函数优先于模板函数:如果有一个非模板函数和一个模板函数都可以匹配,那么优先选择非模板函数。
- 最少转换优先:如果两个模板或重载函数都可以匹配,但是一个需要更少的参数转换,那么优先选择这个。