remove_const的工作原理及c++的类型推导


author: hjjdebug
date: 2025年 05月 21日 星期三 12:51:57 CST
descrip: remove_const的工作原理及c++的类型推导


文章目录

  • 1. 简单的程序代码.
  • 2.std::remove_const_t 到底是怎样工作的?
    • 2.1 测试代码
    • 2.2 类型推导的调试手段.
      • 2.2.1 给类模板添加成员函数,让它打印信息,然后实例化一个类,通过打印信息判断到底走了哪个实例化代码.
      • 2.2.2 用typeid(type).name() 来打印类型名称.
      • 2.2.3 用is_same_v 来判断
      • 2.2.4 is_same<T,U> 模板实现代码及测试
  • 3. 小结:

std::remove_const的工作原理及c++的类型推导

1. 简单的程序代码.

$ cat main.cpp
#include <stdio.h>
#include <type_traits>
int main() 
{std::remove_const_t<const int> n = 3;  //与其这样写,为什么不直接写 int n=3 ?printf("n:%d\n",n);std::remove_const<const double>::type x=6.0;printf("x:%f\n",x);return 0;
}

执行结果:
$ ./tt
n:3
x:6.000000

预处理后的代码: 除了去掉注释,跟源码没什么变化.没得到有用信息
int main()
{
std::remove_const_t n = 3;
printf(“n:%d\n”,n);
std::remove_const::type x=6.0;
printf(“x:%f\n”,x);
return 0;
}
反汇编的代码: 可推断n是整形,x是浮点型或double型,知道类型确实是正确的,但太不直观了.
int main()
{
401136: f3 0f 1e fa endbr64
40113a: 55 push %rbp
40113b: 48 89 e5 mov %rsp,%rbp
40113e: 48 83 ec 10 sub $0x10,%rsp
std::remove_const_t n = 3;
401142: c7 45 f4 03 00 00 00 movl $0x3,-0xc(%rbp) //-0x3(%rbp) 是n变量
//与其这样写,为什么不直接写 int n=3 ?
printf(“n:%d\n”,n);
401149: 8b 45 f4 mov -0xc(%rbp),%eax
40114c: 89 c6 mov %eax,%esi # n是第2参数
40114e: 48 8d 3d b3 0e 00 00 lea 0xeb3(%rip),%rdi # 字符串是第一参数
401155: b8 00 00 00 00 mov $0x0,%eax
40115a: e8 e1 fe ff ff callq 401040 printf@plt # 调用printf@plt
std::remove_const::type x=6.0;
40115f: f2 0f 10 05 b1 0e 00 movsd 0xeb1(%rip),%xmm0
401166: 00
401167: f2 0f 11 45 f8 movsd %xmm0,-0x8(%rbp) # -0x8(rbp)是x变量
printf(“x:%f\n”,x);
40116c: 48 8b 45 f8 mov -0x8(%rbp),%rax
401170: 66 48 0f 6e c0 movq %rax,%xmm0 #xmm0 是第2参数(浮点数)
401175: 48 8d 3d 92 0e 00 00 lea 0xe92(%rip),%rdi #字符串rdi是第一参数
40117c: b8 01 00 00 00 mov $0x1,%eax
401181: e8 ba fe ff ff callq 401040 printf@plt
return 0;
401186: b8 00 00 00 00 mov $0x0,%eax
}
看main.o 的符号信息,毛都没有.
$ nm main.o
U GLOBAL_OFFSET_TABLE
0000000000000000 T main
U printf

我们知道,它把std::remove_const_t<const int> 翻译成了 int
其写法std::remove_const_t<const int> n = 3; 完全等价于 int n=3;
这里第一个问题是, 为什么我们不直接写 int n=3; ?
如果直接给整形变量赋值,当然写int n=3, 我用c写代码这么多年, 也从没见谁用std::remove_const_t<const int>来代替int 的写法.

这是我在c++中看到了一个类型推导语句, 在模板类编程中,类型可以是参数, 对传递来的类型,可能要进行变换.
类型推导就是在这样的环境下发生的. 我也是第一次在c++上看到了类型推导,原来类型还可以这么玩!
测试代码是最简单的例子. const int 去掉const 是什么类型?傻子都知道是int 类型。
但关键是,电脑是怎么知道的? 这里的电脑,实际指的是gcc编译器.

