【模板进阶】std::void_t

news2024/11/24 19:37:58

一、 s t d : : v o i d _ t std::void\_t std::void_t的源码分析和常规范例

1. s t d : : v o i d _ t 1.std::void\_t 1.std::void_t的源码分析

C + + 17 C++17 C++17引入了 s t d : : v o i d _ t std::void\_t std::void_t,它其实是一个别名模板,源码非常简单,大概如下所示:

//void_t的实现
template<typename... Args> //别名模板
using void_t = void; //无论传入什么void_t都是void

它实际上是一个别名模板,但有个特点,就是无论传入什么都会变成 v o i d void void类型。
通过它的这个特性,我们能够检测到应用 S F I N A E SFINAE SFINAE特性时出现的非法类型,也就是说,传入的类型必须是有效的类型,而不是非法类型。


2.常规范例

2.1 使用 s t d : : v o i d _ t std::void\_t std::void_t来判断类内是否有某个类型别名

考虑以下的代码:

//判断类中是否存在某个类型别名
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 {  //继承false_type

};

//特化版本
template<typename T>
struct HasTypeMem <T, std::void_t<typename T::type>> :std::true_type { //继承true_type
    
};

void Test1() {

    //type成员是static constexpr(静态常量)
    std::cout << HasTypeMem<NoInnerType>::value << "\n"; //没有type成员,所以会调用泛化版本

    std::cout << HasTypeMem<HaveInnerType>::value << "\n"; //有type成员,所以会调用特化版本

}

这里我们的 H a s I n n e r T y p e HasInnerType HasInnerType内部有一个类型别名 t y p e type type,而 N o I n n e r T y p e NoInnerType NoInnerType内部没有类型别名。

H a s T y p e M e m HasTypeMem HasTypeMem是用于判断是否类内具有 t y p e type type类型的模板,其泛化版本继承了 s t d : : t r u e _ t y p e std::true\_type std::true_type,而特化版本继承了 s t d : : f a l s e _ t y p e std::false\_type std::false_type

因此我们在实例化 H a s T y p e M e m HasTypeMem HasTypeMem模板的时候,就可以调用其内部的 v a l u e value value静态变量来查看是否存在 t y p e type type这个类型别名。


重点是这个地方:
在这里插入图片描述因为我们在实例化 T T T类型为 H a s I n n e r T y p e HasInnerType HasInnerType类型时,编译器发现 H a s I n n e r T y p e HasInnerType HasInnerType类型内部的确有一个 t y p e type type类型,因此会实例化这个特化版本。
相反,实例 N o I n n e r T y p e NoInnerType NoInnerType模板时,由于无法实例化特化的模板,就会实例化泛化的模板了。


调用结果如下:
在这里插入图片描述


当然,如果你喜欢宏定义,也可以使用宏定义来取个别名,注意这里的"\"之后必须紧跟换行,而##用于连接宏参数:

#define _HAS_TYPE_MEM_(parMtpNm) \
template<typename T, typename = 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);

void Test2() {
    std::cout << HTM_type<NoInnerType>::value << "\n";
    std::cout << HTM_type<HaveInnerType>::value << "\n"; //存在type名称的静态常量

    std::cout << HTM_sizetype<NoInnerType>::value << "\n";
    std::cout << HTM_sizetype<HaveInnerType>::value << "\n"; //不存在sizetype名称的静态常量

}

2.2 判断某个类中是否存在某个成员变量

类似地,我们可以借助 d e c l t y p e decltype decltype s t d : : v o i d t std::void_t std::voidt来判断是否一个类内具有某个成员变量:

//判断某个类中是否存在某个成员变量

//泛化版本
template<typename T, typename U = std::void_t<>>
struct HasMember :std::false_type {};

//特化版本
template<typename T>
struct HasMember<T,std::void_t<decltype(T::m_i)>> :std::true_type {};

void Test3() {
    std::cout << HasMember<NoInnerType>::value << "\n"; //存在成员变量,所以调用特化版本

    std::cout << HasMember<HaveInnerType>::value << "\n"; //不存在成员变量,调用了泛化版本
}

这里使用 d e c l t y p e decltype decltype来推导 T : : m _ i T::m\_i T::m_i的类型,如果存在这个名字的成员变量,就会实例化特化版本,运行结果如下:
在这里插入图片描述


2.3 判断类中是否存在某个成员函数

而成员函数如何推导为类型呢? 可以回顾之前学习的 d e c l v a l declval declval:declval的使用

我们这里使用 d e c l v a l declval declval来临时调用 T T T类型的 m y f u n c ( ) myfunc() myfunc()函数,实际上并没有调用,配合 d e c l t y p e decltype decltype我们可以轻松推导出这个成员函数的类型。参考下方代码:

//判断类中是否存在某个成员函数

//泛化版本
template<typename T,typename U = std::void_t<>>
struct HasMemFunc:std::false_type {};

//特化版本
template<typename T>
struct HasMemFunc<T, decltype(std::declval<T>().myfunc())> : std::true_type {};

void Test4() {
    std::cout << HasMemFunc<NoInnerType>::value << "\n"; //不存在myfunc函数,调用泛化版本

    std::cout << HasMemFunc<HaveInnerType>::value << "\n"; //存在myfunc函数,调用特化版本
}

二、编译器是选择特化类型还是泛化类型

通常我们认为,编译器会优先考虑特化版本,然后才是泛化版本。 的确,大部分情况下是如此的,但是编译器内部有它自己的一套排序方式,并不是绝对如此的。

考虑下方代码:


//编译器如何选择泛化版本还是特化版本

//泛化版本
template<typename T,typename U = int>
struct HasMember2 :std::false_type {};

//特化版本
template<typename T>
struct HasMember2<T, std::void_t<decltype(T::m_i)>> :std::true_type {};

void Test5() {
    std::cout << HasMember2<NoInnerType>::value << "\n"; //存在成员变量,但是仍然调用泛化版本!

    std::cout << HasMember2<HaveInnerType>::value << "\n"; //不存在成员变量,调用了泛化版本
}

这里我们的 N o I n n e r T y p e NoInnerType NoInnerType存在 m _ i m\_i m_i名称的成员类型,但是编译器仍然使用的泛化版本!如下:

在这里插入图片描述

因为在这里,编译器认为第二个参数为 i n t int int比通过 d e c l t y p e ( T : : m _ i ) decltype(T::m\_i) decltype(T::m_i)推导出来要合适,因此优先调用了泛化版本。

三、借助 v o i d _ t void\_t void_t d e c l v a l declval declval实现 i s _ c o p y _ a s s i g n a b l e is\_copy\_assignable is_copy_assignable

3.1 s t d : : i s _ c o p y _ a s s i g n a b l e std::is\_copy\_assignable std::is_copy_assignable的使用

s t d : : i s _ c o p y _ a s s i g n a b l e std::is\_copy\_assignable std::is_copy_assignable C + + C++ C++标准库中的一个类模板,用来判断一个类对象是否可以进行拷贝赋值的。

通常,一个空的类是可以进行拷贝赋值的,或者一个重载了拷贝赋值运算符的类是能够拷贝赋值的:

如下:

//std::is_copy_assignable判断一个类对象是否可以进行拷贝赋值

class ACPABL {

};

class BCPABL {
public:

	BCPABL & operator=(BCPABL const&other) { //赋值运算符
		return *this;
	}
};

当一个类显式的删除赋值运算符,这个类就不能被拷贝:

class CCPABL {
	CCPABL& operator=(CCPABL const& other) = delete; //删除赋值运算符
};

3.2实现 s t d : : i s _ c o p y _ a s s i g n a b l e std::is\_copy\_assignable std::is_copy_assignable

我们可以使用 v o i d _ t void\_t void_t d e c l v a l declval declval来自己实现一个相同功能的类,如果能够发生拷贝
赋值,那么就能满足 v o i d _ t < . . . > void\_t<...> void_t<...>内部的表达式,如下所示:

//实现is_copy_assignable

//泛化版本
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 {//使用T&保证使用左值引用来接受赋值

};

void Test2() {
	std::cout << IsCopyAssignable<ACPABL>::value << "\n";
	std::cout << IsCopyAssignable<BCPABL>::value << "\n";//注意必须是拷贝运算符必须public下的
	std::cout << IsCopyAssignable<CCPABL>::value << "\n"; //删除了拷贝运算符无法赋值
	std::cout << IsCopyAssignable<int>::value << "\n";

}

