類型推導可以說是C++模擬動態語言特性的起點,就從這里開始這個系列吧。
auto
使用迭代器的時候,類型總是一件煩心的事。
vector<vector<int> > v; vector<vector<int> >::iterator it = v.begin();
函數指針也同樣, 類型聲明很蛋疼:
int add(int x,int y){ return x+y; } int main(){ int (*func)(int,int) = add; cout<<func(1,2)<<endl; }
我既然把v.begin()賦給it, 類型已經在編譯期確定了,編譯器知道正確的類型是什么,再加一個類型聲明實在很繁瑣。C++11 有了auto。我們可以這樣寫:
vector<vector<int>> v; // C++11 可以不用在'>>'之間加空格了!
auto it = v.begin(); auto func = add;
編譯器會根據值的類型,推導出autob變量。類型的推導是在編譯期就完成的,仍是靜態類型,和腳本語言不同。實際上是一個語法糖。但由於C++對模板的大量使用,一個變量的類型有時過於復雜難以寫出,這樣的語法糖是必要的。
很簡單,不是么?(實際會有一些細節問題,文末再說)
decltype
如果我們只是想用一個編譯器推導的類型聲明一個變量,但不初始化,auto無能為力。此時可以用decltype。
int add(int x,int y){ return x+y; } int main(){ double i=0; decltype(i) a; // double decltype(add()) b; //int 注意括號。不帶括號就是函數指針了。 }
需要說明,decltype仍然是編譯期行為,add()函數不會真正執行,只是利用它推導了類型。
返回類型后置
考慮一個模板函數
template<typename U, typename V> ??? foo(U u, V v){ ... return u*v; }
總之我要在這個函數做點事情,最后返回一個u*v類型的東西。返回類型該怎么寫?
有了decltype,似乎可以這么:
template<typename U, typename V> decltype(u*v) foo(U u, V v){ //WRONG! return u*v; }
但是不行!u,v不在作用域。
u,v不能用,自己搞出一個可以用的U,V類型的變量總行了吧XD
template<typename U, typename V> decltype(U(0)*V(0)) foo(U u, V v){ return u*v; }
試一下,foo(2,3.3)返回6.6,貌似正常。但是!你怎么知道U,V可以接受一個0作為構造參數的?U,V不一定是數值,有可能是重載了'*'操作符的奇怪東西。沒關系,指針總是可以這樣初始化的:
template<typename U, typename V> decltype(*(U*)(0)**(V*)(0)) foo(U u, V v){ return u*v; }
但是你不覺得太丑了么。。。
C++11引入了返回類型后置解決這個問題,
template<typename U, typename V> auto foo(U u, V v) -> decltype(u*v){ return u*v; }
語法很清晰。總之就是,返回類型后置以后,就進入函數的作用域了,參數都能用了!
另外還有一個用處,假設你要寫個類
//A.h struct A { struct B {}; B func(); }; //A.cpp A::B A::func(){ return B(); }
那個A::B怎么看都別扭,用返回類型后置,可以這樣:
auto A::func() -> B { return B(); }
再來一次,后置的返回類型,處在函數作用域里,B已經可見,不需要A::修飾了。
但你肯定有一個問題,既然我函數代碼已經給出了,編譯器完全可以推斷出返回類型是什么,為什么還要自己顯式給出返回類型?如果能這樣就好了:
template<typename U, typename V> auto foo(U u, V v){ return u*v; }
但是很遺憾,不行。由於“復雜性”,標准沒有這樣做。這也容易理解,假如你的程序編譯成了庫,只給了別人頭文件里的函數簽名,函數體對編譯器是不可見的,那你這個auto算什么意思?
但事情也不那么絕對,通過匿名函數返回值類型可省略這個特性加上auto,有些情況下我們的確可以不用寫返回類型。以后再談。
一些細節
雖然我很不想寫細節,但C++的東西總不是那么簡單,為了完整性,把這些黑暗角落放到最后,希望不會影響對以上特性的好感...
- auto+引用
int i = 0; int& r = i; auto a = r; // type of a? a = 10; cout<<i<<endl; // value of i?
直觀上感覺,r是i的引用,a自然也會推導出int&這個類型。但實際上,r只是i的別名,a類型的推導結果還是int。於是,對a的修改和i無關,結果輸出0。如果想推導出引用,需要這樣
auto& a = r; // auto& a = i也可以
- auto+const
還有一點,auto會忽略頂層的const。
const int i = 0; auto a = i; // a: int audo b = &i // b: const int*
其中,a是普通的int, 因為i是const int,auto把頂層的const忽略了。b是const int*, 這個可以這樣看,const int*實際上是“(const int)*”(當然代碼不能這樣寫),const不是頂層修飾符了,就沒有忽略。
如果想得到推導出一個const類型,需要
const auto a = i; // a: const int
另外,decltype和auto不同,它不會忽略引用和頂層const修飾。
const int i=0; int j=0, &r = j; decltype(i) a; //const int decltype(r) b; //int&
最后,比較特別的是,如果decltype里面的表達式被包含在括號中,視為對表達式求值的類型。
decltype((i)) a;//error: a is int& and must be initialized
對i求值返回的是int&。