Decltype

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

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

## 簡要理解

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

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

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

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

decltype(exp()) x;

## 設計構想

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

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

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

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）與類別成員存取表達式。範例如下：

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

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

這一問題，以及其他相似問題（關於 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 的部分（頁面存檔備份，存於網際網路檔案館）