《Effective Modern C++》翻譯--條款2: 理解auto本身主動類型推導

條款2: 理解auto本身主動類型推導算法

假設你已經讀過條款1關於模板類型推導的內容,那麼你差點兒已經知道了關於auto類型推導的全部。sql

至於爲何auto類型推導就是模板類型推導僅僅有一個地方感到好奇。那是什麼呢?即模板類型推導包含了模板、函數和參數,而auto類型判斷不用與這些打交道。express

這固然是真的。但是不要緊。編程

模板類型推導和auto本身主動類型推導是直接匹配的。數組

從字面上看,就是從一個算法轉換到還有一個算法而已。ruby

在條款1中。闡述模板類型推導採用的是常規的函數模板:markdown

template<typename T>
void f(ParamType param);

並且是常規方法進行調用:函數

f(expr);    //call f with some expression

在調用f的過程當中。編譯器經過expr推導出T和ParamType的類型。post

當使用auto關鍵字聲明一個變量時。auto關鍵字就扮演者上述模板中T的角色,並且類型說明符與ParamType扮演者相同的角色。比語言描寫敘述更加清晰的是展現他們。因此請看這種樣例:編碼

auto x = 27;

這樣,x的類型說明符與本身同樣。

還有一方面,這樣聲明:

const auto cx = x;

這裏的類型說明符是const auto。而這裏:

const auto& rx = x;

此時類型說明符是const auto&。爲了判斷上面這些樣例中的x,cx和rx變量,編譯器起到的做用就是爲每個聲明提供一個模板,並且使用對應的初始化表達式來調用這些模板:

template<typename T>        //conceptual template for deducing x's type
void func_for_x(T param);   

func_for_x(27);             //conceptual call: param's deduced type is x's type

template<typename T>        //conceptual template for deducing cx's type
void func_for_cx(const T param);   

func_for_cx(x);             //conceptual call: param's deduced type is cx's type

template<typename T>        //conceptual template for deducing rx's type
void func_for_rx(const T& param);   

func_for_rx(x);             //conceptual call: param's deduced type is rx's type

正如我以前所說,auto類型推導與模板類型推導是同樣的。

條款1中依據ParamType和在常規模板中param的類型說明符,把模板類型推導分爲三種狀況。

在經過auto進行變量類型推導時。類型說明符替代了ParamType,但也是分爲三個狀況:

•第一種狀況:類型說明符是一個指針或是引用,但不是universal reference。

•另一種狀況:類型說明符是一個universal reference。

•第三種狀況:類型說明符既不是指針也不是引用。

咱們分別看看第一和第三種狀況的樣例:

auto x = 27;           //case 3 (x is neither ptr nor reference)

const auto cx = x;     //case 3 (cx isn't either)

const auto& rx = x;    //case 1 (rx is non-universal ref.)

另一種狀況正如你期待的那樣:

auto&& uref1 = x;       //x is int and lvalue, so uref1's type is int&

auto&& uref2 = cx;      //cx is const int and lvalue, so uref2's type is const int&

auto&& uref3 = 27;      //27 is int and rvalue, so uref3's type is int&&

條款1中總結了對於非引用類型的說明符,數組和函數名怎樣退化爲指針。這固然相同適用於auto類型推導:

const char name[] = "R. N. Briggs";  //name's type is const char[13]

auto arr1 = name;                    //arr1's type is const char*

auto& arr2 = name;                   //arr2's type is const char (&)[13]

void someFunc(int, double);       //someFunc is a function; type is void(int, double)

auto func1 = someFunc;              //func1's type is void(*)(int, double)

auto& func2 = someFunc;             //func2's type is void(&)(int, double)

正如您所見,auto類型推導真的和模板類型推導是同樣的。

就比如一枚硬幣的兩個面相同。

您確定期待兩者的不一樣。讓我從觀察聲明一個變量並初始化爲27開始,在C++98中。給了你兩種語法選擇:

int x1 = 27;
int x2(27);

C++11中還添加了這個:

int x3 = {27};
int x4{27};

總之。四種語法都獲得了一個結果。就是把變量初始化爲27。

但是正如條款5解釋的那樣。使用auto取代固定的類型來聲明變量有很是多的優點,因此使用auto關鍵字取代上面程序中的int類型。簡單的文本替換咱們就獲得這種代碼:

auto x1 = 27;
auto x2(27);
auto x3 = {27};
auto x4{27};

上面的這些都可以經過編譯。但是與以前的相比,表明的含義已經不一樣了。上面四個表達式中前兩個其實是聲明一個值爲27int類型的變量。然後兩個,是聲明一個類型爲std::initializer_list<int>,並且具備一個值爲27的元素!

auto x1 = 27;        //type is int, value is 27

auto x2(27);         //同上

auto x3 = {27};      //type is std::initializer_list<int>, value is {27}

auto x4{27};         //同上

這是因爲auto類型推導有特俗的規則。當爲auto聲明的類型進行初始化使用封閉的大括號時,則推導的類型爲std::initializer_list。

假設這種類型不能被推導。這種編碼是被編譯器拒絕的:

auto x5 = {1, 2, 3.0}; //error! can't deduce T for std::initializer_list<T>

正如上面凝視中提示的那樣,這種狀況下類型推導會失敗。但更重要的是認清此時採用了兩種類型推導。一個是經過auto完畢對x5的類型推導。因爲x5是使用大括號進行的初始化,因此x5必須被推導爲std::initializer_list類型。但是std::initializer_list是一個模板。對於std::initializer_list<int>
的實例化需要推導T的類型。

這種類型推導發生另一種類型推導的管轄範圍:模板類型推導。

在這個樣例中,類型推導失敗,因爲初始化值不具備單一類型。

對待使用大括號進行初始化,auto類型推導和模板類型推導是有差異的。當一個auto變量被大括號進行初始化時,推導的類型爲std::initializer_list實例化的類型。假設一個模板面臨着對於大括號初始化進行類型推導,這種代碼是被拒絕的。(條款32將解釋完美的轉發)

你會懷疑爲何對於大括號進行初始化。auto類型推導會有特殊的規則呢,而模板類型推導則沒有。我本身對此也表示懷疑。不幸的是,我找不到使人信服的解釋。

但是規則就是規則,這也就意味着對於auto推導用大括號初始化的變量時你必須銘記於心,推導類型的老是std::initializer_list。這是特別重要的是要牢記這一點,假設你面對在大括號包圍初始化值做爲理所固然的初始化的理念。

在C++11編程中。當中最典型的錯誤是你想聲明一個其它類型的變量,卻聲明一個std :: initializer_list變量。重申一下:

auto x1 = 27;    //x1 and x2 are ints
auto x2(27);

auto x3 = {27};  //x3 and x4 are std::initializer_list<int>s
auto x4{27};

這個陷阱使得一些開發人員僅僅有無可奈何的時候才使用大括號初始化變量。(咱們在條款7中再討論。)

對於C++11來講。這裏是全部的內容,但是對於C++14,故事還在繼續。C++14贊成使用auto去推導函數的返回值(參見條款3)。並且C++14中的lambda表達式在參數聲明時可以使用auto類型推導。但是,這裏使用的auto推導採用的是模板推導的規則,而不是auto類型推導的規則。

這就意味着,使用大括號初始化將形成類型推導失敗。因此使用auto做爲返回類型的函數返回一個使用大括號初始化的變量編譯不會經過:

auto createInitList()
{
    return {1, 2, 3}; //error: can't deduce type for {1, 2, 3}
}

相同的道理也適用於C++14中lambda表達式使用auto做爲參數(所以產生一個通用lambda表達式):

std::vector v;
....
auto resetV = [&v](const auto& newValue) { v = newValue;};//C++14 only
....
resetV({1, 2, 3});            //error! can't deduce type for {1, 2, 3}

終於的結果是,假設不使用大括號進行初始化。auto類型推導是和模板類型推導一致的。僅在這種狀況下(使用大括號進行初始化)。auto推導爲一個std:: initializer_list,但模板類型推導會失敗。

請記住:

•auto類型對象一般與模板類型推導是相同的。

•惟一的例外是:使用auto和大括號進行初始化時,本身主動推導爲std::initializer_lists。

•對於使用括號進行初始化,模板類型推導會失敗。

相關文章
相關標籤/搜索