decltype(exp)
其中,exp 表示一個表示式(expression)。int x = 0; decltype(x) y = 1; // y -> int decltype(x + y) z = 0; // z -> int const int& i = x; decltype(i) j = y; // j -> const int & const decltype(z) * p = &z; // *p -> const int, p -> const int * decltype(z) * pi = &z; // *pi -> int , pi -> int * decltype(pi)* pp = π // *pp -> int * , pp -> int * *對程式碼的說明:
const decltype(z)*p
推匯出來的其實是 *p 的型別(const int),然後再進一步運算出 p 的型別。
關於推導規則,有很多種版本:只看上面的推導規則,很難理解decltype(exp) 到底是一個什麼型別。為了更好地講解這些規則的適用場景,下面根據上面的規則分3種情況依次討論:
C++ 標準:ISO/IEC 14882:2011,7.1.6.2 Simple type specif iers,第4 款
MSDN:decltype Type Specif ier,http://msdn.microsoft.com/en-us/library/dd537655.aspx
維基百科:decltype,http://en.wikipedia.org/wiki/Decltype
雖然描述不同,但其實是等價的。為了方便理解,這裡選取了 MSDN 的版本。
class Foo { public: static const int Number = 0; int x; }; int n = 0; volatile const int & x = n; decltype(n) a = n; // a -> int decltype(x) b = n; // b -> const volatile int & decltype(Foo::Number) c = 0; // c -> const int Foo foo; decltype(foo.x) d = 0; // d -> int,類存取表示式變數 a、b、c 保留了表示式的所有屬性(cv、參照)。這裡的結果是很簡單的,按照推導規則1,對於識別符號表示式而言,decltype 的推導結果就和這個變數的型別定義一致。
int& func_int_r(void); // 左值(lvalue,可簡單理解為可定址值) int&& func_int_rr(void); // x值(xvalue,右值參照本身是一個xvalue) int func_int(void); // 純右值(prvalue,將在後面的章節中講解) const int& func_cint_r(void); // 左值 const int&& func_cint_rr(void); // x值 const int func_cint(void); // 純右值 const Foo func_cfoo(void); // 純右值 // 下面是測試語句 int x = 0; decltype(func_int_r()) a1 = x; // a1 -> int & decltype(func_int_rr()) b1 = 0; // b1 -> int && decltype(func_int()) c1 = 0; // c1 -> int decltype(func_cint_r()) a2 = x; // a2 -> const int & decltype(func_cint_rr()) b2 = 0; // b2 -> const int && decltype(func_cint()) c2 = 0; // c2 -> int decltype(func_cfoo()) ff = Foo(); // ff -> const Foo可以看到,按照推導規則2,decltype 的結果和函數的返回值型別保持一致。
warning: type qualif?iers ignored on function return type [-Wignored-qualifiers] cint func_cint(void);
因此,decltype 推匯出來的 c2 是一個 int。struct Foo { int x; }; const Foo foo = Foo(); decltype(foo.x) a = 0; // a -> int decltype((foo.x)) b = a; // b -> const int & int n = 0, m = 0; decltype(n + m) c = 0; // c -> int decltype(n += m) d = c; // d -> int &a 和 b 的結果:僅僅多加了一對括號,它們得到的型別卻是不同的。
#include <vector> template <class ContainerT> class Foo { typename ContainerT::iterator it_; // 型別定義可能有問題 public: void func(ContainerT& container) { it_ = container.begin(); } // ... }; int main(void) { typedef const std::vector<int> container_t; container_t arr; Foo<container_t> foo; foo.func(arr); return 0; }單獨看類 Foo 中的 it_ 成員定義,很難看出會有什麼錯誤,但在使用時,若上下文要求傳入一個 const 容器型別,編譯器馬上會彈出一大堆錯誤資訊。
template <class ContainerT> class Foo<const ContainerT> { typename ContainerT::const_iterator it_; public: void func(const ContainerT& container) { it_ = container.begin(); } // ... };這實在不能說是一個好的解決辦法。若 const 型別的特化只是為了配合疊代器的型別限制,Foo 的其他程式碼也不得不重新寫一次。
template <class ContainerT> class Foo { decltype(ContainerT().begin()) it_; public: void func(ContainerT& container) { it_ = container.begin(); } // ... };是不是舒服很多了?
vector<int> v; // ... decltype(v)::value_type i = 0;在冗長的程式碼中,人們往往只會關心變數本身,而並不關心它的具體型別。比如在上例中,只要知道v是一個容器就夠了(可以提取 value_type),後面的所有演算法內容只需要出現 v,而不需要出現像vector<int> 這種精確的型別名稱。這對理解一些變數型別複雜但操作統一的程式碼片段有很大好處。
typedef decltype(nullptr) nullptr_t; //通過編譯器關鍵字nullptr定義型別nullptr_t typedef decltype(sizeof(0)) size_t;這種定義方法的好處是,從型別的定義過程上就可以看出來這個型別的含義。