为什么SFINAE场景下检测非法类型需要使用std::void_t?
核心原理:模板偏特化的匹配规则
问题本质是模板偏特化需要和主模板的默认参数类型匹配才能被选中:
- 你的主模板定义为
template<class T, class = void> struct is_callable: std::false_type {};,当你使用is_callable<某类型>时,第二个模板参数默认传入的是void - 只有偏特化的第二个模板参数推导结果也为
void时,编译器才会选择这个偏特化实现,否则就会回退到主模板的false_type
版本B(无std::void_t)不生效的原因
版本B的偏特化第二个参数是 decltype(&T::operator()),这个表达式的结果是「T类型的调用运算符对应的成员函数指针类型」,比如对于普通的无参lambda,这个类型可能是 void (lambda_type::*)() const,永远不可能等于 void。
所以不管T是否存在可寻址的operator(),这个偏特化的第二个参数类型永远和主模板默认的void不匹配,编译器永远只会选主模板,所以萃取结果永远是false。
std::void_t的作用
std::void_t是C++17引入的工具元函数,它的定义等价于:
template<class...> using void_t = void;
无论你给它传入多少个合法的类型参数,它最终的类型都是void。
放在你的版本A里的效果是:
- 如果
decltype(&T::operator())合法(即T存在可寻址的operator()),那么std::void_t<decltype(&T::operator())>的结果就是void,刚好匹配主模板的默认第二个参数,偏特化被选中,萃取结果为true_type - 如果
decltype(&T::operator())不合法,这个偏特化会被SFINAE规则排除,编译器回退到主模板,萃取结果为false_type
刚好完全符合检测「类型是否可调用」的需求。
注:这个实现仅能检测只有单个
operator()重载的类型,如果类型重载了多个不同签名的operator(),取地址&T::operator()会产生歧义,检测会失败。
内容的提问来源于stack exchange,提问作者oliora
相关产品推荐
相关产品推荐

