当前位置: 首页 > news >正文

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&#xff0c;用于记录卡片中每个数字的数量。 从1开始依次尝试拼出正整数&#xff0c;直到无法拼出下一个数为止。 对于每个尝试拼出的正整数&#xff0c;遍历其每一位的数字&#xff0c;检查该数字在count中是否还…...

虚谷数据库-定时作业

虚谷数据库提供定时作业机制&#xff0c;用于定时、定期、自动的进行某些操作&#xff0c;可通过系统包 dbms_scheduler 进行定时作业创建、调度、查看、删除等。 DBMS_SCHEDULER 系统包封装了以下过程/函数&#xff1a;ENABLE、DISABLE、SET_JOB_ARGUMENT_VALUE、DROP_JOB、R…...

AWD攻防比赛流程手册

AWD简单介绍&#xff1a; AWD&#xff1a;Attack With Defence&#xff0c;即攻防对抗&#xff0c;比赛中每个队伍维护多台服务器&#xff08;一般两三台&#xff0c;视小组参赛人数而定&#xff09;&#xff0c;服务器中存在多个漏洞&#xff08;web层、系统层、中间件层等&am…...

Golang的json解析--Gjson库的使用举例

目录 简介 安装 原生的json解析 Gjson使用举例 基本使用 键路径 使用示例 其他资源 简介 在 Golang 中&#xff0c;解析 JSON 数据是一项非常常见的任务。Go提供了标准的JSON包&#xff0c;可以轻松地将JSON数据序列化和反序列化。但是&#xff0c;在使用标准JSON包…...

基于Langchain构建本地大型语言模型(LLM)问答系统的经验分享

基于Langchain构建本地大型语言模型&#xff08;LLM&#xff09;问答系统的经验分享 https://download.csdn.net/download/xziyuan/89334371?spm1001.2101.3001.9500 最近&#xff0c;我一直在探索如何利用Langchain来构建一个本地的大型语言模型问答系统。在这个过程中&…...

对抗式生成模仿学习(GAIL)

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

信息系统项目管理师 | 新一代信息技术

关注WX&#xff1a;CodingTechWork 物联网 定义 The Internet of Things是指通过信息传感设备&#xff0c;按约定的协议&#xff0c;将任何物品与互联网连接&#xff0c;进行信息交互和通信&#xff0c;以实现智能化识别。定位、跟踪、监控和管理的一种网络。物联网主要解决…...

安全宣传咨询日活动向媒体投稿记住这个投稿好方法

在信息爆炸的时代,作为单位的信息宣传员,我肩负着将每一次重要活动,特别是像“安全宣传咨询日”这样的公益活动,有效传达给公众的重任。这份工作看似简单,实则充满了挑战,尤其是在我初涉此领域时,那段曲折而又难忘的投稿经历,至今记忆犹新。 初探投稿之海,遭遇重重困难 起初,我…...

第7章:系统架构设计基础知识-软件架构风格

由于历史原因&#xff0c;研究者和工程人员对Sofiware Architecture(简称SA)的翻译不尽相同&#xff0c;其软件的“体系结构”和“架构”具有相同的含义。 系统架构其实就是系统的结构&#xff0c;系统架构设计其实就是要给相关利益方说清楚通过什么样的结构来解决需求中功能和…...

自制调色小工具给图片加滤镜,修改图片红、绿、蓝通道及亮度,修改图片颜色

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

【Redis】java客户端(SpringData和jedis)

https://www.oz6.cn/articles/58 https://www.bilibili.com/video/BV1cr4y1671t/?p16 redis官网客户端介绍&#xff1a;https://redis.io/docs/latest/develop/connect/clients/ jedis maven引入依赖 <dependencies><!--引入Jedis依赖--><dependency><…...

大数据安全经典面试题及回答(上)

目录 一、大数据安全的主要挑战及应对策略 二、大数据安全中的“五个V”及其影响 三、在Hadoop集群中实施数据加密的步骤和注意事项 四、在大数据环境中实施访问控制和身份认证 五、大数据环境中数据备份和恢复的策略 六、大数据处理过程中保护用户隐私的策略 七、大数据…...

vi/vim使用命令

你是否在编辑文件时以为键盘坏了&#xff0c;为什么不能删除呢&#xff0c;为什么不能敲代码呢&#xff0c;当你初识vi&#xff0c;会觉得这个东西设计很难用&#xff0c;这篇教程带你熟练得用上这款经典的工具&#xff0c;当你熟练了这款工具就会真正体会到高效率打码 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年&#xff0c;加州大学的计算机科学及Eric Brewer提出&#xff0c;分布式系统有三个指标&#xff1a; Consistency&#xff08;一致性&#xff09;Availability&#xff08;可用性&#xff09;Partition tolerance&#xff08;分区容错性&am…...

基于Spring Boot+VUE职称评审管理系统

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

MySQL 基本语法讲解及示例(上)

第一节&#xff1a;MySQL的基本操作 1. 创建数据库 在 MySQL 中&#xff0c;创建数据库的步骤如下&#xff1a; 命令行操作 打开 MySQL 命令行客户端或连接到 MySQL 服务器。 输入以下命令创建一个数据库&#xff1a; CREATE DATABASE database_name;例如&#xff0c;创建一…...

6.18作业

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&#xff…...

Excel文件转换为HTML文件

文章目录 前言安装python包python代码 前言 将一个Excel文件转换为HTML文件 安装python包 使用pandas和openpyxl库来实现这个功能 pip install pandas openpyxlpython代码 1、首先使用tkinter库中的filedialog模块弹出一个对话框来选择要转换的Excel文件 2、使用pandas库…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

【C++】纯虚函数类外可以写实现吗?

1. 答案 先说答案&#xff0c;可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...