为何`std::enable_if`需默认值?模板推导报错求解
问题解答
1. 为什么std::enable_if需要使用默认值?
std::enable_if的核心是靠SFINAE(替换失败并非错误)机制,在模板实例化时过滤掉不符合条件的重载。当你把enable_if放在模板参数列表里时,必须给它加默认值,根源在于模板参数推导规则:
- 模板参数分为类型参数和非类型参数,你添加的
enable_if相关参数并没有出现在函数的参数列表中,编译器没办法从函数调用里推导它的类型或值; - 如果不给默认值,编译器会因为无法推导这个参数直接报错,根本到不了SFINAE的环节;而默认值的存在,让编译器可以直接用这个值完成模板实例化,同时当
enable_if条件不满足时,由于enable_if::type不存在,会触发SFINAE,这个重载会被自动忽略。
2. 使用std::enable_if必须用::type = 0吗?
当然不是!::type = 0只是非类型模板参数写法的一种,还有很多写法不需要这种默认值,常见的替代方式包括:
- 放在返回值位置:不需要默认值,直接用
enable_if_t作为返回类型,条件不满足时自动触发SFINAE:template <typename T> std::enable_if_t<std::is_integral<T>::value> do_stuff(T t) { std::cout << "do_stuff integral\n"; } - 作为类型模板参数的默认值:这里默认值是
enable_if_t(本质是void),同样能触发SFINAE:// 注意:如果用于多个重载,要避免模板签名重复,比如可以加个占位参数区分 template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>> void do_stuff(T t) { std::cout << "do_stuff integral\n"; } - 放在函数参数的默认值中:不需要额外的模板参数默认值:
template <typename T> void do_stuff(T t, std::enable_if_t<std::is_integral<T>::value>* = nullptr) { std::cout << "do_stuff integral\n"; }
你看到的无需默认值的示例,大多用了上述写法,它们不需要在模板参数列表中给enable_if相关参数加默认值。
3. 你的代码为什么报错?
你的代码问题出在非类型模板参数的类型依赖于被推导的模板参数T:
template <typename T, typename std::enable_if<std::is_integral<T>::value, T>::type = 0> void do_stuff(T t) { ... }
这里的第二个模板参数是非类型参数,它的类型是std::enable_if<..., T>::type(也就是T本身),而T是需要从函数调用中推导的。编译器在推导时陷入了循环:
- 要确定这个非类型参数的类型,必须先知道
T是什么; - 但要推导
T,编译器又需要先确定这个非类型参数的类型,才能判断是否能使用默认值0。
最终编译器无法完成推导,所以报出了"couldn't deduce template parameter ‘
解决这个问题很简单,只需要把enable_if的第二个参数改成不依赖于T的固定类型(比如int):
#include<iostream> #include<type_traits> template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> void do_stuff(T t) { std::cout << "do_stuff integral\n"; } template <typename T, typename std::enable_if<std::is_class<T>::value, int>::type = 0> void do_stuff(T t) { std::cout << "do_stuff class\n"; } int main() { do_stuff(32); return 0; }
这样第二个参数的类型是固定的int,编译器不需要依赖T就能确定它的类型,直接使用默认值0完成实例化,同时触发SFINAE过滤掉不满足条件的重载,代码就能正常编译运行了。
内容的提问来源于stack exchange,提问作者MiCha
相关产品推荐
相关产品推荐

