Effective Modern C++翻译(3)-条款2:明白auto类型推导
條款2 明白auto類型推導(dǎo)
如果你已經(jīng)讀完了條款1中有關(guān)模板類型推導(dǎo)的內(nèi)容,那么你幾乎已經(jīng)知道了所有關(guān)于auto類型推導(dǎo)的事情,因?yàn)槌艘粋€(gè)古怪的例外,auto的類型推導(dǎo)規(guī)則和模板的類型推導(dǎo)規(guī)則是一樣的,但是為什么會(huì)這樣呢?模板的類型推導(dǎo)涉及了模板,函數(shù)和參數(shù),但是auto的類型推導(dǎo)卻沒有涉及其中的任何一個(gè)。
?
這確實(shí)是對(duì)的,但這無關(guān)緊要,在auto類型推導(dǎo)和template之間存在一個(gè)直接的映射,可以逐字逐句的將一個(gè)轉(zhuǎn)化為另外一個(gè)。
?
在條款1中,模板類型推導(dǎo)是以下面的模板形式進(jìn)行舉例講解的:
template<typename T> void f(ParamType param);函數(shù)調(diào)用是這樣
f(expr); //用一些表達(dá)式調(diào)用函數(shù)f在f的函數(shù)調(diào)用中,編譯器使用expr來推導(dǎo)T和ParamType的類型。
?
當(dāng)一個(gè)變量用auto進(jìn)行聲明的時(shí)候,auto扮演了模板中的T的角色,變量的類型說明符(The type specifier)相當(dāng)于ParamType,這個(gè)用一個(gè)例子來解釋會(huì)更容易一些,考慮下面的例子:
auto x=27;這里x的類型說明符就是auto本身,在另一方面,在下面這個(gè)聲明中:
const auto cx=x;類型說明符是const auto。
const auto& rx=x;類型說明符是const auto&,在上面的例子中,為了推導(dǎo)x,cx,rx的類型,編譯器會(huì)假裝每一個(gè)聲明是一個(gè)模板,并且用相應(yīng)的初始化表達(dá)式來調(diào)用(compilers act as if there were a template for each declaration as well as a call to that template with the corresponding initializing expression:)
template<typename T> // 產(chǎn)生概念上的模板來 void func_for_x(T param); // 推導(dǎo)x的類型 func_for_x(27); // 概念上的函數(shù)調(diào)用,參數(shù) // 推導(dǎo)出的類型就是x的類型 template<typename T> // 產(chǎn)生概念上的模板來 void func_for_cx(const T param); // 推導(dǎo)cx的類型 func_for_cx(x); // 概念上的函數(shù)調(diào)用,參數(shù) // 推導(dǎo)出的類型就是cx的類型 template<typename T> // 產(chǎn)生概念上的模板來 void func_for_rx(const T& param); // 推導(dǎo)cx的類型 func_for_rx(x); // 概念上的函數(shù)調(diào)用,參數(shù) // 推導(dǎo)出的類型就是rx的類型就像我說的那樣,auto的類型推導(dǎo)和模板的類型推導(dǎo)是一樣的。
?
條款1把模板的類型推導(dǎo)按照ParamType的類型,分成了3種情況,同樣,在auto聲明的變量中,變量的類型說明符(The type specifier)相當(dāng)于ParamType,所以auto類型推導(dǎo)也有3種情況:
- 情況1:類型說明符是一個(gè)指針或是一個(gè)引用,但不是一個(gè)萬能引用(universal reference)
- 情況2:類型說明符是一個(gè)萬能引用(universal reference)
- 情況3:類型說明符既不是指針也不是引用
?
我們?cè)谏厦嬉呀?jīng)舉過了情況1和情況3的例子
auto x = 27; //條款3(x既不是指針也不是引用) const auto cx = x; //條款3(cx既不是指針也不是引用) const auto& rx = x; //條款1(rx不是一個(gè)萬能引用)情況2也像你想的那樣
auto&& uref1 = x; // x的類型是int并且是一個(gè)左值 // 所以u(píng)ref1的類型是 auto&& uref2 = cx; // cx的類型是const int并且是一個(gè)左值 // 所以u(píng)ref2的類型是const int& auto&& uref3 = 27; // 27的類型是int并且是一個(gè)右值 // 所以u(píng)ref3的類型是int&&條款1同樣也討論了數(shù)組和函數(shù)名在非引用類型的類型說明符下,會(huì)退化為指針類型,這當(dāng)然同樣適用于auto的類型推導(dǎo)
const char name[] = "R. N. Briggs"; //name的類型是const char[13] name's type is const char[13] auto arr1 = name; //arr1的類型是const char* auto& arr2 = name; //arr2的類型是 // const char (&)[13] void someFunc(int, double); //someFunc是一個(gè)函數(shù); //類型是void(int, double) auto func1 = someFunc; // func1的類型是 // void (*)(int, double) auto& func2 = someFunc; // func2的類型是 // void (&)(int, double)就像你看到的那樣,auto類型推導(dǎo)其實(shí)和模板類型推導(dǎo)是一樣的,他們就相當(dāng)于硬幣的正反兩個(gè)面。
?
但是在一點(diǎn)上,他們是不同的,如果你想把一個(gè)聲明一個(gè)變量,它的初始值是27,C++98中,你可以使用下面的兩種語法
int x1 = 27; int x2(27);在C++11中,提供對(duì)統(tǒng)一的集合初始化(uniform initialization)的支持,增加下面是聲明方式。
int x3 = {27}; int x4{27};總而言之,上面的4種聲明方式的結(jié)果是一樣的,聲明了一個(gè)變量,它的初始值是27。
?
但是就像條款5解釋的那樣,使用auto聲明變量要比使用確定的類型聲明更有優(yōu)勢(shì),所以將上面代碼變量聲明中的int替換成auto會(huì)是非常好的,直接的文本上的替換產(chǎn)生了下面的代碼:
auto x1 = 27; auto x2(27); auto x3 = {27}; auto x4{27};這些聲明都能夠通過編譯,但他們并非全和替代前有著同樣的意義,前兩個(gè)的確聲明了一個(gè)int類型的變量,初始值為27;然而,后兩個(gè)聲明了一個(gè)std::initializer_list<int>類型的變量,它包括一個(gè)元素,初始值是27;
auto x1 = 27; // 類型是int,初始值是27 auto x2(27); // 同上 auto x3 = {27}; //類型是std::initializer_list<int>//初始值是27 auto x4{27}; //同上?
這是由于auto類型推導(dǎo)的一個(gè)特殊的規(guī)則,當(dāng)變量使用大括號(hào)的初始化式(braced initializer)初始化的時(shí)候,被推導(dǎo)出的類型是std::initializer_list,如果這個(gè)類型不能被推導(dǎo)出來(比如,大括號(hào)的初始化式中的元素有著不同的類型),代碼將不能通過。
auto x5 = {1, 2, 3.0}; // 錯(cuò)誤!無法推導(dǎo)出std::initializer_list<T>中T的類型
就像注釋里指出的的那樣,類型推導(dǎo)在這種情況下失敗了,但是,重要的是認(rèn)識(shí)到這里其實(shí)發(fā)生了兩種形式的類型推導(dǎo),一種來源于auto的使用,x5的類型需要被推導(dǎo)出來,另外因?yàn)閍uto是用大括號(hào)的初始化式初始化的,x5的類型必須被推導(dǎo)為std::initializer_list,但是std::initializer_list是一個(gè)模板,所以實(shí)例化模板std::initizalizer_list<T>意味著T的類型必須被推導(dǎo)出來,在上面的例子中,模板的類型推導(dǎo)失敗了,因?yàn)榇罄ㄌ?hào)里變量類型不是一致的。
?
對(duì)待大括號(hào)的初始化式(braced initializer)的不同是auto類型推導(dǎo)和模板類型推導(dǎo)的唯一區(qū)別,當(dāng)auto變量用一個(gè)大括號(hào)的初始化式(braced initializer)初始化的時(shí)候,推導(dǎo)出的類型是實(shí)例化后的std::initializer_list模板的類型,而模板類型推導(dǎo)面對(duì)大括號(hào)的初始化式(braced initializer)時(shí),代碼將不會(huì)通過(這是由于完美轉(zhuǎn)發(fā)perfect forwarding的結(jié)果,將在條款32中進(jìn)行講解)
?
你可能會(huì)猜想為什么auto類型推導(dǎo)對(duì)于大括號(hào)的初始化式(braced initializer)有著特殊的規(guī)則,而模板類型推導(dǎo)確沒有,我也想知道,不幸的是,我沒有找到一個(gè)吸引人的解釋,但是規(guī)則就是規(guī)則,這意味著,你必須記住如果你用auto聲明一個(gè)變量,并且用大括號(hào)的初始化式進(jìn)行初始化的時(shí)候,推導(dǎo)出的類型總是std::initializer_list,如果你想更深入的使用統(tǒng)一的集合初始化時(shí),你就更要牢記這一點(diǎn),(It’s especially important to bear this in mind if you embrace the philosophy of uniform initialization of enclosing initializing values in braces as a matter of course.)C++11的一個(gè)最經(jīng)典的錯(cuò)誤就是程序員意外的聲明了一個(gè)std::initializer_list類型的變量,但他們的本意卻是想聲明一個(gè)其他類型的變量。讓我再重申一下:
auto x1 = 27; // x1和 x2都是int類型 auto x2(27); auto x3 = {27}; // x3和x4是 auto x4{27}; // std::initializer_list<int>類型?
陷阱的主要原因是一些程序員只有當(dāng)必要的時(shí)候,才使用大括號(hào)的初始化式進(jìn)行初始化)(This pitfall is one of the reasons some developers put braces around their initializers only when they have to. (什么時(shí)候你必須時(shí)候?qū)⒃跅l款7中討論)
?
對(duì)于C++11,這已經(jīng)是一個(gè)完整的故事了,但是對(duì)于C++14,故事還沒有結(jié)束,C++14允許auto來指出一個(gè)函數(shù)的返回類型需要被推導(dǎo)出來(見條款3),C++14的lambda表達(dá)式可能需要在參數(shù)的聲明時(shí)使用auto,不管怎樣,這些auto的使用,采用的是模板類型推導(dǎo)的規(guī)則,而不是auto類型推導(dǎo)規(guī)則,這意味著,大括號(hào)的初始化式會(huì)造成類型推導(dǎo)的失敗,所以一個(gè)帶有auto返回類型的函數(shù)如果返回一個(gè)大括號(hào)的初始化式將不會(huì)通過編譯。
auto createInitList() { return { 1, 2, 3 }; // 錯(cuò)誤: 無法推導(dǎo)出 } // { 1, 2, 3 }的類型同樣,規(guī)則也適用于當(dāng)auto用于C++14的lambda(產(chǎn)生一個(gè)通用的lambda(generic lambda))的參數(shù)類型說明符時(shí),
std::vector v;auto resetV = [&v](const auto& newValue) { v = newValue; }; //只在C++14下允許 … resetV( { 1, 2, 3 } ); //錯(cuò)誤! 無法推導(dǎo)出 //{ 1, 2, 3 }的類型?
最終結(jié)果是auto類型推導(dǎo)和模板類型推導(dǎo)是完全相同的,除非(1)一個(gè)變量被聲明了,(2)它的初始化是用大括號(hào)的初始化式進(jìn)行初始化的(its initializer is inside braces),只有這種情況下,auto下被推導(dǎo)為std::initializer_list,而模板會(huì)失敗。
?
請(qǐng)記住:
- auto的類型推導(dǎo)通常和模板類型推導(dǎo)完全相同。
- 唯一的例外是,當(dāng)變量用auto聲明,并且使用大括號(hào)的初始化式初始化時(shí),auto被推導(dǎo)為std::initializer_list。
- 模板類型推導(dǎo)在面對(duì)大括號(hào)的初始化式(braced initializer)初始化時(shí)會(huì)失敗。
轉(zhuǎn)載于:https://www.cnblogs.com/magicsoar/p/3974659.html
總結(jié)
以上是生活随笔為你收集整理的Effective Modern C++翻译(3)-条款2:明白auto类型推导的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: -bash: belts.awk: co
- 下一篇: 西安的友友们,最近去哪里比较好玩?