C++ SFINAE:std::enable_if在模板参数与返回值中的区别
两种
enable_if实现的区别与选择建议 先把两种实现的完整形式明确一下,你提到的第二种应该是返回值位置的SFINAE写法,我先补全:
第一种实现(模板参数默认值)
#include <type_traits> #include <cstdio> template<class T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> void test( const T& t ){ printf("int\n"); } template<class T, typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0> void test( const T& t ){ printf("float\n"); }
第二种实现(返回值SFINAE)
#include <type_traits> #include <cstdio> template<class T> typename std::enable_if<std::is_integral<T>::value>::type test(const T& t) { printf("int\n"); } template<class T> typename std::enable_if<std::is_floating_point<T>::value>::type test(const T& t) { printf("float\n"); }
接下来聊聊两者的核心区别:
SFINAE触发位置不同
第一种是在模板参数列表里触发SFINAE:当类型条件不满足时,模板参数的类型推导会直接失败,编译器会跳过这个模板,不会报错;第二种是在返回值类型上触发SFINAE:如果类型条件不满足,返回值的类型推导失败,同样会跳过该模板。重载兼容性差异
如果后续需要给test添加非模板重载(比如void test(const std::string& s)),两种写法都没问题,但涉及复杂模板重载场景时,模板参数默认值的方式更灵活——返回值SFINAE要求每个重载的返回值类型必须能被正确推导,而模板参数的写法不会限制返回值(当前例子都是void,差异不大,但如果返回值不同时区别就会显现)。代码风格与可读性
第一种写法把类型判断逻辑放在模板参数里,函数签名的主体(返回值、参数列表)更简洁;第二种把条件和返回值绑定在一起,习惯看返回值的人能一眼知道函数生效的类型范围。这更多是个人或团队的风格偏好。编译器支持的历史差异
在C11刚推出时,部分编译器对返回值SFINAE的支持有小bug,但到C17及以后,两种写法的编译器兼容性都非常完善了,这个问题现在基本可以忽略。
该怎么选择?
- 如果函数返回值是
void,或者多个重载返回类型一致,两种写法都可以,选你看着顺眼的就行。 - 如果函数需要不同返回类型,或者你想把类型条件和返回值更紧密绑定,优先选返回值SFINAE的写法。
- 如果后续可能扩展模板参数,或者和可变参数模板等特性结合,模板参数默认值的方式更灵活,因为它不会占用返回值的位置。
- 另外,如果你用的是C++20及以上,更推荐用
requires表达式替代enable_if,可读性更强:template<class T> void test(const T& t) requires std::is_integral_v<T> { printf("int\n"); } template<class T> void test(const T& t) requires std::is_floating_point_v<T> { printf("float\n"); }
内容的提问来源于stack exchange,提问作者Martin Perry
相关产品推荐
相关产品推荐

