Decltype

在 C++ 程式設計語言中，decltype 作為運算子，用於獲取表示式的資料型別。C++11 標準引入 decltype，主要是為泛型程式設計而設計，以解決泛型程式設計中有些型別由模板引數決定而難以（甚至不可能）表示的問題。

從語義上說，decltype 的設計適合於通用庫編寫者或程式設計新手。總體上說，對於變數或函式引數作為表示式，由 decltype 推匯出的型別與原始碼中的定義可精確匹配。而正如 sizeof 運算子一樣，decltype 不對運算元求值。

簡要理解

雖然 C++ 規範或大多數的文件及書籍都將 decltype 定義為一個運算子，但是針對初學者而言，應將此運算子理解為一種特殊的型別宣告，而不是像一些強型別語言那樣將此運算子理解為一個帶返回值的函式。為說明此問題，舉例如下：

對於其它語言的開發者，可能寫出如下的程式碼：

include

using std::cout;

int main

{

	int n = 12;

	cout

相反，可以將 decltype 視為一種特殊的型別宣告方式，C++ 語言裡面類似的功能為自動變數。

舉例：設開發人員無法確定一個表示式 exp 的值型別，然而需要宣告一個與之型別匹配的變數，則可以如下宣告：

decltype (exp ) x;

設計構想

隨著 C++ 引入模板，以及由標準模板庫引領的泛型程式設計逐漸興起，實現一個能獲取表示式型別的機制的需求便由此出現，而這一機制常稱為 typeof。在泛型程式設計中，若型別由函式引數決定，則獲知之常非易事，在需要獲取函式模板例項化的返回型別時尤然。

為此，許多編譯器廠商都基於程式語言現有的功能，自行實現了這類運算子，其實現如 typeof，以及一些功能有限，但更易移植的實現，以滿足這一需求。早在 C++ 還未完全標準化的 1997 年，布萊恩・帕克（Brian Parker）就基於 sizeof 運算子，提出了一種可移植的解決方案。對此，比爾・吉本斯（Bill Gibbons）則提出，這一方案仍有諸多限制，而且通常來說，直接引入 typeof 機制效果都更好。2000 年 10 月，安德烈・亞歷山德雷斯庫在 IT 技術雜誌《》上評論道：“（若）有 typeof（運算子），撰寫和理解模板程式碼就會便易許多。” 他也提到 “typeof 和 sizeof（運算子）有相同的後端，（這是）因為 sizeof 無論如何必須去計算型別。” 與芭芭拉・E・摩（Barbara E. Moo）也談到內建於程式語言中的 typeof 功能非常有用，但也提醒道 “使用時常會引入一些難以發覺的程式錯誤，且尚有無法解決的問題（即並非萬用）。” 並提出可以利用型別轉換（如使用標準模板庫所提供的 typedef），更有效、更通用地實現這一功能。但是，史蒂夫・丹斯特（Steve Dewhurst）則稱如此轉換 “在設計與釋出上花費巨大”，而且 “採用直接提取表示式型別的方法更簡單”（大意）。2011 年間，在一片關於 C++0x 的文章中，克尼格和摩預言道：“decltype 將會廣泛用於為每日的程式編寫提供便利。”

2002 年間， 比雅尼・斯特勞斯特魯普提議擴充 C++ 程式語言，為之引入查詢表示式型別，以及不必指明型別便可初始化物件的機制。斯特勞斯特魯普注意到，在 GCC 與編譯器中，typeof 所提供的 “引用丟棄”（reference-dropping）語義可能存在問題；另一方面，若使用基於表示式左值性、返回一個引用型別的運算子實現之，又難以理解。於是，在呈交給 C++ 標準委員會的初始提案中，便將兩種實現方法雜糅起來：只有當表示式的宣告型別包含一個引用時，運算子才會返回一個引用型別。為強調推匯出的型別能確實反映表示式的宣告型別，提案中提議將此運算子命名為 decltype。提案還提及了 decltype 的一項主要設計初衷，也即讓編寫完美的轉發函式成為可能。在程式設計時，程式設計師有時需要編寫一個泛型轉發函式，使之不論以何種型別例項化，都能返回同於包裝函式的型別，而若無 decltype 運算子，就幾乎不可能做到這一點。decltype 的樣例程式碼如下所示，其中利用了 C++11 標準中的 “返回型別後置”（trailing-return-type）語法。

int& foo (int& i);

float foo (float& f);

template auto transparent_forwarder (T& t) −> decltype (foo (t)) {return foo (t);

}

decltype 便是本段程式碼的核心部分，用於儲存 “包裝函式是否返回一個引用型別” 這一資訊 。

語義

類似於 sizeof 運算子，decltype 不對其運算元求值。粗略來說，decltype (e) 返回型別前，進行了如下推導：

若表示式 e 為一個無括號的變數、函式引數、類成員訪問，那麼返回型別即為該變數或引數或類成員在源程式中的 “宣告型別”；
否則的話，根據表示式的值分類（value categories），設 T 為 e 的型別：
若 e 是一個左值（lvalue，即 “可定址值”），則 decltype (e) 將返回 T&；
若 e 是一個臨終值（xvalue），則返回值為 T&&；
若 e 是一個純右值（prvalue），則返回值為 T。

這些語義是為滿足通用庫編寫者的需求而設計，但由於 decltype 的返回型別總與物件（或函式）的定義型別相匹配，這對程式設計新手來說也更為直觀。更正式地說，規則 1 適用於不帶括號的識別符號表示式（id-expression）與類成員訪問表示式。示例如下：

const int&& foo ;

const int bar ;

int i;

struct A {double x;};

const A* a = new A ;

decltype (foo ) x1; // 型別為 const int&&

decltype (bar ) x2; // 型別為 int

decltype (i) x3; // 型別為 int

decltype (a->x) x4; // 型別為 double

decltype ((a->x)) x5; // 型別為 const double&

由上可見，最後兩個對 decltype 的呼叫，返回結果有所不同。這是因為，帶括號的表示式 (a->x) 既非 “識別符號表示式”，亦非類訪問表示式，因而未指向一個命名物件，而是一個左值，於是推導型別便為 “指向表示式型別的引用”，亦即 const double&。

在 2008 年 12 月，雅克・雅爾維（Jaakko Järvi）向標準委員會指出一個問題：在 C++ 中，“帶限定識別符號”（qualified-id）無法由 decltype 作成，而這正與 “decltype (e) 可作‘型別定義名’（typedef-name）看待” 的設計初衷不一致。在評論標準委員會為 C++0x（C++11 前名）制定的正式草案時，日本 ISO 會員成員提到，“一個定義域運算子（::）不適用於 decltype，但本應適用才對。（若能解決這一問題，則）這在需要從例項中獲取成員型別（巢狀型別）很有用，如下所示”：

vector v;

decltype (v)::value_type i = 0; //int i = 0;

這一問題，以及其他相似問題（關於 decltype 無法在衍生類宣告和解構函式呼叫中使用），都交由大衛・範德沃德（David Vandevoorde）處理，並在 2010 年 3 月投票納入工作日程表。解決辦法是：如果表示式是純使用者定義型別（plain user defined type），即不是引用、指標、函式等，則 decltype 表示式返回類名。這意味著可以訪問巢狀型別。甚至可以用 decltype (expr) 作為派生時的基型別。

可用性

decltype 包含於當前的 C++ 標準 C++11 中，並由許多編譯器以擴充套件的形式提供：微軟在 Visual C++ 2010 編譯器中提供了 decltype 運算子，基本實現了標準委員會提案中所描述的語義，並且在託管程式碼或原生程式碼中都可使用。據其文件稱，這一實現 “主要對編寫模板庫的開發者有用。” 從 2008 年 3 月 5 日釋出的 4.3 版開始，GCC C++ 編譯器也加入了 decltype 運算子。這一運算子也已納入了的 C++ Builder 2009、Intel C++ 編譯器與 Clang。