目录
std::void_t
源码分析和常规范例
源码
// ALIAS TEMPLATE void_t template <class... _Types> using void_t = void;
- 功能:能够检测到应用SFINAE(替换失败并不是一个错误)特性时出现的非法类型。换句话说,给进来的类型 _Types 必须是一个有效的类型,不能是一个非法类型。
判断类中是否存在某个类型别名
struct NoInnerType { int m_i; }; struct HaveInnerType { using type = int; //类型别名 void myfunc() {} }; //泛化版本 template <typename T,typename U = std::void_t<> > struct HasTypeMem : std::false_type //struct 默认是public ,class默认是private继承 { }; //特化版本 template <typename T> struct HasTypeMem<T, std::void_t<typename T::type> > : std::true_type { };
- 调用
cout << HasTypeMem<NoInnerType>::value << endl; //0 cout << HasTypeMem<HaveInnerType>::value << endl; //1
- 对于HasTypeMem<NoInnerType>::value 在面对特化版本时,由于没有成员类型别名type,T::type为非法类型,所以选择忽略特化版本,从而实例化泛化版本,泛化版本继承false_type,其父类有成员value,值为0;
- 对于HasTypeMem<HaveInnerType>::value在面对特化版本时,有成员类型别名type,从而std::void_t<typename T::type>检测不是非法类型,其值为void,从而实例化特化版本,其父类有成员value,值为1;
上面类型成员固定了为type,下面通过宏进行一般化。
//带参数的宏定义,注意 反斜杠 表示下一行接着本行来,是本行的一部分 #define _HAS_TYPE_MEM_(parMTpNm) \ template <typename T,typename U = std::void_t<> > \ struct HTM_##parMTpNm : std::false_type {}; \ template <typename T> \ struct HTM_##parMTpNm<T, std::void_t<typename T::parMTpNm> > : std::true_type{};
- 调用
_HAS_TYPE_MEM_(type); _HAS_TYPE_MEM_(sizetype); //_HAS_TYPE_MEM_(type);展开后 /*template <typename T, typename U = std::void_t<> > struct HTM_type : std::false_type{}; template <typename T> struct HTM_type<T, std::void_t<typename T::type> > : std::true_type{};*/ //_HAS_TYPE_MEM_(sizetype);展开后 /*template <typename T, typename U = std::void_t<> > struct HTM_sizetype : std::false_type {}; template <typename T> struct HTM_sizetype<T, std::void_t<typename T::sizetype> > : std::true_type {};*/ cout << HTM_type<NoInnerType>::value << endl; cout << HTM_type<HaveInnerType>::value << endl; cout << HTM_sizetype<NoInnerType>::value << endl; cout << HTM_sizetype<HaveInnerType>::value << endl;
判断类中是否存在某个成员变量
struct NoInnerType { int m_i; }; struct HaveInnerType { using type = int; //类型别名 void myfunc() {} }; //泛化版本 template <typename T,typename U = std::void_t<> > struct HasTypeMem : std::false_type //struct 默认是public ,class默认是private继承 { }; //特化版本 template <typename T> struct HasTypeMem<T, std::void_t<decltype(T::m_i)> > : std::true_type { };
- 调用
cout << HasMember<NoInnerType>::value << endl; // 1 cout << HasMember<HaveInnerType>::value << endl; // 0
- NoInnerType中有成员变量m_i。所以decltype(T::m_i)为int,是一个合法类型,因此实例化特化版本(实例化为:HasMember<HaveInnerType,void>)。
- HaveInnerType没有成员变量m_i。所以decltype(T::m_i)是一个非法类型,从而std::void_t<decltype(T::m_i)>是一个非法类型,根据SFINAE特性,忽略特化版本,实例化特化版本。
- 可通过宏使其一般化,可以判断是否存在其他的成员变量。
判断类中是否存在某个成员函数
struct NoInnerType { int m_i; }; struct HaveInnerType { using type = int; //类型别名 void myfunc() {} }; //泛化版本 template <typename T, typename U = std::void_t<> > struct HasMemFunc : std::false_type { }; //特化版本 template <typename T> struct HasMemFunc<T, std::void_t<decltype(std::declval<T>().myfunc())> > : std::true_type { };
- 调用
cout << HasMemFunc<NoInnerType>::value << endl; cout << HasMemFunc<HaveInnerType>::value << endl;
【注】泛化版本和特化版本到底编译器如何选择
- 编译器通过一种复杂的排序规则来决定使用类模板的泛化版本还是特化版本。
- 一般来说,void跟任何其他类型比,都是最后选择的那个。所以上面的泛化版本中默认参数为 U = std::void_t<> 等价于 U = void。
- 实际情景下可以通过写测试代码进行判断。