2.std::remove_const_t 到底是怎样工作的?

欧式几何学从5个公设出发,推出几百个定理.
c++也只是遵从几条编译规则, 它怎么会认识std::remove_const_t<const int> 这个修饰词?
而且把它等价翻译成 int.
可惜的是,我们预处理了源代码,反编译了执行代码,查看了obj文件符号,只知道它变成了int, 但不知道它
是根据什么规则来变化的.
想搞清这个问题, 方法就是去掉库代码<type_traits> 自己写std::remove_const_t 代码.
下面我直接给出测试代码, 这是我阅读<type_traits> 及查找网络得到的. 然后解释它的工作原理.
代码也还算简单:

2.1 测试代码

$ cat main.cpp
#include <stdio.h>
namespace std 
{template <class T>struct remove_const {typedef T type;};template <class T>struct remove_const<const T> {typedef T type;};template <typename T>using remove_const_t = typename std::remove_const<T>::type; //需要加上typename
}int main() 
{std::remove_const_t<const int> n = 3;  printf("n:%d\n",n);std::remove_const<const double>::type x=6.0;printf("x:%f\n",x);return 0;
}

代码不用<type_trait>, 自己写也能编译通过,运行结果相同.
只是预处理,查符号仍然找不到任何踪迹,但看反汇编代码已经都处理好了.
那么我们就先分析模板代码吧.
std 是个命名空间.
remove_const_t 实际上是一个类型 “std::remove_const<T>::type” 的小名
using 的用法与typedef 的用法类似, 是c++11 新扩充的关键词.
这里用using A = typename B 为B类型指明小名A

remove_const<T> 是一个类模板,它定义为:
template <class T>
struct remove_const{ typedef T type;} //type 就是 T 的小名,名称转换

它还有一个const 的特化版本, 定义为:
template <class T>
struct remove_const<const T> {typedef T type;}
特化版本会优先匹配带const 修饰的类型, 它定义的type 把const 给去掉了.
去掉const 就是匹配了这个类模板,重定义了T 为type

ok! 模板代码大体搞懂了,那就看看怎么调用吧.
std::remove_const_t<const int>这是个using 定义的小名, 等价于 std::remove_const<const int>::type
std::remove_const<const int>是一个类,它里边定义的type就是传来的参数类型(模板类定义的),
即 typedef int type
这个type 从外边看,其全名是std::remove_const<const int>::type, 所以模板类中定义了
typedef int std::remove_const<const int>::type
那现在书写了 std::remove_const<const int>::type n=3, 当然就等于 int n=3了.
原来前边的一大堆乱七八糟的字符串,用typedef被定义成 int 的小名了. 这就是这个长字符串的本质意义.

同样的,std::remove_const<const double>::type 这个长字符串
在模板类中是这样定义的 typedef double std::remove_const<const double>::type
现在你倒过来用: std::remove_const<const double>::type x=6.0; 就等价于 double x=6.0

从前面那个长字符串,被重定义为输入参数类型, 看起来像类型推导过程,
给一个类型得到另一个类型,也可以称类型萃取.

分析通了, 但是总觉得不塌实, 因为这都是gcc 干的, 干的还挺多.
能不能让我们跟踪一下,调试一下,看看执行的过程?

2.2 类型推导的调试手段.

2.2.1 给类模板添加成员函数,让它打印信息,然后实例化一个类,通过打印信息判断到底走了哪个实例化代码.

测试例子就不给了,节省篇幅,仅提供一个思路.

2.2.2 用typeid(type).name() 来打印类型名称.

关于typeid的用法,请参考例如:https://blog.csdn.net/hejinjing_tom_com/article/details/148063802?spm=1001.2014.3001.5501

2.2.3 用is_same_v 来判断

std::is_same_v是C++17引入的一个模板变量, 用于在编译时检查两个类型是否相同.
它是std::is_same的简化版本,直接返回一个布尔值(true或false),表示两个类型是否相同
std::is_same_v<T, U>会检查类型T和U是否相同。如果T和U是相同的类型,则返回true;否则返回false
使用举例:

