C++之std::type_identity
目录
1.简介
2.C++20的std::type_identity
3.使用 type_identity
3.1.阻止参数推导
3.1.1.模板参数推导过程中的隐式类型转换
3.1.2.强制显式实例化
3.2.阻止推断指引
3.3.类型保持
3.4.满足一些稀奇古怪的语法
4.示例
5.总结
1.简介
std::type_identity
是 C++17 引入的一个实用工具,用于确保类型别名保持其引用的完整性。在某些模板元编程的场景中,尤其是在与类型萃取(type traits)和完美转发(perfect forwarding)相关的场景中,保持类型的“原样”传递是非常重要的。
std::type_identity
是一个简单的模板,它定义了一个别名 type
,该别名简单地重新声明了其模板参数类型。但重要的是,它不会修改或“破坏”传递给它的类型。
在 C++17 之前,要实现这样的效果通常需要一些技巧,比如使用结构体的模板特化或复杂的继承层次结构。但是,std::type_identity
使得这个过程变得更加简单和直观。
我们用一个例子来说明这个问题,尽管这个例子有点微不足道:
template <class T>
T Add(T a, T b) {return a + b;
}Add(4.2, 1); //错误
尽管将整数与浮点数累加并返回浮点数是 Add 函数应该支持的运算,但是编译器却在这里报错,因为在参数推导的时候,它根据第一个参数推导出 T 是 double 类型,但是根据第二个参数推导出 T 是 int 类型,这产生了矛盾,于是编译器罢工了。
解决这个问题的方法很简单,比如我们可以谢绝自动推导,用显式实例化(Explicit instantiation)的方式,只是每次调用的时候会麻烦一点:
foo<double>(4.2, 1);
当然,更常用的实践是借助 C++ 的非推导语境(non-deduced contexts)规避不希望的参数推导,比如下面这种 identity 惯用法:
template< class T >
struct identity {using type = T;
};template <class T>
T Add(T a, typename identity<T>::type b) {return a + b;
}//相当于 double Add(double a, double b)
foo(4.2, 1); //OK, T 被推导为 double
根据 identity 的定义,identity<T>::type 其实就是 T,为什么加上这个多此一举的东西就 OK 了?这并不是什么黑魔法,它只是借助了模板参数推导规则中最常用的一种非推导语境,即:
对于模板参数中出现的嵌套类型表达式,域解析运算符(::)左边的嵌套名称说明符如果是个限定性说明符(Qualified identifiers),则该嵌套名称说明符不参与模板参数推导。
所以用了 identity 大法之后,"::type" 左边的 identity<T> 就不参与模板参数推导,T 就是根据第一个参数推导出的 double,identity<T>::type 就也是 double 了。
2.C++20的std::type_identity
C++ 20 的 type traits 增加了一个 type_identity,其作用和上一节中自定义的 identity<T> 一样,只是不用重复发明轮子了。直接使用 type_identity 的代码是这个样子的:
template <class T>
T Add(T a, typename std::type_identity<T>::type b) {return a + b;
}
C++ 还提供了一个别名:
template< class T >
using type_identity_t = typename type_identity<T>::type;
使用 type_identity_t<T> 的代码更简单一点:
template <class T>
T Add(T a, std::type_identity_t<T> b) {return a + b;
}
3.使用 type_identity
3.1.阻止参数推导
3.1.1.模板参数推导过程中的隐式类型转换
其实建立非推导语境的常用目的是让一个模板参数的类型依赖另一个模板参数的推导结果,这种非推导语境的建立还会带来一些意想不到的效果。比如这个例子:
template <typename ...args_t>
void func(std::function<void(args_t...)> function_, args_t ...args){/// do something here
}void func2(std::function<void(int)> function_, int args) {/// do something here
}void test() {func([](int a){ }, 1); //编译错误func2([](int a){ }, 1); //没有问题
}
代码中的 func 和 func2 函数的作用相当于一个调用器(Invoker),区别就是 func 是个函数模板,而 func2 是个普通函数。test 函数中调用 func 会导致编译错误,调用 func2 确实正常的。func2 能正常调用说明从 lambda 到 std::function 的隐式转换是没有问题的,那为什么 func 就不能转换呢?
原因就是在参数推导的时候是不考虑隐式类型转换的,func 是函数模板,它的第一个参数类型 std::function<> 依赖于对模板参数 args_t 的推导结果,推导出来的 std::function<void(args_t...)> 无法与实参传入的 lambda 表达式类型进行匹配,导致推导最终失败,实际上并没有产生一个类似 :
void func(std::function<void(int)> function_, int)
的推导结果,在随后的重载决议时虽然允许隐式转换,但是因为没有一个上述结果能与之进行转换,最终的结果就是编译失败,错误原因是没有一个 func 的实例能匹配 lambda 表达式的调用。func2 能调用成功是因为 func2 是普通函数,此处会进行隐式类型转换。
此时如果让 std::function<void(args_t...)> 不参与推导,那么它就不需要与实参传入的 lambda 表达式进行匹配,也就是不会导致推导错误,就能得到上面的推导结果,于是在随后的重载决议的时候就能通过隐式类型转换完成函数调用。此时就需要用 type_identity 建立非推导语境了,我们将 func 的设计改成这样就可以了:
template <typename... args_t>
void func(std::type_identity_t<std::function<void(args_t...)>> function_, args_t ...args) {/// do something here
}
3.1.2.强制显式实例化
利用 type_identity,还可以在设计上要求用户在使用函数模板的时候必须显式指定模板参数,比如这个例子:
template <typename U, typename V>
void foo(std::type_identity_t<U> u, V v) { ... }foo<double>(5.9, 6); //编译正确
foo<int>(5.9, 6); // 编译正确,此处发生隐式类型转换
foo(5.3, 6.2); //错误
foo 函数从设计上强制用户必须指定第一个参数的类型,目的可能是想允许第一个参数的隐式类型转换,也可能是其他目的,总之,使用 type_identity 可以达到这种效果,使用 foo 函数的用户必须显式指定第一个参数的类型。
3.2.阻止推断指引
C++ 17 引入了一个新的语言特性,就是 CTAD,借助于隐式或显式的推断指引,类模板的模板参数也支持自动推导。但是,隐式的类型推导有可能会产生错误的结果,比如这个 smart_pointer 类的设计:
template <class T>
class smart_pointer {
public:smart_pointer(T* object);//...
}
借助于隐式推断指引,用户可以写出这样的代码,不需要显式指定模板参数 T:
Widget* widget{/* ... */};
smart_pointer ptr{widget};
但是问题是,T* 代表的指针无法区别 object 是单个对象的指针还是数组,因为数组在函数调用的时候也会退化成指针,所以自动推导出来的类型有可能是错误的,比如这样的代码:
Widget widget[N];
smart_pointer ptr{widget};
此时推导类型 T 仍然是 Widget,我们希望的是 Widget[]。
借助于 type_identity,我们可以阻止这种隐式的推断指引,强制用户指定正确的模板参数类型。我们将 smart_pointer 的构造函数修改一下:
smart_pointer(std::type_identity_t<T>* object);
这样上述代码就会产生错误,用户必须这样使用才能正确编译,这也是我们期望的正确结果:
Widget widget[N];
smart_pointer<Widget[]> ptr{widget};
3.3.类型保持
type_identity_t<T> 本质上还是 T,所以可以被用在一些需要短暂记忆并保持类型的场合。资料 [5] 是 Timur Doumler 为推动 type_identity 进入标准而做的提案,它给出了几个利用 type_identity 的类型保持功能,使得 type_identity 可以作为其他元函数(Meta function)的实现基础,比如我们可以模仿标准库实现一个 remove_const 的元函数(type traits):
template <typename T>
struct remove_const : std::type_identity<T> {};template <typename T>
struct remove_const<T const> : std::type_identity<T> {};
大家可能会有疑问,为什么不直接写成这样:
template <typename T>
struct remove_const : T {};template <typename T>
struct remove_const<T const> : T {};
如果写成第二种形式,则这样的断言会失败:
static_assert(std::is_same_v<remove_const<int const>, int>);
因为通过 remove_const<T const> 或 remove_const<T> 得到的类型都是 remove_const<T>,不是 T。使用 type_identity,我们就可以借助于它的 type 类型保持得到原始的 T,这样的断言就是成功的:
static_assert(std::is_same_v<remove_const2<int const>::type, int>);
因为通过 remove_const2<T>::type 可以得到原始类型 T,而不是 remove_const2<T>,那不是我们要的结果。
3.4.满足一些稀奇古怪的语法
type_identity 的其他用法还包括满足一些稀奇古怪的语法形式,比如语法上我们要创建一个临时数组,但是直接写 'T[]{}' 语法形式上就是错误的,因为编译器不能确定 '[]' 的左边是标识符还是类型。但是用 type_identity 中转一下,编译器就确定知道 '[]' 左边是个类型:
template<typename T>
void Print(T v[]) { ... }template<typename T>
void Process(T t) {Print(std::type_identity_t<T[]>{ 1,2,3 }); //编译器能正确理解//PrintInt(T[]{ 1,2,3 }); //语法错误,[] 左边不能确定是类型
}
4.示例
代码如下:
#include <iostream>
#include <type_traits>
#include <functional>template <typename T>
struct type_identity
{using type = T;
};template <typename T>
using type_identity_t = typename type_identity<T>::type;template <typename... args_t>
void func_wrapped(type_identity_t<std::function<void(args_t...)>> function_,args_t ...args)
{std::cout << "typeid(function_).name(): "<< typeid(function_).name() << std::endl;std::cout << "typeid(std::function<void(args_t...)>).name(): "<< typeid(std::function < void(args_t...)>).name() << std::endl;std::cout << "std::is_same<>::value "<< std::is_same< std::function<void(args_t...)>,type_identity_t<std::function<void(args_t...)>>>::value << std::endl << std::endl;// do something here
}void test()
{std::cout << __FUNCTION__ << std::endl;
}int main()
{std::cout << std::boolalpha;func_wrapped([](int a) { }, 1);func_wrapped(test);return 0;
}
输出:
typeid(function_).name(): St8functionIFviEE
typeid(std::function<void(args_t...)>).name(): St8functionIFviEE
std::is_same<>::value truetypeid(function_).name(): St8functionIFvvEE
typeid(std::function<void(args_t...)>).name(): St8functionIFvvEE
std::is_same<>::value true
5.总结
希望大家能够有所收获,笔者水平有限。成文之处难免有理解谬误之处,欢迎大家多多讨论,指教。
推荐文章阅读
std::type_identity
相关文章:

