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

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

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

相关文章

【Python/Pytorch - 网络模型】-- 手把手搭建E3D LSTM网络

文章目录 文章目录 00 写在前面01 基于Pytorch版本的E3D LSTM代码02 论文下载 00 写在前面 测试代码&#xff0c;比较重要&#xff0c;它可以大概判断tensor维度在网络传播过程中&#xff0c;各个维度的变化情况&#xff0c;方便改成适合自己的数据集。 需要github上的数据集…

这些数据可被Modbus采集,你还不知道???

为什么要用Modbus采集模块 Modbus采集模块之所以被广泛使用&#xff0c;是因为它提供了标准化的通信协议&#xff0c;确保了不同设备间的兼容性。它支持多种通信方式&#xff0c;易于实现&#xff0c;并且能够适应不同的网络环境。Modbus模块能够收集和传输各种工业数据&#x…

061、Python 包:模块管理

包&#xff08;Package&#xff09;是一种用于组织模块的层次结构。包实际上就是一个包含了__init__.py文件的目录&#xff0c;该文件可以为空或包含包的初始化代码。通过使用包&#xff0c;可以更好地组织和管理大型项目中的模块&#xff0c;避免命名冲突&#xff0c;并提高代…

kettle从入门到精通 第七十一课 ETL之kettle 再谈http post,轻松掌握body中传递json参数

场景&#xff1a; kettle中http post步骤如何发送http请求且传递body参数&#xff1f; 解决方案&#xff1a; http post步骤中直接设置Request entity field字段即可。 1、手边没有现成的post接口&#xff0c;索性用python搭建一个简单的接口&#xff0c;关键代码如下&#…

深度学习模型的生命周期与推理系统架构

目录 深度学习模型的生命周期 ​编辑 深度学习模型的生命周期 推理相比训练的新特点与挑战 推理系统架构 推理系统 vs 推理引擎 顶层:API接口和模型转换 中层:运行时(计算引擎) 底层:硬件级优化 边缘设备计算 主要问题 边缘部署和推理方式 方式1:边缘设备计…

可提供实习证明/实习鉴定报告,企业项目试岗实训开营啦

在数字化转型的浪潮中&#xff0c;大数据和人工智能等前沿技术已成为推动经济发展和科技进步的关键动力。当前&#xff0c;全球各行各业都在积极推进数字化转型&#xff0c;不仅为经济增长注入新活力&#xff0c;也对人才市场结构产生了深刻影响&#xff0c;尤其是对数字化人才…

在 KubeSphere 上快速安装和使用 KDP 云原生数据平台

作者简介&#xff1a;金津&#xff0c;智领云高级研发经理&#xff0c;华中科技大学计算机系硕士。加入智领云 8 余年&#xff0c;长期从事云原生、容器化编排领域研发工作&#xff0c;主导了智领云自研的 BDOS 应用云平台、云原生大数据平台 KDP 等产品的开发&#xff0c;并在…

联邦学习周记|第四周

论文&#xff1a;Active Federated Learning 链接 将主动学习引入FL&#xff0c;每次随机抽几个Client拿来train&#xff0c;把置信值低的Client概率调大&#xff0c;就能少跑几次。 论文&#xff1a;Active learning based federated learning for waste and natural disast…

“Git之道:掌握常用命令,轻松管理代码“

目录 1. 初始化和配置 2. 提交和更新 3. 分支和合并 4. 查看和比较 5. 远程仓库 6. 文件操作命令 1. 初始化和配置 git init&#xff1a;在当前目录初始化一个新的Git仓库git config&#xff1a;配置Git的全局或局部选项git clone&#xff1a;从远程仓库克隆一个本地副本…

vue3第四十节(pinia的用法注意事项解构store)

pinia 主要包括以下五部分&#xff0c;经常用到的是 store、state、getters、actions 以下使用说明&#xff0c;注意事项&#xff0c;仅限于 vue3 setup 语法糖中使用&#xff0c;若使用选项式 API 请直接查看官方文档&#xff1a; 一、前言&#xff1a; pinia 是为了探索 vu…

