詳解C++17中的decltype類型推導(dǎo)
引子
在編程過程中,有時我們需要根據(jù)表達式的類型來聲明變量,尤其是在涉及模板編程和泛型編程時,經(jīng)常會遇到這樣的問題:(1)、有些泛型類型由模板參數(shù)決定,但是卻很難或根本無法表示;(2)、需要在編譯時確定變量的類型。
除此之外,我們知道auto在自動類型推導(dǎo)時,會忽略類型的修飾符。如此會導(dǎo)致auto推導(dǎo)的類型會與原表達式的類型存在不一致問題。
為了更好的解決這些問題,從C++11標(biāo)準(zhǔn)開始,C++引入了decltype關(guān)鍵字,其作用是讓編譯器在編譯時識別表達式的類型,方便的的進行類型推導(dǎo),同時也解決泛型編程和模板編程中變量類型表示的問題。
標(biāo)準(zhǔn)演進
decltype是declare type的縮寫。C++11標(biāo)準(zhǔn)引入了decltype的核心功能和推導(dǎo)規(guī)則,C++11以后的各標(biāo)準(zhǔn)都本別對C++11自定的規(guī)則進行擴容和改進。具體演進過程如下所示:
- C++11:引入關(guān)鍵字,并引入decltype的核心功能,用于根據(jù)表達式推導(dǎo)出變量的類型;
- C++14:引入兩個重要改進
- 引入decltype(auto)語法,此語法可用于函數(shù)返回值類型的推導(dǎo)。基于decltype(auto)語法,函數(shù)的返回值類型可通過函數(shù)體的返回值表達式來推導(dǎo),從而簡化函數(shù)返回值類型的聲明。
- 放寬了對不完整類型的限制:在 C++11 中,如果 decltype 推導(dǎo)的表達式結(jié)果是一個不完整類型,那么會導(dǎo)致編譯錯誤。而在 C++14 中,對不完整類型的處理更加寬松,允許使用decltype 推導(dǎo)不完整類型的變量。
- C++17:decltype(atuo)支持非類型模板形參占位符。
C++11
引入關(guān)鍵字,并引入decltype的核心功能,用于根據(jù)表達式推導(dǎo)出變量的類型;當(dāng)使用decltype(e) 推導(dǎo)表達式 e(類型為T)的類型時,C++11標(biāo)準(zhǔn)定義decltype的推導(dǎo)規(guī)則如下:
- 如果是一個未加括號的標(biāo)識符表達式或類成員訪問,那么decltype(e)的推導(dǎo)結(jié)果為e類型T;假如不存在這樣的實體或e是一組重載函數(shù),那么decltype(e)無法推導(dǎo)。而且推導(dǎo)過程const/volatile 限定符會被忽略;
- 如果e是一個可調(diào)用對象,那么decltype(e)推導(dǎo)為可調(diào)用對象返回值的類型;
- 如果e是一個左值,decltype(e)推導(dǎo)為T&。const/volatile 限定符不能忽略;
- 如果e是一個將亡值,decltype(e)推導(dǎo)為T&&,const/volatile 限定符不可忽略;
- 如decltype(e)無法命中上述4情況,decltype(e)將會推導(dǎo)為e的類型T;
為了讓大家更形象的理解這5條規(guī)則,下面我們通過一些示例來說明這五條推導(dǎo)規(guī)則。
示例 1: 未加括號標(biāo)識符表達式
int x = 42; decltype(x) y; // 推導(dǎo)結(jié)果是 int,滿足第1條規(guī)則
示例 2: 加括號的標(biāo)識符表達式
int x = 42; decltype((x)) y = x; // 推導(dǎo)結(jié)果是 int&,滿足第三條規(guī)則
示例3:未加括號的類成員訪問
struct MyClass {
int member;
};
const MyClass obj;
decltype(obj.member) result = obj.member; // 推導(dǎo)結(jié)果是 int, 忽略const/volatile 限定符,滿足第1條規(guī)則示例4:加括號的類成員訪問
struct MyClass {
int member;
};
const MyClass obj;
decltype((bj.member)) result = obj.member; // 推導(dǎo)結(jié)果是 const int&, const/volatile 限定符不能忽略,滿足第3條規(guī)則示例 5: 可調(diào)用對象表達式
int add(int a, int b)
{
return a + b;
}
decltype(add(1, 2)) result; // 推導(dǎo)結(jié)果是 int,滿足第2條規(guī)則示例 6: 將亡值
int x = 42; decltype(std::move(x)) result = std::move(x); // 推導(dǎo)結(jié)果為int&&,std::move(x) 為將亡值
示例 7: 右值表達式
int x = 42; decltype(x + 1) result; // 推導(dǎo)結(jié)果是 int(右值表達式 x + 1 的類型是 int)
示例8:右值引用變量
int&& i = 500; decltype(i) x2; // x2的類型是int&&,滿足第5條
C++14
C++14主要引進了兩個重要改進,他們分別是:放寬對不完整類型的限制;引入decltype(auto)語法。
放寬對不完整類型的限制
C++11標(biāo)準(zhǔn)要求decltype在使用時,推導(dǎo)的表達式必須是完整類型。如果decltype推導(dǎo)的表達式是一個不完整類型,例如某個類的聲明但尚未定義,那么會導(dǎo)致編譯錯誤。C++14對這個限制進行了放寬,允許使用decltype推導(dǎo)不完整類型的變量。這使得編寫一些特定的模板代碼更加方便,因為在某些情況下,可能需要推導(dǎo)出不完整類型。
但是,雖然C++14放寬了對不完整類型的限制,但仍然要求推導(dǎo)的表達式在使用時必須是可見的,即需要在推導(dǎo)時至少對類型進行了前向聲明。否則,將會導(dǎo)致編譯錯誤。
以下是一個示例,演示如何在泛型編程中使用 decltype 推導(dǎo)不完整類型:
template <typename T>
struct Container
{
using ValueType = decltype(*std::declval<T>()); // 使用 decltype 推導(dǎo)不完整類型
// 其他成員和函數(shù)...
};
int main()
{
Container<std::vector<int>> container;
using ValueType = typename decltype(container)::ValueType; // 推導(dǎo)結(jié)果為 int&
return 0;
}decltype(auto)
除了放寬對不完整類型的限制,C++14還有一個特色就是decltype(auto)。decltype(auto)作用是告訴編譯器auto的推導(dǎo)規(guī)則遵循decltype而非auto。不過有一點需要注意就是decltype(auto)必須單獨聲明,不能與其他相結(jié)合。所以下述聲明是不合法的:decltype(auto)*,const decltype(auto), volatile decltype(auto)。
decltype(auto) 的推導(dǎo)規(guī)則如下:
- 如果初始化表達式是一個標(biāo)識符表達式,那么decltype(auto)推導(dǎo)為表達式的類型(const/volatile 限定符和引用修飾符不能忽略);
- 如果初始化表達式是一個函數(shù)調(diào)用表達式,那么decltype(auto)推導(dǎo)為函數(shù)調(diào)用表達式的返回類型;
- 如果初始化表達式是一個左值表達式(如變量名、數(shù)組名、成員訪問等),那么decltype(auto)推導(dǎo)為對應(yīng)左值類型的引用類型(const/volatile 限定符和引用修飾符不能忽略)。
- 如果初始化表達式是一個右值表達式(如字面值、臨時對象、表達式的結(jié)果等),那么decltype(auto)推導(dǎo)為對應(yīng)右值的類型(const/volatile 限定符和引用修飾符不能忽略)。
- 如果初始化表達式是一個將亡值(如移動賦值),那么decltype(auto)推導(dǎo)為對應(yīng)類型的右值引用
示例 1:標(biāo)識符表達式
int x = 42; decltype(auto) y = x; // 推導(dǎo)結(jié)果是 int(x 的類型)
示例 2:函數(shù)調(diào)用表達式
int add(int a, int b)
{
return a + b;
}
decltype(auto) result = add(1, 2); // 推導(dǎo)結(jié)果是 int(add 函數(shù)返回類型)示例 3:左值表達式
const int x = 42; decltype(auto) ref = (x); // 推導(dǎo)結(jié)果是 const int&(x 的引用類型)
示例4:右值表達式
decltype(auto) x2 = 50; // 推導(dǎo)結(jié)果是 int
示例4:將亡值
int x2 = 50; decltype(auto) x3 = std::move(x2); // 推導(dǎo)結(jié)果為int&&
除了變量類型推導(dǎo)以外,在C++14中引入了decltype(auto)作為一種返回類型的語法。它用于在函數(shù)聲明中指定返回類型,該返回類型將從函數(shù)體中的表達式推導(dǎo)而來。
為了更好的理解decltype(auto)作為一種返回類型的語法,我們參考下面三種函數(shù)返回類型自動推導(dǎo)定義方式。
第一種: C++14 基于auto新特性返回值類型自動推導(dǎo)
template<typename Container, typename Index>
auto accessOrUpdate(Container& c, Index i) {
return c[i]; // 返回類推導(dǎo)為c[i]的類型,而且會異常引用限制
}
std::vector<int> v{1,2,3,4,5};
accessOrUpdate(v,2) = 10; // 編譯錯誤,不允許賦值第二種:C++14 基于auto和decltype實現(xiàn)返回值類型推導(dǎo)
template <typename Container, typename Index>
auto accessOrUpdate(Container &c, Index i) -> decltype(c[i]) {
return c[i];
}
std::vector<int> v{1,2,3,4,5};
accessOrUpdate(v,2) = 10;第三種:C++14 decltype(auto)實現(xiàn)返回值類型推導(dǎo)
template <typename Container, typename Index>
decltype(auto) accessOrUpdate(Container &c, Index i) {
return c[i];
}
std::vector<int> v{1,2,3,4,5};
accessOrUpdate(v,2) = 10;對比上述三種函數(shù)返回值類型推導(dǎo),decltype(auto)可讓編譯器根據(jù)表達式的類型自動推導(dǎo)函數(shù)的返回類型,而不需要顯式地指定返回類型。這種方式可簡化代碼,而且推導(dǎo)更加靈活。
C++17
為與auto交相輝映,C++17開始支持decltype(auto)非類型模板。但是需特別注意的是在C++17標(biāo)準(zhǔn)中,非類型模板參數(shù)類型必須是整理類型(int, short, long等),枚舉類型,指針類型,左值引用類型和std::nullptr_t,而自定義類型,浮點數(shù)和字符串則不允許作非類型模板參數(shù)。
template<decltype(auto) n> // C++17 decltype(auto)形參聲明
auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能從花括號初始化器列表推導(dǎo)
{
return {n, n};
}
f<5>(); // n為int
f<(5)>(); // n為int&
f<'a'>(); // n為char
f<('a')>(); // n為char&
f<1.0>(); // 編譯失敗double不能作為模板參數(shù),double不允許做非類型模板參數(shù)。C++20允許字面量類類型作為非類型模板參數(shù)。例如在C++20之前,下述代碼無法編譯通過,而在C++20中則可以編譯通過。
class A {};
template <A a>
class B {};
A a;
B<a> b; // C++20 前編譯失敗,C++20 可以編譯成功。總結(jié)
本文從泛型編程中經(jīng)常會遇到2個常見問題入手,循序漸進的分析了從C++11開始引入的關(guān)鍵字decltype,希望本文可以對大家有所幫助。
相關(guān)文章
C++數(shù)據(jù)結(jié)構(gòu)與算法之判斷一個鏈表是否為回文結(jié)構(gòu)的方法
這篇文章主要介紹了C++數(shù)據(jù)結(jié)構(gòu)與算法之判斷一個鏈表是否為回文結(jié)構(gòu)的方法,結(jié)合實例形式分析了回文結(jié)構(gòu)并結(jié)合實例給出了C++判斷回文的操作技巧,需要的朋友可以參考下2017-05-05
C++實現(xiàn)LeetCode(133.克隆無向圖)
這篇文章主要介紹了C++實現(xiàn)LeetCode(133.克隆無向圖),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07

