为何修改SFINAE实现中的decltype表达式后结果不符合预期?
咱们先把问题拆解清楚,先看你原来的正确实现:
#include <type_traits> #include <iostream> using namespace std; struct A { using key_type = int; }; template<typename T, typename = void> struct has_key_type : false_type {}; template<typename T> struct has_key_type<T, decltype(typeid(typename T::key_type), void())>: true_type {}; int main() { cout << has_key_type<A>::value << endl; cout << has_key_type<int>::value << endl; }
这段代码能输出1 0,核心是利用了SFINAE(替换失败并非错误)的特性:当T包含key_type成员时,特化模板的第二个参数会被推导为void——和主模板的默认参数void完全匹配,所以编译器会优先选择这个特化版本;如果T没有key_type,特化模板的替换会失败,编译器就会回退到主模板的false_type。
那为什么把void()改成int()后就失效了呢?
当你把特化里的decltype(typeid(typename T::key_type), void())改成decltype(typeid(typename T::key_type), int())时,这个表达式的类型变成了int。这时候问题就出在模板参数匹配上:
主模板的第二个参数默认是void,当你实例化has_key_type<A>时,特化模板的第二个参数是int,和主模板的默认参数类型void不匹配——编译器找不到和has_key_type<A, void>匹配的特化版本,只能直接使用主模板的false_type,所以输出变成了0 0。
简单说,SFINAE的特化版本要能和主模板的默认参数类型对上,才能被优先选中。原来的代码里特化参数和主模板默认参数都是void,所以替换成功时会触发特化;改成int()后,特化参数是int,和主模板的void不匹配,哪怕T::key_type存在,这个特化也不会被选用。
如果想让修改后的代码正常工作,只需要让特化的第二个参数和主模板的默认参数类型一致就行,比如把主模板的默认参数改成int:
#include <type_traits> #include <iostream> using namespace std; struct A { using key_type = int; }; // 主模板默认参数改为int template<typename T, typename = int> struct has_key_type : false_type {}; template<typename T> struct has_key_type<T, decltype(typeid(typename T::key_type), int())>: true_type {}; int main() { cout << has_key_type<A>::value << endl; cout << has_key_type<int>::value << endl; }
这时候输出就会回到预期的1 0了。
内容的提问来源于stack exchange,提问作者xmllmx

