C++11初探:類型推導,auto和decltype


類型推導可以說是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&。

 

目錄

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM