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.pswp.cn/bicheng/81890.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/81890.shtml
英文地址,請注明出處:http://en.pswp.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;包含父…