$ cat main.cpp
#include <stdio.h>
#include <type_traits>
using namespace std;
int main() 
{bool res=is_same_v<int,int>; //把bool 数值直接放这printf("int,int compare result:%d\n",res);res=is_same_v<int,const int>;printf("int,const int compare result:%d\n",res);res=is_same_v<int,float>;printf("int,float compare result:%d\n",res);return 0;
}

执行结果:
$ ./tt
int,int compare result:1
int,const int compare result:0
int,float compare result:0

但我却对is_same_v<T,U>的实现过程更感兴趣, 它是怎样工作的呢?
网络上讲 std::is_same_v<T, U>的工作原理基于模板编程, 没下文了, 要继续探讨模板编程才好啊.
还是从<type_traits> 上扣代码吧. 其实很简单!!
为了防止符号走型,把它放到代码块中说明.

//这是ubuntu20  gcc 9.4 下<type_traits>的实现
// 主模板template<typename _Tp, typename _Up>struct is_same: public false_type { };// 特化版本template<typename _Tp>struct is_same<_Tp, _Tp>: public true_type { };// is_same_v 是简化书写版
template <typename _Tp, typename _Up>inline constexpr bool is_same_v = is_same<_Tp, _Up>::value;//true_type, false_type 定义typedef integral_constant<bool, true> true_type;typedef integral_constant<bool, false> false_type;//integral_constant 定义 //整形常数类模板template<typename _Tp, _Tp __v>struct integral_constant{ //true_type的value就是true,false_type的value就是fase,整数类型的value就是你存进来的值static constexpr _Tp value = __v;  //定义了value 变量,保留了参数值给valuetypedef _Tp value_type;typedef integral_constant<_Tp, __v> type;constexpr operator value_type() const noexcept { return value; }constexpr value_type operator()() const noexcept { return value; }};

不能调试用眼睛读的代码也就这么多了,再多了就没法理解了.
gcc 9.4 的实现方法基于整形常数模板. 稍微复杂些
测试代码我们采用一种简化写法吧,就不继承谁了,直接给结果.
跟gcc的效果一致,更容易理解

2.2.4 is_same<T,U> 模板实现代码及测试

源代码:

$ cat main.cpp
#include <stdio.h>
//#include <type_traits>
namespace std 
{/// remove_const 代码 /template <class T>struct remove_const {typedef T type;};template <class T>struct remove_const<const T> {typedef T type;};template <typename T>using remove_const_t = typename std::remove_const<T>::type; //注意加上typename is_same 代码 //// 主模板, 类型不同走这里template<typename T, typename U>struct is_same {static constexpr bool value = false;};// 特化版本, 类型相同走这里template<typename T>struct is_same<T, T> {static constexpr bool value = true;};// C++17简化版, 返回一个类常量(bool值)template<typename T, typename U>inline constexpr bool is_same_v = is_same<T, U>::value;
}int main() 
{std::remove_const_t<const int> n = 3;  bool res=std::is_same_v<std::remove_const_t<const int>, int>;printf("res:%d,n:%d\n",res,n);std::remove_const<const double>::type x=6.0;printf("x:%f\n",x);return 0;
}

反编译
bool res=std::is_same_v<std::remove_const_t, int>;
401149: c6 45 f3 01 movb $0x1,-0xd(%rbp)
直接把1(true)付给了变量 res;

是否相同的判别是通过编译器创建不同的模板类来确定的,而创建哪个模板类是编译器根据模板参数确定的.
而模板类型参数是我们直接传递的, 有可能是基本类型,有可能是推导类型, 推导类型最后还得变成基本类型,
这样最终还是通过编译器汇编时期的判断确定是相同类型或不同类型.
不过知道了这个工作原理,我们可以大胆的用is_same_v 来判别两个类型是否相同了.

3. 小结:

std::remove_const_t typedef为 std::remove_const::type
而模板类中又typedef int std::remove_const::type, 所以说
std::remove_const::type 等价于int, 是int的小名,由typedef 定义的.
实际推导的过程是用模板类匹配类型参数, 把类型参数重新typedef一下.

