Effective Modern C++翻譯(3)-條款2:明白auto類型推導

條款2 明白auto類型推導

如果你已經讀完了條款1中有關模板類型推導的內容,那么你幾乎已經知道了所有關于auto類型推導的事情,因為除了一個古怪的例外,auto的類型推導規則和模板的類型推導規則是一樣的,但是為什么會這樣呢?模板的類型推導涉及了模板,函數和參數,但是auto的類型推導卻沒有涉及其中的任何一個。

?

這確實是對的,但這無關緊要,在auto類型推導和template之間存在一個直接的映射,可以逐字逐句的將一個轉化為另外一個。

?

在條款1中,模板類型推導是以下面的模板形式進行舉例講解的:

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

函數調用是這樣

f(expr); //用一些表達式調用函數f

在f的函數調用中,編譯器使用expr來推導T和ParamType的類型。

?

當一個變量用auto進行聲明的時候,auto扮演了模板中的T的角色,變量的類型說明符(The type specifier)相當于ParamType,這個用一個例子來解釋會更容易一些,考慮下面的例子:

auto x=27;

這里x的類型說明符就是auto本身,在另一方面,在下面這個聲明中:

const auto cx=x;

類型說明符是const auto。

const auto& rx=x;

類型說明符是const auto&,在上面的例子中,為了推導x,cx,rx的類型,編譯器會假裝每一個聲明是一個模板,并且用相應的初始化表達式來調用(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>              // 產生概念上的模板來
void func_for_x(T param);         // 推導x的類型
func_for_x(27);                   // 概念上的函數調用,參數
                                  // 推導出的類型就是x的類型
template<typename T>              // 產生概念上的模板來
void func_for_cx(const T param);  // 推導cx的類型
func_for_cx(x);                   // 概念上的函數調用,參數
                                  // 推導出的類型就是cx的類型
template<typename T>              // 產生概念上的模板來
void func_for_rx(const T& param); // 推導cx的類型
func_for_rx(x);                   // 概念上的函數調用,參數
                                  // 推導出的類型就是rx的類型

就像我說的那樣,auto的類型推導和模板的類型推導是一樣的。

?

條款1把模板的類型推導按照ParamType的類型,分成了3種情況,同樣,在auto聲明的變量中,變量的類型說明符(The type specifier)相當于ParamType,所以auto類型推導也有3種情況:

  • 情況1:類型說明符是一個指針或是一個引用,但不是一個萬能引用(universal reference)
  • 情況2:類型說明符是一個萬能引用(universal reference)
  • 情況3:類型說明符既不是指針也不是引用

?

我們在上面已經舉過了情況1和情況3的例子

auto x = 27;        //條款3(x既不是指針也不是引用) 
const auto cx = x;  //條款3(cx既不是指針也不是引用)
const auto& rx = x; //條款1(rx不是一個萬能引用)

情況2也像你想的那樣

auto&& uref1 = x;    // x的類型是int并且是一個左值
                     // 所以uref1的類型是
auto&& uref2 = cx;   // cx的類型是const int并且是一個左值
                     // 所以uref2的類型是const int&

auto&& uref3 = 27;   // 27的類型是int并且是一個右值
                     // 所以uref3的類型是int&&  

條款1同樣也討論了數組和函數名在非引用類型的類型說明符下,會退化為指針類型,這當然同樣適用于auto的類型推導

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是一個函數;
                                    //類型是void(int, double)
auto func1 = someFunc;              // func1的類型是
                                    // void (*)(int, double)
auto& func2 = someFunc;             // func2的類型是
                                    // void (&)(int, double)

就像你看到的那樣,auto類型推導其實和模板類型推導是一樣的,他們就相當于硬幣的正反兩個面。

?

但是在一點上,他們是不同的,如果你想把一個聲明一個變量,它的初始值是27,C++98中,你可以使用下面的兩種語法

int x1 = 27;
int x2(27);

在C++11中,提供對統一的集合初始化(uniform initialization)的支持,增加下面是聲明方式。

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

總而言之,上面的4種聲明方式的結果是一樣的,聲明了一個變量,它的初始值是27。

?

但是就像條款5解釋的那樣,使用auto聲明變量要比使用確定的類型聲明更有優勢,所以將上面代碼變量聲明中的int替換成auto會是非常好的,直接的文本上的替換產生了下面的代碼:

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

這些聲明都能夠通過編譯,但他們并非全和替代前有著同樣的意義,前兩個的確聲明了一個int類型的變量,初始值為27;然而,后兩個聲明了一個std::initializer_list<int>類型的變量,它包括一個元素,初始值是27;

auto x1 = 27;    // 類型是int,初始值是27
auto x2(27);     // 同上
auto x3 = {27};  //類型是std::initializer_list<int>//初始值是27
auto x4{27};     //同上

?

這是由于auto類型推導的一個特殊的規則,當變量使用大括號的初始化式(braced initializer)初始化的時候,被推導出的類型是std::initializer_list,如果這個類型不能被推導出來(比如,大括號的初始化式中的元素有著不同的類型),代碼將不能通過。

auto x5 = {1, 2, 3.0}; // 錯誤!無法推導出std::initializer_list<T>中T的類型


就像注釋里指出的的那樣,類型推導在這種情況下失敗了,但是,重要的是認識到這里其實發生了兩種形式的類型推導,一種來源于auto的使用,x5的類型需要被推導出來,另外因為auto是用大括號的初始化式初始化的,x5的類型必須被推導為std::initializer_list,但是std::initializer_list是一個模板,所以實例化模板std::initizalizer_list<T>意味著T的類型必須被推導出來,在上面的例子中,模板的類型推導失敗了,因為大括號里變量類型不是一致的。

?

對待大括號的初始化式(braced initializer)的不同是auto類型推導和模板類型推導的唯一區別,當auto變量用一個大括號的初始化式(braced initializer)初始化的時候,推導出的類型是實例化后的std::initializer_list模板的類型,而模板類型推導面對大括號的初始化式(braced initializer)時,代碼將不會通過(這是由于完美轉發perfect forwarding的結果,將在條款32中進行講解)

?

你可能會猜想為什么auto類型推導對于大括號的初始化式(braced initializer)有著特殊的規則,而模板類型推導確沒有,我也想知道,不幸的是,我沒有找到一個吸引人的解釋,但是規則就是規則,這意味著,你必須記住如果你用auto聲明一個變量,并且用大括號的初始化式進行初始化的時候,推導出的類型總是std::initializer_list,如果你想更深入的使用統一的集合初始化時,你就更要牢記這一點,(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的一個最經典的錯誤就是程序員意外的聲明了一個std::initializer_list類型的變量,但他們的本意卻是想聲明一個其他類型的變量。讓我再重申一下:

auto x1 = 27;    // x1和 x2都是int類型
auto x2(27);
auto x3 = {27};  // x3和x4是
auto x4{27};     // std::initializer_list<int>類型

?

陷阱的主要原因是一些程序員只有當必要的時候,才使用大括號的初始化式進行初始化)(This pitfall is one of the reasons some developers put braces around their initializers only when they have to. (什么時候你必須時候將在條款7中討論)

?

對于C++11,這已經是一個完整的故事了,但是對于C++14,故事還沒有結束,C++14允許auto來指出一個函數的返回類型需要被推導出來(見條款3),C++14的lambda表達式可能需要在參數的聲明時使用auto,不管怎樣,這些auto的使用,采用的是模板類型推導的規則,而不是auto類型推導規則,這意味著,大括號的初始化式會造成類型推導的失敗,所以一個帶有auto返回類型的函數如果返回一個大括號的初始化式將不會通過編譯。

auto createInitList()
{
return { 1, 2, 3 };  // 錯誤: 無法推導出
}                    // { 1, 2, 3 }的類型

同樣,規則也適用于當auto用于C++14的lambda(產生一個通用的lambda(generic lambda))的參數類型說明符時,

std::vector v;auto resetV =
[&v](const auto& newValue) { v = newValue; }; //只在C++14下允許 
…
resetV( { 1, 2, 3 } );                        //錯誤! 無法推導出
                                              //{ 1, 2, 3 }的類型

?

最終結果是auto類型推導和模板類型推導是完全相同的,除非(1)一個變量被聲明了,(2)它的初始化是用大括號的初始化式進行初始化的(its initializer is inside braces),只有這種情況下,auto下被推導為std::initializer_list,而模板會失敗。

?

請記住:

  • auto的類型推導通常和模板類型推導完全相同。
  • 唯一的例外是,當變量用auto聲明,并且使用大括號的初始化式初始化時,auto被推導為std::initializer_list。
  • 模板類型推導在面對大括號的初始化式(braced initializer)初始化時會失敗。

轉載于:https://www.cnblogs.com/magicsoar/p/3974659.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/376657.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/376657.shtml
英文地址,請注明出處:http://en.pswp.cn/news/376657.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

信息論與編碼復習

若信源有m種消息&#xff0c;且每個消息是以相等可能產生的&#xff0c;則該信源的信息量可表示為Ilogm。 信息率是通過接收到的信息可獲得的發送信息的信息量,即互信息。單位:bit/符號。 信息速率是單位時間內傳輸的信息量。單位:bit/s 碼字CodeWord。由若干個碼元組成&#x…

拖拽碰撞效果最終版

拖拽碰撞效果最終版&#xff0c;沒準還有bug&#xff0c;不過現在在各個瀏覽器下效果是對的&#xff0c;代碼需要精簡一些&#xff0c;以后有時間了在弄吧&#xff0c;現在先不理了&#xff0c;感冒了&#xff0c;沒有心情整理 <!DOCTYPE HTML> <html lang"en-US…

Python 如何利用函數修改函數外list?

#在函數內修改列表的時候&#xff0c;在列表后面加上[:]&#xff0c;無論幾維列表均可。 def foo(listA):listA[:] [1,2,3] def foo2(listB):listB [1,2,3] listA [4,5,6] listB [4,5,6] foo(listA) foo2(listB) print listA #result: [1,2,3] print listB #result: [4,5,6…

圖片壓縮android bitmap compress(圖片壓縮)

本文純屬個人見解&#xff0c;是對前面學習的總結&#xff0c;如有描述不正確的地方還請高手指正~ 有些場景中&#xff0c;須要照相并且上傳到服務&#xff0c;但是由于圖片的巨細太大&#xff0c;那么就 上傳就 會很慢(在有些網絡情況下)&#xff0c;而且很耗流量&#xff0c;…

linux進程間通信快速入門【一】:管道編程

介紹 管道本質上就是一個文件&#xff0c;前面的進程以寫方式打開文件&#xff0c;后面的進程以讀方式打開。這樣前面寫完后面讀&#xff0c;于是就實現了通信。雖然實現形態上是文件&#xff0c;但是管道本身并不占用磁盤或者其他外部存儲的空間。在Linux的實現上&#xff0c;…

返回長度hdu 1518 square

查了好多資料&#xff0c;發現還是不全&#xff0c;干脆自己整理吧&#xff0c;至少保證在我的做法正確的&#xff0c;以免誤導讀者&#xff0c;也是給自己做個記載吧&#xff01; 題目的意思是比較明顯的&#xff0c;就是當初給你m根木棒&#xff0c;當初讓你判斷利用這些木棒…

POJ 3233 Matrix Power Series 矩陣快速冪 + 二分

題意&#xff1a;求矩陣的次方和 解題思路&#xff1a;最容易想到方法就是兩次二分因為 我們可以把一段 A^1 A^2 .......A^K 變成 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) 當k 為奇數的時候 或者 A^1 ..A^(K/2) ( A^1 ..A^(K/2))*(A^(k/2)) A^K 當K 為偶數的時候…

時間序列進行分析的一些手法以及代碼實現(移動平均、指數平滑、SARIMA模型、時間序列的(非)線性模型)

文章目錄1、移動平均moving average方法weighted average方法2、指數平滑單指數平滑 exponential_smoothing雙指數平滑三指數平滑 Triple exponential smoothing3、平穩性以及時間序列建模SARIMA模型4、時間序列的&#xff08;非&#xff09;線性模型時間序列的滯后值使用線性回…

政權組織形式

神馬國家結構、政權組織形式的 現在我比較明確的是英國的政權組織形式是式君主立憲制、美國是總統制、中國是人民代表大會制。 目前,世界各國采用的國家結構可分為單一制和復合制兩大類。其中&#xff0c;復合制國家結構形式主要包括聯邦制和邦聯制兩種類型。英國、法國、意大利…

三大平衡樹(Treap + Splay + SBT)總結+模板

Treap樹 核心是 利用隨機數的二叉排序樹的各種操作復雜度平均為O(lgn) Treap模板&#xff1a; #include <cstdio> #include <cstring> #include <ctime> #include <iostream> #include <algorithm> #include <cstdlib> #include <cmath…

mysqld進程 ut_delay 占用率過高

采用性能分析工具perf top -p mysqld進程 在測試mysql數據庫時&#xff0c;用perf top如果看到熱點函數是ut_delay或者_raw_spin_lock的話&#xff0c;說明鎖爭用比較嚴重。 ut_delay這是innodb的一個自旋瑣。也就是說&#xff0c;在這里由于鎖等待&#xff0c;innodb不停地在…

TClientDataSet使用要點

TClientDataSet控件繼承自TDataSet&#xff0c;其數據存儲文件格式擴展名為 .cds&#xff0c;是基于文件型數據存儲和操作的控件。該控件封裝了對數據進行操作處理的接口和功能&#xff0c;而本身并不依賴上述幾種數據庫驅動程序&#xff0c;基本上能滿足單機"瘦"數據…

滑動窗口在重構數據集的作用

step1&#xff1a;使用滑動窗口重構數據集 給定時間序列數據集的數字序列&#xff0c;我們可以將數據重構為看起來像監督學習問題。 我們可以通過使用以前的時間步作為輸入變量并使用下一個時間步作為輸出變量來做到這一點。 通過觀察重構后的數據集與原本的時間序列&…

sliverlight - Unhandled Error in Silverlight Application錯誤

使用firebug控制臺輸出錯誤&#xff1a; Unhandled Error in Silverlight Application 查詢“GetFlow_Process”的 Load 操作失敗。遠程服務器返回了錯誤: NotFound。 位于 System.ServiceModel.DomainServices.Client.OperationBase.Complete(Exception error) 位于 System.S…

前向驗證對于模型的更新作用

首先&#xff0c;讓我們看一個小的單變量時間序列數據&#xff0c;我們將用作上下文來理解這三種回測方法&#xff1a;太陽黑子數據集。該數據集描述了剛剛超過 230 年&#xff08;1749-1983 年&#xff09;觀察到的太陽黑子數量的每月計數。 數據集顯示了季節之間差異很大的…

2014年9月21日_隨筆,jdic,ETL,groovy,Nutz好多東西想學

&#xff08;1&#xff09;老媽十一要回老家&#xff0c;才突然發現買票好難啊。有親朋很重要 &#xff08;2&#xff09;這周我做了什么。jdic,ETL,groovy, Nutz好多東西想學。 Nutz開發成員專訪、Nutz優酷視頻(演講)、Nutz 入門教程、 &#xff08;3&#xff09;想改變&#…

PHP-面向對象(八)

1、多態的介紹與優勢 多態性是繼抽象和繼承后&#xff0c;面向對象語言的第三個特征。從字面上理解&#xff0c;多態的意思是“多種形態”&#xff0c;簡單來說&#xff0c;多態是具有表現多種形態的能力的特征&#xff0c;在OO中是指“語言具有根據對象的類型以不同方式處理。…

雙指數平滑中參數對于預測模型的影響

先看看α 在β一致的情況下&#xff0c;α越小&#xff0c;模型越滯后。 再看看β 在α一致的情況下&#xff0c;β越大&#xff0c;模型對于趨勢的預測更敏銳。

SQL 性能不佳的幾個原因

SQL 性能不佳的幾個原因 ?不準確的統計數據?差勁的索引?差勁的查詢設計 ?差勁的執行計劃&#xff0c;通常是由不正確的參數引起的?過度阻塞和死鎖 ?非基于集合的操作?不良數據庫設計 ?過度碎片 ?不能重復使用執行計劃 ?查詢頻繁重編譯 ?不當使用游標 ?數據庫日志的…

分頁查詢

分頁查詢算是比較常用的一個查詢了在DAO層主要是查兩個數據第一個總條數第二個要查詢起始記錄數到查詢的條數當第一次點擊查詢時候(非下一頁時Page類里面預設的就是 index就是0 pageSize是預設值當點擊下一頁的時候 index 和 pageSize帶的就是頁面上面給的值了分頁的頁面一般的…