天天看點

c++11 之 decltype

在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)傳回類型前,進行了如下推導:

  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月投票納入工作日程表。

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] 

參考資料

繼續閱讀