void_t

  • 作者: 雪山肥鱼
  • 时间: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;
}
图片.png

自行实现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;
}

注释分析:

  1. 泛化/特化 IfCanAdd 与 泛化/特化vectAddResult 联动,产生 isfnae友好关系
  2. 返回 true,IfcanAdd为 true,则 在vecaddResult 中 有 type类型,如果是false 则 vecaddResult 没有 type类型。
  3. 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+ 就不会被选中
};
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容