C++之std::type_identity
目录 1.简介 2.C20的std::type_identity 3.使用 type_identity 3.1.阻止参数推导 3.1.1.模板参数推导过程中的隐式类型转换 3.1.2.强制显式实例化 3.2.阻止推断指引 3.3.类型保持 3.4.满足一些稀奇古怪的语法 4.示例 5.总结 1.简介 std::type_identity 是 C17 引入的…...

头歌资源库(10)拼数字
一、 问题描述 二、算法思想 初始化一个长度为10的数组count,用于记录卡片中每个数字的数量。 从1开始依次尝试拼出正整数,直到无法拼出下一个数为止。 对于每个尝试拼出的正整数,遍历其每一位的数字,检查该数字在count中是否还…...
虚谷数据库-定时作业
虚谷数据库提供定时作业机制,用于定时、定期、自动的进行某些操作,可通过系统包 dbms_scheduler 进行定时作业创建、调度、查看、删除等。 DBMS_SCHEDULER 系统包封装了以下过程/函数:ENABLE、DISABLE、SET_JOB_ARGUMENT_VALUE、DROP_JOB、R…...
AWD攻防比赛流程手册
AWD简单介绍: AWD:Attack With Defence,即攻防对抗,比赛中每个队伍维护多台服务器(一般两三台,视小组参赛人数而定),服务器中存在多个漏洞(web层、系统层、中间件层等&am…...
Golang的json解析--Gjson库的使用举例
目录 简介 安装 原生的json解析 Gjson使用举例 基本使用 键路径 使用示例 其他资源 简介 在 Golang 中,解析 JSON 数据是一项非常常见的任务。Go提供了标准的JSON包,可以轻松地将JSON数据序列化和反序列化。但是,在使用标准JSON包…...
基于Langchain构建本地大型语言模型(LLM)问答系统的经验分享
基于Langchain构建本地大型语言模型(LLM)问答系统的经验分享 https://download.csdn.net/download/xziyuan/89334371?spm1001.2101.3001.9500 最近,我一直在探索如何利用Langchain来构建一个本地的大型语言模型问答系统。在这个过程中&…...

对抗式生成模仿学习(GAIL)
目录 1 预先基础知识 1.1 对抗生成网络(GAN) 1.1.1 基本概念 1.1.2 损失函数 1.1.2.1 固定G,求解令损失函数最大的D 1.1.2.2 固定D,求解令损失函数最小的G 1.2 对抗式生成模仿学习特点 2 对抗式生成模仿学习(…...

信息系统项目管理师 | 新一代信息技术
关注WX:CodingTechWork 物联网 定义 The Internet of Things是指通过信息传感设备,按约定的协议,将任何物品与互联网连接,进行信息交互和通信,以实现智能化识别。定位、跟踪、监控和管理的一种网络。物联网主要解决…...

安全宣传咨询日活动向媒体投稿记住这个投稿好方法
在信息爆炸的时代,作为单位的信息宣传员,我肩负着将每一次重要活动,特别是像“安全宣传咨询日”这样的公益活动,有效传达给公众的重任。这份工作看似简单,实则充满了挑战,尤其是在我初涉此领域时,那段曲折而又难忘的投稿经历,至今记忆犹新。 初探投稿之海,遭遇重重困难 起初,我…...
第7章:系统架构设计基础知识-软件架构风格
由于历史原因,研究者和工程人员对Sofiware Architecture(简称SA)的翻译不尽相同,其软件的“体系结构”和“架构”具有相同的含义。 系统架构其实就是系统的结构,系统架构设计其实就是要给相关利益方说清楚通过什么样的结构来解决需求中功能和…...

自制调色小工具给图片加滤镜,修改图片红、绿、蓝通道及亮度,修改图片颜色
上篇: 上篇我们给地图添加了锐化、模糊等滤镜,这篇来写一个小工具给图片调色。 调色比锐化等滤镜要简单许多,直接拿到像素值修改即可。不需要用到卷积核。。。(*^▽^*) 核心原理就是图像结构,使用context.getImageData获取图像像…...

【Redis】java客户端(SpringData和jedis)
https://www.oz6.cn/articles/58 https://www.bilibili.com/video/BV1cr4y1671t/?p16 redis官网客户端介绍:https://redis.io/docs/latest/develop/connect/clients/ jedis maven引入依赖 <dependencies><!--引入Jedis依赖--><dependency><…...
大数据安全经典面试题及回答(上)
目录 一、大数据安全的主要挑战及应对策略 二、大数据安全中的“五个V”及其影响 三、在Hadoop集群中实施数据加密的步骤和注意事项 四、在大数据环境中实施访问控制和身份认证 五、大数据环境中数据备份和恢复的策略 六、大数据处理过程中保护用户隐私的策略 七、大数据…...

vi/vim使用命令
你是否在编辑文件时以为键盘坏了,为什么不能删除呢,为什么不能敲代码呢,当你初识vi,会觉得这个东西设计很难用,这篇教程带你熟练得用上这款经典的工具,当你熟练了这款工具就会真正体会到高效率打码 Vi 是在…...
webpack打包gz文件,nginx开启gzip压缩
wepback配置 webpack4配合"compression-webpack-plugin": "^6.1.2"打包压缩gz chain.plugin("compression").use(new CompressionPlugin({test: /\.js$|\.html$|\.css$/,threshold: 10240, // 超过10KB的压缩deleteOriginalAssets: false,// 保…...

微服务开发与实战Day11 - 微服务面试篇
一、分布式事务 1. CAP定理 1998年,加州大学的计算机科学及Eric Brewer提出,分布式系统有三个指标: Consistency(一致性)Availability(可用性)Partition tolerance(分区容错性&am…...

基于Spring Boot+VUE职称评审管理系统
1管理员功能模块 管理员登录,通过填写注册时输入的用户名、密码、角色进行登录,如图1所示。 图1管理员登录界面图 管理员登录进入职称评审管理系统可以查看首页、个人中心、用户管理、评审员管理、省份管理、评审条件管理、职称申请管理、结果公布管理、…...

MySQL 基本语法讲解及示例(上)
第一节:MySQL的基本操作 1. 创建数据库 在 MySQL 中,创建数据库的步骤如下: 命令行操作 打开 MySQL 命令行客户端或连接到 MySQL 服务器。 输入以下命令创建一个数据库: CREATE DATABASE database_name;例如,创建一…...
6.18作业
完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到其他界面 如果账号和密码不匹配ÿ…...
Excel文件转换为HTML文件
文章目录 前言安装python包python代码 前言 将一个Excel文件转换为HTML文件 安装python包 使用pandas和openpyxl库来实现这个功能 pip install pandas openpyxlpython代码 1、首先使用tkinter库中的filedialog模块弹出一个对话框来选择要转换的Excel文件 2、使用pandas库…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...