深入解析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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...