可以发现,如果可以发生拷贝赋值,那么 d e c l t y p e ( s t d : : d e c l v a l < T & > ( ) = s t d : : d e c l v a l < c o n s t   T & > ( ) ) decltype(std::declval<T\&>() = std::declval<const \ T\&>()) decltype(std::declval<T&>()=std::declval<const T&>())一定能成立,那么就可以实例化出特化版本了。

注意这里需要使用 T & T\& T&来保证 d e c l v a l declval declval返回的一定是左值引用来接受赋值,而不是右值引用。


运行结果如下:

在这里插入图片描述
可以发现,显式删除拷贝赋值函数的类是无法被赋值的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2193670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何为树莓派安装操作系统,以及远程操控树莓派的两种方法,无线操控和插网线操控

文章目录 一、下载树莓派的系统二、将文件下载到SD卡中1.使用官方软件2.其他选择 三、远程连接电脑安装vnc-viewer1.无线操作&#xff08;配置树莓派&#xff0c;开启VNC&#xff09;电脑远程配置2.有线连接&#xff08;需要一根网线&#xff09; 总结 一、下载树莓派的系统 下…

github学生认证(Github Copilot)

今天想配置一下Github Copilot&#xff0c;认证学生可以免费使用一年&#xff0c;认证过程中因为各种原因折腾了好久&#xff0c;记录一下解决方法供大家参考。 p.s.本文章只针对Github学生认证部分遇到的问题及解决方法&#xff0c;不包括配置copilot的全部流程~ 1、准备工作…

【python实操】python小程序之定义类

引言 python小程序之定义类 文章目录 引言一、定义类1.1 题目1.2 代码1.3 代码解释 二、思考2.1 面向对象编程&#xff08;OOP&#xff09;原则2.2 self 参数2.3 内存地址2.4 代码的可读性和可维护性 一、定义类 1.1 题目 小猫爱吃鱼&#xff0c;小猫要喝水&#xff0c;定义不…

Vue (快速上手)

Vue 初识Vue 在Vscode中创建html文件&#xff0c;然后打开该文件&#xff0c;输入英文!即可显示出提示框&#xff0c;选定第一个即可出现默认的html模板。 案例一&#xff1a;值的传递 使用大括号{{}}声明 一个将要被Vue所控制的 DOM 区域&#xff0c;其值可以在<script&…

构建数字化生态平台,开启企业新未来

随着数字化时代的到来&#xff0c;构建数字化生态平台已成为企业获取竞争优势、实现可持续发展的重要途径。数字化生态平台是指利用数字技术构建的&#xff0c;能够整合多方资源&#xff0c;为用户提供一站式服务和价值的开放平台。 数字化生态平台的特点 1.开放性&#xff1a…

Vue/组件的生命周期

这篇文章借鉴了coderwhy大佬的Vue生命周期 在Vue实例化或者创建组件的过程中 内部涉及到一系列复杂的阶段 每一个阶段的前后时机都可能对应一个钩子函数 以下是我根据coderwhy大佬文章对于每一个阶段的一些看法 1.过程一 首先实例化Vue或者组件 在实例化之前 会对应一个钩子函…

Android 组件化利器:WMRouter 与 DRouter 的选择与实践

在移动端开发中&#xff0c;组件化 和 模块化 是常见的开发策略。随着项目的规模增大&#xff0c;不同功能之间的依赖、耦合度会变得越来越复杂&#xff0c;模块间的解耦成为不可避免的需求。路由框架正是在这种需求背景下应运而生。本文我们将讨论两款在Android开发中比较流行…

PyEcharts教程(002):上手PyEcharts

2、上手PyEcharts&#xff08;以jupyter notebook编译&#xff09; 2.1 如何查看pyecharts版本 import pyecharts print(pyecharts.__version__)2.2 上手Pyecharts 首先绘制第一个图表 from pyecharts.charts import Bar # 创建柱形图对象 bar Bar() # 添加x轴 bar.add_xa…

