目錄
•1 設計構想
•2 語義
•3 可用性
設計構想
隨著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便是本段代碼的核心部分,用於保存“包裝函式是否返回一個引用類型”這一信息 。
語義
類似於sizeof操作符,decltype也不需對其運算元求值。粗略來說,decltype(e)返回類型前,進行了如下推導:
若表達式e指向一個局部變數、命名空間作用域變數、靜態成員變數或函式參數,那么返回類型即為該變數(或參數)的“聲明類型”;
若e是一個左值(lvalue,即“可定址值”),則decltype(e)將返回T&,其中T為e的類型;
若e是一個x值(xvalue),則返回值為T&&;
若e是一個純右值(prvalue),則返回值為T。
1.若表達式e指向一個局部變數、命名空間作用域變數、靜態成員變數或函式參數,那么返回類型即為該變數(或參數)的“聲明類型”;
2.若e是一個左值(lvalue,即“可定址值”),則decltype(e)將返回T&,其中T為e的類型;
3.若e是一個x值(xvalue),則返回值為T&&;
4.若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月投票納入工作日程表。
可用性
decltype包含於當前的C++標準C++11中,並由許多編譯器以擴展的形式提供:微軟在Visual C++ 2010編譯器中提供了decltype操作符,基本實現了標準委員會提案中所描述的語義,並且在託管代碼或原生代碼中都可使用。據其文檔稱,這一實現“主要對編寫模板庫的開發者有用。”從2008年3月5日發布的4.3版開始,GCCC++編譯器也加入了decltype操作符。這一操作符也已納入了Codegear的C++ Builder 2009、Intel C++編譯器與Clang。
例子
#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 << " "; });
cout << endl;
}
結果:
41 52 63
cute fluffy kittens
如果在C++98,你不得不傳遞模板參數類型來調用 plus<int>() 和 plus<string>(),重複聲明一次元素類型。