深入解析decltype和decltype(auto)

来源:https://www.cnblogs.com/isharetech/p/18130921
-Advertisement-
Play Games

decltype關鍵字是C++11新標準引入的關鍵字,它和關鍵字auto的功能類似,也可以自動推導出給定表達式的類型,但它和auto的語法有些不同,這篇文章講解了decltype的使用場景以及和auto不同的地方,同時也講解了和auto結合使用的用法。 ...


decltype關鍵字是C++11新標準引入的關鍵字,它和關鍵字auto的功能類似,也可以自動推導出給定表達式的類型,但它和auto的語法有些不同,auto推導的表達式放在“=”的右邊,並作為auto所定義的變數的初始值,而decltype是和表達式結合在一起,語法如下:

decltype(expr) var;

它的語法像是函數調用,但它不是函數調用而是運算符,和sizeof運算符類似,在編譯期間計算好,表達式expr不會被真正執行,因此不會產生彙編代碼,如下的代碼:

int func(int);
decltype(func());

func函數不會真正被調用,只會在編譯期間獲取他的類型。decltype和auto在功能上大部分相似,但推導規則和應用場景存在一些區別,如用auto定義變數時必須提供初始值表達式,利用初始值表達式推導出類型並用它作為變數的初始值,而decltype定義變數時可以不需要初始值。還有使用auto作為值語義的推導時,會忽略表達式expr的引用性和CV屬性,而decltype可以保留這些屬性,關於auto的詳細解析,可以參考另一篇文章《深入解析C++的auto自動類型推導》

decltype在普通代碼中應用並不廣泛,主要用在泛型編程中較多,因此沒有auto使用得多,下麵將介紹decltype的推導規則,在介紹過程中遇到和auto規則不一樣的地方則將兩者對照說明,最後再介紹decltype無法被auto替代的應用場景。

推導規則

我將decltype的推導規則歸納為兩條,根據expr有沒有帶小括弧分為兩種形式,如以下的形式:

decltype(expr)
// 或者
decltype((expr))
  • expr沒有帶括弧的情形

當expr是單變數的標識符、類的數據成員、函數名稱、數組名稱時,推導出來的結果和expr的類型一致,並且會保留引用屬性和CV修飾詞,如下麵的例子:

int func(int, int) {
    int x;
    return x;
}

class Base {
public:
	int x = 0;
};

int x1 = 1;		// (1) decltype(x1)為int
const int& x2 = 2;	// (2) decltype(x2)為const int&
const Base b;				
b.x;			// (3) decltype(b.x)為int
int a[10];		// (4) decltype(a)為int[10]
decltype(func);		// (5) 結果為int(int, int)

(1)式decltype(x1)的結果和x1的類型一致,為int類型。

(2)式的結果也是和x2一致,這裡和auto的推導規則不同的是,它可以保留x2的引用屬性和const修飾詞,所以它的類型是const int&。

(3)式中定義的類對象b雖然是const的,但成員x的類型是int類型,所以結果也是int。

(4)和(5)都保留了原本的類型,這個也是和auto的推導結果不同的,使用auto推導的規則它們會退化為指針類型,這裡則保留了它們數組和函數的類型。

當expr是一條表達式時,decltype(expr)的結果視expr表達式運算後的結果而定(在編譯時運算而非運行時運算),當expr返回的結果是右值時,推導的結果和返回結果的類型一致,當expr返回的結果是左值時,推導的結果是一個引用,見下麵的例子:

int x1 = 1;
int x2 = 2;
decltype(x1 + x2);	// (1) int
decltype(func());	// (2) int
decltype(x1,x2);	// (3) int&
decltype(x1,0);		// (4) int
decltype(a[1]);		// (5) int&

(1)式因為兩個變數相加後返回一個數值,它是一個右值,所以推導結果和它的類型一致,這裡換成加減乘除都是一樣的。

(2)是一個函數調用,跟上面的使用函數名稱不同,這裡會調用函數(編譯時),根據函數的返回結果來確定推導出來的類型,如果返回結果是引用或者指針類型,則推導結果也會引用或者指針類型,此函數返回的結果是int型,所以結果也是int型。

(3)和(4)是逗號表達式,它的返回結果是逗號後的那個語句,(3)是返回x2,它是一個變數,是一個左值,所以推導結果是int&,而(4)的返回結果是0,是一個右值,因此結果和它的類型一致。

(5)是訪問數組中的元素,它是一個左值,因此推導結果是一個引用。

  • expr帶括弧的情形

當expr帶上括弧之後,它的推導規則有了變化,表達式加上括弧後相當於去執行這條語句然後根據返回結果的類型來推導,見下麵的例子:

class Base {
public:
	int x = 0;
};

int x1 = 1;
int x2 = 2;
const Base b;
b.x;
decltype((x1+x2)); 	// (1) int
decltype((x1));		// (2) int&
decltype((b.x));	// (3) const int&

(1)式中相加後的結果是一個右值,加上括弧後依然是一個右值,因此推導結果是int。

(2)式中跟之前沒有加括弧的情況不一樣,加上括弧相當於是返回x1變數,因此是一個左值,推導結果是一個引用。

(3)式中也跟之前的結果不一樣了,加上括弧相當於返回類的數據成員x,因此是一個左值,推導結果是一個引用,但因為定義的類對象b是一個const對象,要保持它的內容不可被修改,因此引用要加上const修飾。

最後還有要註意一點的是,decltype和auto一樣也可以和&和一起結合使用,但和auto的規則不一樣,auto與&和結合表示定義的變數的類型是一個引用或者指針類型,而decltype則是保留這個符號並且和推導結果一起作為最終的類型,見下麵的例子:

int x1 = 1;
auto *pi = &x1;		// (1) auto為int,pi為int*
decltype(&x1) *pp;	// (2) decltype(&x1)為int*,pp為int**

(1)式中的auto推導結果為int而不是int,要將pi定義為指針類型需要明確寫出auto

(2)式的decltype(&x1)的推導結果為int,它會和定義中的(*pp前面的星號)結合在一起,因此最終的結果是int**。

decltype的使用場景

上面提到decltype和auto的一個區別就是使用auto必須要有一個初始值,而decltype在定義變數時可以不需要初始值,這在定義變數時暫時無法給出初始值的情況下非常有用,見下麵的例子:

#include <map>
#include <string>

template<typename ContainerT>
class Object {
public:
    void init(ContainerT& c) { it_ = c.begin(); }
private:
    decltype(ContainerT().begin()) it_;
};

int main() {
    std::map<std::string, int> m;
    Object<std::map<std::string, int>> obj;
    obj.init(m);
}

在定義類的成員it_時還沒有初始值,這時無法使用auto來推導它的類型,況且這裡也無法使用auto來定義類的數據成員,因為現在還不支持使用auto來定義非靜態的數據成員的,但使用decltype卻是可以的。

還有一種情形是使用auto無法做到的,就是auto在使用值語義的推導規則的時候會忽略掉引用屬性和CV修飾詞,比如:

int i = 1;
const int& j = i;
auto x = j;	// auto的結果為int

這裡x無法推導出和變數j一樣的類型,你可能會說,如果要使用引用類型,那可以這樣寫:

const auto& x = j;	// auto的結果為int, x的類型const int&

但這又會帶來其它的問題,這樣定義出來的變數的類型永遠都是const引用的類型,無法做到根據不同的表達式推導出相應的類型,如果使用decltype則可以做到:

int i = 1;
const int& j = i;
decltype(j) x = j;	// x的類型為const int&
decltype(i) y = i;	// y的類型為int

上面的代碼使用decltype就可以根據不同的初始值表達式來推導出不同的結果。但你可能會覺得初始值表達式要在左右兩邊寫上兩遍,比較累贅,單個變數的還好,如果是個長表達式的話就會顯得代碼很冗餘,也不優雅,比如:

int x = 1;
int y = 2;
double z = 5.0;
decltype(x + y + z) i = x + y + z;

如果上面的例子中表達式再長點就更難看也更麻煩了,幸好C++14標準提出了decltype和auto結合的功能,也就是decltype(auto)的用法。

decltype(auto)的使用解析

自動推導表達式的結果的類型

decltype(auto)的使用語法規則如下:

decltype(auto) var = expr;

它的意思是定義一個變數var,auto作為類型占位符,使用自動類型推導,但推導的規則是按照decltype的規則來推導。因此上面的代碼可以這樣來寫:

decltype(auto) j = x + y + z;

它的用法跟使用auto一樣,利用右邊的表達式來推導出變數j的類型,但是推導規則使用的是decltype的規則。這對需要保持右邊表達式的引用屬性和CV修飾詞時就非常有用,上面的代碼可以改為:

int i = 1;
const int& j = i;
decltype(auto) x = j;	// x的類型為const int&
decltype(auto) y = i;	// y的類型為int

decltype(auto)用於推導函數返回值的類型