51单片机的红外感应洗手器【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机红外感应传感器继电器LED等模块构成。适用于智能红外感应自动洗手器等相似项目。 可实现功能: 1、红外感应传感器实时检测是否有人体接近&#xff08;距离小于20cm&#xff09; 2、如果有人靠近&#xff0c;继电器自动闭合&#…

ZJYYC2360. 圆球的最大得分

思路&#xff1a;这是一道区间dp的题目。最大的数放在最远处会更优&#xff0c;所以每个小孩可以放在 l 处或 r 处&#xff0c;即这段区间的最左边或最右边。这题可以用记忆化搜索来写&#xff0c;用dp[l][r]来记录 i ~ j 之间调整位置后的最大得分。 #include <bits/stdc.…

学校周赛(3)

​ A: 题目&#xff1a; ​​​​​​​ 解题&#xff1a; 本道题木只需要找到一个*的位置&#xff0c;并且查看这个*是否满足四种情况即可&#xff0c;对与判断的体哦见是四周不出现任何的*,由于每次搜索我们首先搜索到的的最左上角的*,因此我们以左上角的为中心进行讨论…

在VSCode中使用Excalidraw

概述 Excalidraw是一款非常不错的示意图绘制软件&#xff0c;没想到在VSCode中有其扩展&#xff0c;可以在VScode中直接使用。 安装扩展 使用 需要创建.excalidraw.svg、.excalidraw或.excalidraw.png等名称的文件。 搭配手写版使用 自由画笔工具可以配合手写板&#xff0c…

【算法】DFS 系列之 穷举/暴搜/深搜/回溯/剪枝(下篇)

【ps】本篇有 8 道 leetcode OJ。 目录 一、算法简介 二、相关例题 1&#xff09;字母大小写全排列 .1- 题目解析 .2- 代码编写 2&#xff09;优美的排列 .1- 题目解析 .2- 代码编写 3&#xff09;N 皇后 .1- 题目解析 .2- 代码编写 4&#xff09;有效的数独 .1-…

教育领域的技术突破:SpringBoot系统实现

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Excel中的屠龙大招

indirect的地位部分动摇&#xff0c;神坛下已初生大力骑士——“”。 (笔记模板由python脚本于2024年10月06日 18:57:11创建&#xff0c;本篇笔记适合同时喜欢python和Excel的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&…

C++——模拟实现list

1.初步实现结点和链表 namespace jxy {template<class T>struct list_node{T _data;list_node<T>* _prev;list_node<T>* _next;list_node(const T& x T()):_data(x),_prev(nullptr),_next(nullptr){}};template<class T>class list//list的框架本…

C# 雷赛运动控制器 SMC304 新建工程

雷赛SMC304资料下载地址 https://www.leisai.com/cn/cpzx/info_36_itemid_3389_lcids_140_cid_3340.html 在官网下载需要的资料 新建文件 在官网下载的资料中找出需要三个文件 把文件添加到现有的项目中 编译选择x64 将连接雷赛电脑的网口IP号改为&#xff1a;如下图所示

深度学习环境安装

**前置知识&#xff1a; 1、各个软件之间的关系&#xff1a; pytorch/tensorflow库&#xff0c;调用cuda runtime version&#xff0c;接着cuda调用驱动&#xff08;cuda driver version&#xff09;&#xff0c;最后驱动又调用GPU显卡。 Anaconda&#xff1a; 集成了python&…

华为海思:大小海思的双轮驱动战略分析

华为海思,作为华为旗下的半导体设计部门,近年来在芯片设计领域取得了显著成就,成为了中国乃至全球芯片设计的重要力量。实际上,华为海思并非单一实体,而是由两个主要分支构成:大海思和小海思。这两个分支虽然同属华为海思,但在定位、产品布局以及市场策略上有所不同,共…

YOLOv8改进 - 注意力篇 - 引入EMA注意力机制

一、本文介绍 作为入门性篇章&#xff0c;这里介绍了EMA注意力在YOLOv8中的使用。包含EMA原理分析&#xff0c;EMA的代码、EMA的使用方法、以及添加以后的yaml文件及运行记录。 二、EMA原理分析 EMA官方论文地址&#xff1a;EMA文章 EMA代码&#xff1a;EMA代码 EMA注意力机…