is_same_v(T,U)的判断更加简单, 也是根据模板类型匹配, T,U为同一类型,返回true, 否则返回false
值得一提的是c++更加注重gcc 的编译过程了.

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

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

相关文章

人脸识别,使用 deepface + api + flask, 改写 + 调试

1. 起因&#xff0c; 目的&#xff0c; 感受: github deepface 这个项目写的很好&#xff0c; 继续研究使用这个项目&#xff0c;改写 api。增加一个前端 flask app 2. 先看效果 3. 过程: 大力改写原始项目中 api 这部分的代码&#xff0c; 原始项目的文件结构太繁杂了: 我把…

三维表面轮廓仪的维护保养是确保其长期稳定运行的关键

三维表面轮廓仪是一种高精度测量设备&#xff0c;用于非接触式或接触式测量物体表面的三维形貌、粗糙度、台阶高度、纹理特征等参数。其主要基于光学原理进行测量。它利用激光或其他光源投射到被测物体表面&#xff0c;通过接收反射光或散射光&#xff0c;结合计算机图像处理技…

Lambda表达式的高级用法

今天来分享下Java的Lambda表达式&#xff0c;以及它的高级用法。 使用它可以提高代码的简洁度&#xff0c;使代码更优雅。 一、什么是lambda表达式 Lambda 表达式是 Java 8 引入的特性&#xff0c;用于简化匿名内部类的语法&#xff0c;使代码更简洁&#xff0c;尤其在处理函…

31-35【动手学深度学习】深度学习硬件

1. CPU和GPU 1.1 CPU CPU每秒钟计算的浮点运算数为0.15&#xff0c;GPU为12。GPU的显存很低&#xff0c;16GB&#xff08;可能32G封顶&#xff09;&#xff0c;CPU可以一直插内存。 左边是GPU&#xff08;只能做些很简单的游戏&#xff0c;视频处理&#xff09;&#xff0c;中…

【MySQL成神之路】MySQL常见命令汇总

目录 MySQL常用命令总结 1. 数据库操作 2. 表操作 3. 数据操作&#xff08;DML&#xff09; 4. 索引与优化 5. 用户与权限管理 6. 备份与恢复 7. 事务控制 8. 常用函数 9. 系统状态与日志 总结 MySQL常用命令总结 MySQL作为最流行的关系型数据库之一&#xff0c;提供…

Dify的大语言模型(LLM) AI 应用开发平台-本地部署

前言 今天闲着&#xff0c;捣鼓一下 Dify 这个开源平台&#xff0c;在 mac 系统上&#xff0c;本地部署并运行 Dify 平台&#xff0c;下面记录个人在本地部署Dify 的过程。 Dify是什么&#xff1f; Dify是一个开源的大语言模型&#xff08;LLM&#xff09;应用开发平台&#…

【论文阅读】针对BEV感知的攻击

Understanding the Robustness of 3D Object Detection with Bird’s-Eye-View Representations in Autonomous Driving 这篇文章是发表在CVPR上的一篇文章&#xff0c;针对基于BEV的目标检测算法进行了两类可靠性分析&#xff0c;即恶劣自然条件以及敌对攻击。同时也提出了一…

SonarQube的核心作用与用途

SonarQube作为一个开源的代码质量管理平台&#xff0c;致力于持续分析代码的健康状态&#xff0c;帮助开发团队提升代码质量。以下是其核心作用与用途的详细说明&#xff1a; 1、静态代码分析 SonarQube通过静态代码分析技术&#xff0c;自动识别代码中的潜在问题。它能够检测…

AI工程师系列——面向copilot编程

前言 ​ 笔者已经使用copilot协助开发有一段时间了,但一直没有总结一个协助代码开发的案例,特别是怎么问copilot,按照什么顺序问,哪些方面可以高效的生成需要的代码,这一次,笔者以IP解析需求为例,沉淀一个实践案例,供大家参考 当然,其实也不局限于copilot本身,类似…

【软件设计师】知识点简单整理

文章目录 数据结构与算法排序算法图关键路径 软件工程决策表耦合类型 编程思想设计模式 计算机网络域名请求过程 数据结构与算法 排序算法 哪些排序算法是稳定的算法?哪些不是稳定的算法,请举出例子。 稳定排序算法&#xff1a;冒泡排序、插入排序、归并排序、基数排序、计数…

