decltype 是
GCC 實現的第一個C++ 11 新特性。它實際上起源於一個相當古老的GNU 擴展關鍵字——__typeof__。這個非標準關鍵字也能夠在C 語言中使用,GNU Compiler Collection 的專業用戶可能對它更熟悉一些。2008 年,GCC 4.3.x 就實現了這個特性,同時去除了__typeof__ 的一些缺點。現在,decltype 和__decltype 兩個關鍵字在GCC 中都適用;前者只能用在C++ 11 模式下,後者可以同時應用於C++ 11 和C++ 98 模式。__typeof__ 則已經停止使用。
下面來看看
decltype 的基本使用。簡單來說,decltype 關鍵字用於查詢表達式的類型。不過,這只是其基本用法。當這個簡單的表述同C++ 11 的其它特性結合起來之後,一些意想不到的有趣用法就此產生。
decltype 的語法是
decltype ( expression )
這裏的括號是必不可少的。根據前面的說法,decltype 的作用是“查詢表達式的類型”,因此,上面語句的效果是,返回expression 表達式的類型。注意,decltype 僅僅“查詢”表達式的類型,並不會對表達式進行“求值”。
先看一個最基礎的例子:
const int&& foo();
int i;
struct A {
double x;
};
const A* a = new A();
decltype(foo()) x1; // const int&& (1)
decltype(i) x2; // int (2)
decltype(a->x) x3; // double (3)
decltype((a->x)) x4; // double& (4)
傳統的__typeof__ 有一個頗爲詬病的地方,在於不能很好地處理引用類型。而decltype 則沒有這個問題。而decltype 實際上更好地融入了C++ 11 類型系統,來看一個比較複雜的例子:
int i;
float f;
double d;
typedef decltype(i + f) type1; // float
typedef decltype(f + d) type2; // double
typedef decltype(f < d) type3; // bool
上面的例子清楚看出,decltype 能夠很好地處理類型轉換這裏問題。或許你會對上面代碼中的
(4) 心生疑問。爲什麼decltype((a->x)) 會是double&?這是由decltype 的定義決定的。decltype 判別的規律還是比較複雜的:
對於decltype( e ) 而言,其判別結果受以下條件的影響:
1.
如果e 是一個標識符或者類成員的訪問表達式,則decltype(e) 就是e 所代表的實體的類型。如果沒有這種類型或者e 是一個重載函數集,那麼程序是錯誤的(上例中的(2) 和(3));
2.
如果e 是一個函數調用或者一個重載操作符調用(忽略e 外面的括號),那麼decltype(e) 就是該函數的返回類型(上例中的(1));
3.
如果e 不屬於以上所述的情況,則假設e 的類型是T:當e 是一個左值時,decltype(e) 就是
T&;否則(e 是一個右值),decltype(e) 是T(上例中的(4) 即屬於這種情況。在這個例子中,e 實際是(a->x),由於有這個括號,因此它不屬於前面兩種情況,所以應當以本條作爲判別依據。而(a->x) 是一個左值,因此會返回double &)。說了這麼多,decltype 到底有什麼用?事實上,decltype 在複雜的模板編程中非常有用。不過,這需要結合我們前面提到的auto 關鍵字。舉個經典的例子,請看下面的代碼:
template
??? foo(T t, U u)
{
return t + u;
}
問號這裏該填寫什麼呢?問題的關鍵在於,我們正在處理模板,因此我們根本不知道
T 和U 的實際類型。即使這兩個模板值實際都是C++ 內置類型,我們也無法確切地知道它們的和的類型。在過去的GNU C++ 運行時庫中,我們可以使用前面說過的__typeof__ 擴展,編寫相當難看的代碼:
template
decltype((*(T*)0)+(*(U*)0)) foo(T t, U u)
{
return t + u;
}
而在C++11 中,我們可以使用auto 關鍵字:
template
auto foo(T t, U u) -> decltype(t + u)
{
return t + u;
}
看起來好多了吧!現在這已經是語言級別的支持了。最後,我們來看一個更加實際的例子。在GCC 的
C++11 運行時庫中有這麼一段代碼:
template<typename _IteratorL, typename _IteratorR>
inline auto operator-(const reverse_iterator<_IteratorL>& __x,const reverse_iterator<_IteratorR>& __y)-> decltype(__y.base()- __x.base())
{
return __y.base()- __x.base();
}
現在,這段代碼應該更加清晰了。這實際上解決了
C++ 98 實現的一個真正的bug。在GCC 的C++ 98 版本中,這段代碼是這樣的:
template<typename _IteratorL, typename _IteratorR>
inline
typename reverse_iterator<_IteratorL>::difference_type operator-(constreverse_iterator<_IteratorL>& __x,const reverse_iterator<_IteratorR>& __y)
{
return __y.base()- __x.base();
}
這段代碼只有在這兩方的
difference_type 相同時才適用。