Decltype

在C++程序設計語言中，decltype作為類型查詢操作符，用於獲取表達式的數據類型。C++11標準正式引入decltype，主要是為泛型編程而設計，以解決泛型編程中有些類型由模板參數決定而難以（甚至不可能）書寫的痛點。

從語義上看，decltype的設計既適合通用庫編寫者，也對普通開發者友好。當表達式是變量名或函數參數時，由decltype推導出的類型與原始碼中聲明的類型完全一致。與sizeof操作符一樣，decltype不對操作數求值，僅在編譯期分析類型。

## 簡要理解

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

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

```cpp
#include <iostream>

using std::cout;

int main()
{
	int n = 12;
	cout << decltype(n);//實際上decltype與js或C#的typeof完全不一樣
	getchar();
	return 0;
}
```

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

舉例：設開發人員無法確定一個表達式exp的值類型，然而需要聲明一個與之類型匹配的變量，則可以如下聲明：

decltype(exp) x;

這與C++11引入的auto自動類型推導形成了互補：auto用於變量初始化時的類型省略，而decltype更擅長提取已有表達式的類型，常用於模板元編程和返回類型後置等場景。

## 設計構想

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

為此，許多編譯器廠商都基於程序語言現有的功能，自行實現了這類操作符，其實現如typeof，以及一些功能有限，但更易移植的實現，以滿足這一需求。早在C++還未完全標準化的1997年，布萊恩·帕克（Brian Parker）就基於sizeof操作符，提出了一種可移植的解決方案。對此，比爾·吉本斯（Bill Gibbons）則提出，這一方案仍有諸多限制，而且通常來說，直接引入typeof機制效果都更好。2000年10月，安德烈·亞歷山德雷斯庫在IT技術雜誌《Dr. Dobb's Journal》上評論道：「（若）有typeof（操作符），撰寫和理解模板代碼就會便易許多。」他也提到「typeof和sizeof（操作符）有相同的後端，（這是）因為sizeof無論如何必須去計算類型。」Andrew Koenig (程式設計師)與芭芭拉·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）語法。

```cpp
int& foo(int& i);
float foo(float& f);

template <class T> 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）與類成員訪問表達式。

示例：括號對類型推導的影響

```cpp
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，但本應適用才對。（若能解決這一問題，則）這在需要從實例中獲取成員類型（嵌套類型）很有用，如下所示」：

```cpp
vector<int> 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操作符。這一操作符也已納入了CodeGear的C++ Builder 2009、Intel C++編譯器與Clang。

## 外部連結

- 比雅尼·史特勞斯特魯普的「C++11 FAQ」中關於decltype的部分