decltype(auto)可以用於推導函數返回值的類型,auto也可以用於推導函數的返回值類型,在講解auto的那篇文章中就已講解過。但auto有個問題就是會忽略掉返回值的引用屬性,但如果你用auto&來推導返回值類型的話,那所有的類型都將是引用類型,這也不是實際想要的效果,有沒有辦法做到如果返回值類型是值類型時就推導出值類型,如果返回值類型是引用則推導出結果是引用類型?假設有一個處理容器元素的函數,它接受一個容器的引用和一個索引,函數處理完這個索引的元素之後再返回這個元素,一般來說,容器都有重載了“[]"運算符,但有的容器可能返回的是這個元素的值,有的可能返回的是元素的引用,如:

T& operator[](std::size_t index);
// 或者
T operator[](std::size_t index);

這時我們就可以用decltype(auto)來自動推導這個函數的返回值類型,函數的定義如下:

template<typename Container, typename Index>
decltype(auto) process(Container& c, Index i) {
    // processing
    return c[i];
}

當傳進來的容器的operator[]函數返回的是引用時,則上面的函數返回的是引用類型,如果operator[]函數返回的是一個值時,則上面的函數返回的是這個值的類型。

decltype(auto)使用陷阱

最後,對於decltype(auto)能夠推導函數返回值為引用類型這一點,需要提醒一下的是,小心會有下麵的陷阱,如下麵的函數:

decltype(auto) func() {
    int x;
    // do something...
    return x;
}

這裡推導出來的返回值類型是int,並且會拷貝局部變數x的值,這個沒有問題。但如果是這樣的定義:

decltype(auto) func() {
    int x;
    // do something...
    return (x);
}

這個版本返回的是一個引用,它將引用到一個即將銷毀的局部變數上,當這個函數返回後,所返回的引用將引用到一個不存在的變數上,造成引用空懸的問題,程式的結果將是未知的。無論是有意的還是無意的返回一個引用,都要特別小心。

此篇文章同步發佈於我的微信公眾號:深入解析decltype和decltype(auto)

如果您感興趣這方面的內容,請在微信上搜索公眾號iShare愛分享或者微信號iTechShare並關註,或者掃描以下二維碼關註,以便在內容更新時直接向您推送。
image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 拓展閱讀 MySQL View MySQL truncate table 與 delete 清空表的區別和坑 MySQL Ruler mysql 日常開發規範 MySQL datetime timestamp 以及如何自動更新,如何實現範圍查詢 MySQL 06 mysql 如何實現類似 oracl ...
  • C++ 解引用 獲取記憶體地址和值 在上一頁的示例中,我們使用了指針變數來獲取變數的記憶體地址(與引用運算符 & 一起使用)。但是,你也可以使用指針來獲取變數的值,這可以通過使用 * 運算符(解引用運算符)來實現: string food = "Pizza"; // 變數聲明 string* ptr = ...
  • 大家好,我是白夜,今天給大家聊聊面向對象的三大特征——封裝 一、包(package) 1.1、包的引入 先來看看我們之前寫的代碼結構 以上代碼存在的問題 所有類寫在一個目錄下麵,非常難管理,因為以後項目不可能只有這麼幾個類,當類數量很大的時候,就不容易管理了。 不能寫同名但是不同需求的類。 為瞭解決 ...
  • 隨著B端業務快速發展,系統愈趨複雜。我們發起了B端架構升級專項,基於B端業務的特點,從研發規範建設、B端架構基建、系統架構升級和落地保障等多方面提升了B端的架構水平 ...
  • 問題背景 訪問某個 HTTP 功能變數名稱介面,偶發性超時,原因可能多種多樣,比如 DNS 解析問題、網路質量問題、對端服務負載問題等,在客戶端沒有良好埋點的情況下,排查起來比較費勁,只能挨個方向嘗試,這裡送大家一個小工具,可以快速採樣 DNS 解析延遲,快速確認是否是 DNS 解析問題。 使用演示 運行工 ...
  • 前端 https://blog.csdn.net/m0_37613503/article/details/128961447 資料庫 1.用戶表 CREATE TABLE `x_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varc ...
  • 1.VS上安裝Qt擴展 點擊菜單欄【擴展】->【管理擴展】,在搜索框搜索“Qt”, 點擊下載Qt Visual Studio Tools, 以2022版為例,需要關閉所有視窗才能執行安裝 關閉VS後,彈出安裝視窗,等待其安裝完成 2. 新建QT工程測試 等待安裝完成後,添加一個Qt Vertion後 ...
  • 隨著互聯網的迅猛發展,越來越多的應用場景需要進行用戶實名認證,其中手機號機主姓名核驗就是其中必不可少的一環。在電商、游戲、直播、金融等領域,用戶實名認證成為了一個重要的手段,以提高安全性和信任度。 近年來,隨著手機號的普及和使用頻率的增加,手機號的歸屬地信息也逐漸成為人們關註的焦點。手機號機主姓名核 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...