- 作者: 雪山肥鱼
- 时间:2022223 22:25
- 目的: void_t
# 源码分析和常规范例
## 判断类中是否存在某个类型别名
## 判断类种是否存在某个成员变量
## 判断类种是否存在某个成员函数
# 泛化版本和特化版本编译器的选择
# 借助 void_t 和 declval实现 is_copy_assignable
# 综合范例
源码分析和常规范例
c++17中引入:
template <class... _Types>
using void_t = void;
无论传进去声明,给的都是 void。
功能: 能够检测到应用SFINAE (替换失败并不是一个错误)特性时出现的非法类型。给进来的必须是有效类型,而不能是非法类型
判断类中是否存在某个类型别名
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
{
};
//特化版本
template <typename T>
struct HasTypeMem<T, std::void_t<typename T::type>> :std::true_type
{
};
int main(int argc, char **argv) {
//value 来自于父类 std::false_type 和 std::true_type
cout << HasTypeMem<NoInnerType>::value << endl;
cout << HasTypeMem<HaveInnerType>::value << endl;
return 0;
}
输出 0 ,1
分析:
NoInnerType 直接匹配到了 泛化, 因为其内部 没有 type类型。
此时输出的false_type 的值 为 0
HaveInnerType 匹配到了特化,因为其内部有type。
此时输出 true_type的值为 1
另一种写法:用宏去写
#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::parMTpNm> > : 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::parMTpNm> > : std::true_type {};*/
int main() {
cout << _nmsp1::HTM_type<_nmsp1::NoInnerType>::value << endl;
cout << _nmsp1::HTM_type<_nmsp1::HaveInnerType>::value << endl;
cout << _nmsp1::HTM_sizetype<_nmsp1::NoInnerType>::value << endl;
cout << _nmsp1::HTM_sizetype<_nmsp1::HaveInnerType>::value << endl;
}
判断类种是否存在某个成员变量
template <typename T, typename U = std::void_t<> >
struct HasMember : std::false_type //HasMember<HaveInnerType,void>
{
};
//特化版本
template <typename T>
struct HasMember<T, std::void_t<decltype(T::m_i)> > : std::true_type //HasMember<NoInnerType,void>
{
};
cout << _nmsp1::HasMember<_nmsp1::NoInnerType>::value << endl;
cout << _nmsp1::HasMember<_nmsp1::HaveInnerType>::value << endl;
判断类种是否存在某个成员函数
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 << _nmsp1::HasMember<_nmsp1::NoInnerType>::value << endl;
cout << _nmsp1::HasMember<_nmsp1::HaveInnerType>::value << endl;
泛化版本和特化版本编译器的选择
疑问 : 把 void_t 换成 如下:
//template <typename T, typename U = std::void_t<> >
template <typename T, typename U = int >
//template <typename T, typename U = void >
struct HasMember : std::false_type //HasMember<HaveInnerType,void>
{
};
//特化版本
template <typename T>
struct HasMember<T, std::void_t<decltype(T::m_i)> > : std::true_type //HasMember<NoInnerType,void>
{
};
结果是 0 , 0 选到1 泛化版本 .
编译器认为 推测出来的int 比 void_t(返回 void) 更为合适。
void 比其他类型 都弱一些
编译器有自己的选择,不用深究。
都是void 则,编译器则会优先考虑 特化版本 void_t
借助 void_t 和 declval实现 is_copy_assignable
is_copy_assignable 类模板 用来判断一个类对象是否能够进行拷贝赋值
is_copy_assignable 举例:
class ACLABL {
};
class BCLABL {
public:
BCLABL & operator=(const BCLABL & tmpobj) { //拷贝运算符
return *this;
}
};
class CCLACL {
public:
CCLACL & operator=(const CCLACL & tmpobj) = delete;
};
int main(int argc, char ** argv) {
/*
ACLABL aobj1;
ACLABL aobj2;
aobj2 = aobj1;
BCLABL bobj1;
BCLABL bobj2;
bobj2 = bobj1;
CCLACL cobj1;
CCLACL cobj2;
cobj2 = cobj1; //有问题 ,不可以做 opertor= 运算
*/
cout << "int:" << std::is_copy_assignable<int>::value << endl;
cout << "ACLBL" << std::is_copy_assignable<ACLABL>::value << endl;
cout << "BCLABL" << std::is_copy_assignable<BCLABL>::value << endl;
cout << "CCLACL" << std::is_copy_assignable<CCLACL>::value << endl;
return 0;
}
自行实现is_copy_assignable
class ACLABL {
};
class BCLABL {
public:
BCLABL & operator=(const BCLABL & tmpobj) { //拷贝运算符
return *this;
}
};
class CCLACL {
public:
CCLACL & operator=(const CCLACL & tmpobj) = delete;
};
//泛化版本
template <typename T, typename U = std::void_t<>>
struct IsCopyAssignable : std::false_type {
};
//特化版本
template <typename T>
struct IsCopyAssignable<T, std::void_t<decltype(std::declval<T&>() = std::declval<const T&>())>>:std::true_type
{
};
int main(int argc, char ** argv) {
cout << "int:" << IsCopyAssignable<int>::value << endl;
cout << "ACLBL:" << IsCopyAssignable<ACLABL>::value << endl;
cout << "BCLABL:" << IsCopyAssignable<BCLABL>::value << endl;
cout << "CCLACL:" << IsCopyAssignable<CCLACL>::value << endl;
return 0;
}
代码解读:
//核心:
decltype(declval<T&>() = declval<const T &>())
因为 declval<T&>() 返回的是 T&,即左值类型,declval<const T&> 同样返回的是 const T &
相当于 调用了 aobj1 = abj2 ,即aobj1.operator=(obj2)。如果有copy赋值,则会自动匹配到特化版本的模板。同时因为继承了 std::true_type,有 则输出 1,无则走 泛化版本 std::false_type 为0.
综合范例
需求:两个vecotr容器。一个int,一个double。希望重载加法运算符。a容器的第一个元素和b容器的的一个元素相加,以此类推。
基础类型:
/*
//问题:到底返回声明类型 int? double?
template<typename T, typename U>
vector<T> operator +(vector<T> const & vec1, vector<U> const & vec2) {
}
*/
//基本类型
template<typename T, typename U>
struct VecAddResult {
using type = decltype(T()+U());//把结果类型的推导交给编译器
};
//设计一个类模板,返回 int+double 的类型
template<typename T, typename U>
vector<typename VecAddResult<T, U>::type> operator +(vector<T> const & vec1, vector<U> const & vec2) {
vector<typename VecAddResult<T, U>::type> tmpvec;
return tmpvec
}
int main(int argc, char ** argv) {
return 0;
}
面对问题:无法确定返回值类型,所以引入另一个新类模板。
当需要的是类类型:
#if 1
//类类型,两个vector中用相同的类进行相加。
struct elemC {
elemC operator+(const elemC & tmp);
};
//基本类型
template<typename T, typename U>
struct VecAddResult {
using type = decltype(declval<T>() + declval<U>());//把结果类型的推导交给编译器
};
//设计一个类模板,返回 int+double 的类型
template<typename T, typename U>
vector<typename VecAddResult<T, U>::type> operator +(vector<T> const & vec1, vector<U> const & vec2) {
vector<typename VecAddResult<T, U>::type> tmpvec;
return tmpvec
}
int main(int argc, char ** argv) {
vector<elemC> veca;
vector<elemC> vecb;
veca + vecb;
return 0;
}
注意:需要重载 类种的 +
当然可以用别名模板:
//类类型,两个vector中用相同的类进行相加。
struct elemC {
elemC operator+(const elemC & tmp);
};
//基本类型
template<typename T, typename U>
struct VecAddResult {
using type = decltype(declval<T>() + declval<U>());//把结果类型的推导交给编译器,这里需要用到 elemc opertor+
};
template <typename T, typename U>
using VectAddResult_t = typename VecAddResult<T, U>::type;
//设计一个类模板,返回 int+double 的类型
template<typename T, typename U>
vector< VectAddResult_t<T,U> > operator +(vector<T> const & vec1, vector<U> const & vec2) {
vector<VectAddResult_t <T, U>> tmpvec;
return tmpvec
}
int main(int argc, char ** argv) {
vector<elemC> veca;
vector<elemC> vecb;
veca + vecb;
return 0;
}
改进:
现在的缺陷是 veca+vecb 由于重载了 opertor+,所以才能相加。但去掉了 opertor+ 则不能相加。报错位置粗暴,并不是在 + 这里报错。
希望通过SFINAE特性,检测两个对象能不能相加。
两模板联动,产生 ISFNAE 友好关系:
/*
template<typename T, typename U>
struct VecAddResult {
using type = decltype(declval<T>() + declval<U>());//这里需要用到 elemc opertor+
};
*/
//template<typename, typename, typename V = std::void_t<>>//因为T U 根本没用到
template <typename T, typename U, typename V = std::void_t<>>
struct IfCanAdd : std::false_type {
};
template<typename T, typename U>
struct IfCanAdd<T, U, void_t< decltype( declval<T>() + declval<U>()) >> :std::true_type {
};
//泛化版本, 与 IfCanAdd 联动 ,sfinae 友好关系
template <typename T, typename U, bool ifcando = IfCanAdd<T, U>::value>
struct VectAddResult {
//能相加 就定义type
using type = decltype(declval<T>(), declval<U>());
};
//特化版本
template <typename T, typename U>
struct VectAddResult<T, U, false> {
//不能相加,就无法定义type,重载 opertor+ 就不会被选中,因为 opertor+返回的类型 是 VectAddResutl
};
struct elemC {
elemC operator+(const elemC & tmp);
};
template <typename T, typename U>
using VectAddResult_t = typename VectAddResult<T, U>::type;
template<typename T, typename U>
vector< VectAddResult_t<T,U> > operator +(vector<T> const & vec1, vector<U> const & vec2) {
vector<VectAddResult_t <T, U>> tmpvec;
return tmpvec;
}
int main(int argc, char ** argv) {
vector<elemC> veca;
vector<elemC> vecb;
veca + vecb;
return 0;
}
注释分析:
- 泛化/特化 IfCanAdd 与 泛化/特化vectAddResult 联动,产生 isfnae友好关系
- 返回 true,IfcanAdd为 true,则 在vecaddResult 中 有 type类型,如果是false 则 vecaddResult 没有 type类型。
- operator能调用,的前提是返回类型是 vectAddReuslt中 有type类型。
有type类型,才会选中opertor+. 否则,不会被选中。
如果注释掉 重载的 elemC 中的 opertor + 则编译器会报错,报错位置在 veca+vecb ,这样才是合理的。
编写模板时,如果选择要实例化某个模板(opertor+, VecAddResult),实例化函数中不应该报错,顶多报无法 实例化,比如上述代码例子。
两个对象不能相加,直接报错在
a+b;
而不是报错在:using type 这里.
//泛化版本, 与 IfCanAdd 联动 ,sfinae 友好关系
template <typename T, typename U, bool ifcando = IfCanAdd<T, U>::value>
struct VectAddResult {
//能相加 就定义type
using type = decltype(declval<T>(), declval<U>());
};
//特化版本
template <typename T, typename U>
struct VectAddResult<T, U, false> {
//不能相加,就无法定义type,重载 opertor+ 就不会被选中
};