深入解析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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...