SFINAE(Substitution Failure Is Not An Error)是 C++ 模板特化的关键机制。如果在选择模板时定义不正确(例如,当类型替换导致在选择模板阶段出错时),这不是编译错误,而只是使得模板不可供选择。
SFINAE 是 std::enable_if、各种类型检测器(type traits)的基础,能够实现标签分发模式(tag dispatching)。
正确示例:
#include <type_traits> // 对于整数 template<typename T> typename std::enable_if<std::is_integral<T>::value, void>::type foo(T) { std::cout << "Integral "; } // 对于其他类型 template<typename T> typename std::enable_if<!std::is_integral<T>::value, void>::type foo(T) { std::cout << "Not integral "; }
不正确使用: 如果不小心描述 std::enable_if 或特化的条件,可能会意外引起调用模糊(ambiguous overload)或编译错误。
可以使用 SFINAE 在常规(非模板)函数的重载之间进行选择吗?
答案:不可以,SFINAE 仅适用于函数或类模板。对于普通的重载函数,SFINAE 不起作用。人们常常错误地试图在普通函数的参数中编写 std::enable_if,这不会引发选择,而是使得签名不明确。
错误示例:
void foo(int, typename std::enable_if<true, int>::type* = nullptr) {} // 错误:不是模板
故事
在序列化库中实现 SFINAE 的模板函数时,错误地未考虑两种特化的条件交集。结果——模糊重载情况和在出现新数据类型时无法构建模块。
故事
在旧代码的 boost::asio 库中,由于通过 dependent false 在 static_assert 中错误实现 SFINAE,出现了无法阅读的错误信息:编译器展开了所有模板,而不是删除不正确的选项。通过在签名层面上单独的 enable_if 进行修复。
故事
在将 SFINAE 类型的算法搜索移植到旧编译器(MSVC 2012)时,团队遇到了类型解构不正确的问题,选定的模板接受了错误类型。检查通过在编译前使用类型特征解决,消除了模板中的替换。