1. 程式人生 > >《Effective Modern C++》翻譯--條款2: 理解auto自己主動類型推導

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

attribute double 相同 而不是 enc art spa div ews

條款2: 理解auto自己主動類型推導

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

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

這當然是真的。可是沒關系。

模板類型推導和auto自己主動類型推導是直接匹配的。

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

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

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

而且是常規方法進行調用:

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

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

當使用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。

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

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