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+ 就不会被选中
};
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容