在C++中,decltype作為操作符,用于查詢表達式的資料類型。decltype在C++11标準制定時引入,主要是為泛型程式設計而設計,以解決泛型程式設計中,由于有些類型由模闆參數決定,而難以(甚至不可能)表示之的問題。
泛型程式設計在整個1990年代越發流行,對實作類型推導機制的需求也應運而生。為此,許多編譯器廠商都基于程式語言現有的功能,自行實作了這類操作符,其實作如typeof,以及一些功能有限,但更易移植的實作。2002年間,比雅尼·斯特勞斯特魯普提議在C++内标準化這類操作符,并将之加入C++;且建議命之為“decltype”,以反映其具有擷取表達式的“聲明類型”(Declared Type)的功能。
從語義上說,decltype的設計适合于通用庫編寫者與程式設計新手。總體上說,對于目标對象或函數,由decltype推導出的類型與源碼中的定義可精确比對。而正如sizeof操作符一樣,decltype亦不需對操作數求值。
中文名
操作符
外文名
decltype
用 途
查詢表達式
實 質
資料類型
目錄
- 1目錄
- 2設計構想
1目錄編輯
- 1 設計構想
- 2 語義
- 3 可用性
2設計構想編輯
随着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)文法。
int& foo(int& i);float foo(float& f); template <class T> auto transparent_forwarder(T& t) −> decltype(foo(t)) { return foo(t);}
decltype便是本段代碼的核心部分,用于儲存“包裝函數是否傳回一個引用類型”這一資訊 。
3語義編輯
類似于sizeof操作符,decltype也不需對其操作數求值。粗略來說,decltype(e)傳回類型前,進行了如下推導:
- 若表達式e指向一個局部變量、命名空間作用域變量、靜态成員變量或函數參數,那麼傳回類型即為該變量(或參數)的“聲明類型”;
- 若e是一個左值(lvalue,即“可尋址值”),則decltype(e)将傳回T&,其中T為e的類型;
- 若e是一個x值(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<int> v;decltype(v)::value_type i = 0; // int i = 0;
這一問題,以及其他相似問題(關于decltype無法在派生類聲明和析構函數調用中使用),都交由大衛·範德沃德(David Vandevoorde)處理,并在2010年3月投票納入工作日程表。
4可用性編輯
decltype包含于目前的C++标準C++11中,并由許多編譯器以擴充的形式提供:微軟在Visual C++ 2010編譯器中提供了decltype操作符,基本實作了标準委員會提案中所描述的語義,并且在托管代碼或原生代碼中都可使用。據其文檔稱,這一實作“主要對編寫模闆庫的開發者有用。”從2008年3月5日釋出的4.3版開始,GCCC++編譯器也加入了decltype操作符。這一操作符也已納入了Codegear的C++ Builder 2009、Intel C++編譯器與Clang。[1]
5例子編輯
#include <algorithm>
#include <iostream>
#include <iterator>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
using namespace std;
struct Plus {
template <typename T, typename U>
auto operator()(T&& t, U&& u) const
-> decltype(forward<T>(t) + forward<U>(u)) {
return forward<T>(t) + forward<U>(u);
}
};
int main() {
vector<int> i;
i.push_back(1);
i.push_back(2);
i.push_back(3);
vector<int> j;
j.push_back(40);
j.push_back(50);
j.push_back(60);
vector<int> k;
vector<string> s;
s.push_back("cut");
s.push_back("flu");
s.push_back("kit");
vector<string> t;
t.push_back("e");
t.push_back("ffy");
t.push_back("tens");
vector<string> u;
transform(i.begin(), i.end(), j.begin(), back_inserter(k), Plus());
transform(s.begin(), s.end(), t.begin(), back_inserter(u), Plus());
for_each(k.begin(), k.end(), [](int n) { cout << n << " "; });
cout << endl;
for_each(u.begin(), u.end(), [](const string& r) { cout << r << " "; });
結果:
41 52 63
cute fluffy kittens
如果在C++98,你不得不傳遞模闆參數類型來調用plus<int>() 和 plus<string>(),重複聲明一次元素類型。[2]
- 參考資料