FastAPI 支持文件下载和上传

文章目录 1. 文件下载处理1.1. 服务端处理1.1.1. 下载小文件1.1.2. 下载大文件&#xff08;yield 支持预览的&#xff09;1.1.3. 下载大文件&#xff08;bytes&#xff09;1.1.4. 提供静态文件服务1.1.5. 中文文件名错误 1.2. 客户端处理1.2.1. 普通下载1.2.2. 分块下载1.2.3. …

naive-ui切换主题

1、在App.vue文件中使用 <script setup lang"ts"> import Dashboard from ./views/dashboard/index.vue import { NConfigProvider, NGlobalStyle, darkTheme } from naive-ui import { useThemeStore } from "./store/theme"; // 获取存储的主题类…

Kotlin 协程 (三)

协程通信是协程之间进行数据交换和同步的关键机制。Kotlin 协程提供了多种通信方式&#xff0c;使得协程能够高效、安全地进行交互。以下是对协程通信的详细讲解&#xff0c;包括常见的通信原语、使用场景和示例代码。 1.1 Channel 定义&#xff1a;Channel 是一个消息队列&a…

使用SQLite Studio导出/导入SQL修复损坏的数据库

使用SQLite Studio导出/导入SQL修复损坏的数据库 使用Zotero时遇到了数据库损坏&#xff0c;在软件中寸步难行&#xff0c;遂尝试修复数据库。 一、SQLite Studio简介 SQLite Studio是一款专为SQLite数据库设计的免费开源工具&#xff0c;支持Windows/macOS/Linux。相较于其…

【git config --global alias | Git分支操作效率提升实践指南】

git config --global alias | Git分支操作效率提升实践指南 背景与痛点分析 在现代软件开发团队中&#xff0c;Git分支管理是日常工作的重要组成部分。特别是在规范的开发流程中&#xff0c;我们经常会遇到类似 feature/user-management、bugfix/login-issue 或 per/cny/dev …

(八)深度学习---计算机视觉基础

分类问题回归问题聚类问题各种复杂问题决策树√线性回归√K-means√神经网络√逻辑回归√岭回归密度聚类深度学习√集成学习√Lasso回归谱聚类条件随机场贝叶斯层次聚类隐马尔可夫模型支持向量机高斯混合聚类LDA主题模型 一.图像数字化表示及建模基础 二.卷积神经网络CNN基本原…

在tensorflow源码环境里,编译出独立的jni.so,避免依赖libtensorflowlite.so,从而实现apk体积最小化

需要在APP里使用tensorflow lite来运行PC端训练的model.tlite&#xff0c;又想apk的体积最小&#xff0c;尝试了如下方法&#xff1a; 1. 在gradle里配置 implementation("org.tensorflow:tensorflow-lite:2.16.1") 这样会引入tensorflow.jar&#xff0c;最终apk的…

neo4j框架:java安装教程

安装使用neo4j需要事先安装好java&#xff0c;java版本的选择是一个犯难的问题。本文总结了在安装java和使用Java过程中遇到的问题以及相应的解决方法。 Java的安装包可以在java官方网站Java Downloads | Oracle 中国进行下载 以java 8为例&#xff0c;选择最后一行的x64 compr…

[服务器备份教程] Rclone实战:自动备份数据到阿里云OSS/腾讯云COS等对象存储

更多服务器知识&#xff0c;尽在hostol.com 各位服务器的守护者们&#xff0c;咱们都知道&#xff0c;数据是数字时代的“黄金”&#xff0c;而服务器上的数据更是我们业务的命脉。可天有不测风云&#xff0c;硬盘可能会突然“寿终正寝”&#xff0c;手滑执行了“毁灭性”命令…

Nextjs App Router 开发指南

Next.js是一个用于构建全栈web应用的React框架。App Router 是 nextjs 的基于文件系统的路由器&#xff0c;它使用了React的最新特性&#xff0c;比如 Server Components, Suspense, 和 Server Functions。 术语 树(Tree): 一种用于可视化的层次结构。例如&#xff0c;包含父…