decltype

decltype

decltype,在C++中,作為操作符,用於查詢表達式的數據類型。decltype在C++11標準制定時引入,主要是為泛型編程而設計,以解決泛型編程中,由於有些類型由模板參數決定,而難以(甚至不可能)表示之的問題。 泛型編程在整個1990年代越發流行,對實現類型推導機制的需求也應運而生。為此,許多編譯器廠商都基於程式語言現有的功能,自行實現了這類操作符,其實現如typeof,以及一些功能有限,但更易移植的實現。2002年間,比雅尼·史特勞斯特魯普提議在C++內標準化這類操作符,並將之加入C++;且建議命之為“decltype”,以反映其具有獲取表達式的“聲明類型”(Declared Type)的功能。 從語義上說,decltype的設計適合於通用庫編寫者與編程新手。總體上說,對於目標對象或函式,由decltype推導出的類型與源碼中的定義可精確匹配。而正如sizeof操作符一樣,decltype亦不需對運算元求值。

目錄

•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>(),重複聲明一次元素類型。

相關詞條

熱門詞條

聯絡我們