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 的編譯過程了.