动手学深度学习(Pytorch版)代码实践 -深度学习基础-11暂退法Dropout

11暂退法Dropout #Dropout 是一种正则化技术&#xff0c;主要用于防止过拟合&#xff0c; #通过在训练过程中随机丢弃神经元来提高模型的泛化能力。 import torch from torch import nn from d2l import torch as d2l import liliPytorch as lpdef dropout_layer(X, dropout):…

大数据—“西游记“全集文本数据挖掘分析实战教程

项目背景介绍 四大名著&#xff0c;又称四大小说&#xff0c;是汉语文学中经典作品。这四部著作历久不衰&#xff0c;其中的故事、场景&#xff0c;已经深深地影响了国人的思想观念、价值取向。四部著作都有很高的艺术水平&#xff0c;细致的刻画和所蕴含的思想都为历代读者所…

0元体验苹果macOS系统,最简单的虚拟机部署macOS教程

前言 最近发现小伙伴热衷于在VMware上安装体验macOS系统&#xff0c;所以就有了今天的帖子。 正文开始 首先&#xff0c;鉴于小伙伴们热衷macOS&#xff0c;所以小白搜罗了一圈macOS系统&#xff0c;并开启了分享通道。 本次更新的系统版本是&#xff1a; macOS 10.13.6 ma…

【靶场搭建】-01- 在kali上搭建DVWA靶机

1.DVWA靶机 DVWA&#xff08;Damn Vulnerable Web Application&#xff09;是使用PHPMysql编写的web安全测试框架&#xff0c;主要用于安全人员在一个合法的环境中测试技能和工具。 2.下载DVWA 从GitHub上将DVWA的源码clone到kali上 git clone https://github.com/digininj…

温湿度采集与OLED显示

目录 一、什么是软件I2C 二、什么是硬件I2C 三、STM32CubeMX配置 1、RCC配置 2、SYS配置 3、I2C1配置 3、I2C2配置 4、USART1配置 5、TIM1配置 6、时钟树配置 7、工程配置 四、设备链接 1、OLED连接 2、串口连接 3、温湿度传感器连接 五、每隔2秒钟采集一次温湿…

第二十三篇——香农第二定律(二):到底要不要扁平化管理?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 对于企业的理解&#xff0c;扁平化的管理&#xff0c;如果从香农第二定律…

Qt 实战(5)布局管理器 | 5.2、深入解析Qt布局管理器

文章目录 一、深入解析Qt布局管理器1、为什么要使用布局管理器&#xff1f;2、布局管理器类型3、布局管理器用法详解3.1、QBoxLayout&#xff08;垂直与水平布局&#xff09;3.2、QGridLayout&#xff08;网格布局&#xff09;3.3、QFormLayout&#xff08;表单布局&#xff09…

特斯拉、路特斯、中国一汽、毕博、博世等企业将出席中国汽车供应链降碳和可持续国际峰会

由ECV International 举办的2024中国汽车供应链脱碳与可持续国际峰会将于2024年9月23-24日在上海召开。 在本次峰会上&#xff0c;来自全球各地的行业领袖、政策制定者、研究人员和利益相关者将齐聚一堂&#xff0c;商讨对于减少碳排放和促进整个汽车供应链可持续实践至关重要…

教学资源共享平台的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;老师管理&#xff0c;用户管理&#xff0c;成绩管理&#xff0c;教学资源管理&#xff0c;作业管理 老师账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用…

什么是拷贝?我:Ctrl + C ...

前言 当谈及拷贝&#xff0c;你的第一印象会不会和我一样&#xff0c;ctrl c ctrl v ... &#xff1b;虽然效果和拷贝是一样的&#xff0c;但是你知道拷贝的原理以及它的实现方法吗&#xff1f;今天就让我们一起探究一下拷贝中深藏的知识点吧。 拷贝 首先来看下面一段代码…