C++11新增的最強大的特性之一,泛化之美的可變模板參數

前言

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

C++11的新特性--可變模板參數(variadic templates)是C++11新增的最強大的特性之一,它對參數進行了高度泛化,它能表示0到任意個數、任意類型的參數。相比C++98/03,類模版和函數模板中只能含固定數量的模版參數,可變模板參數無疑是一個巨大的改進。然而由於可變模板參數比較抽象,使用起來需要一定的技巧,所以它也是C++11中最難理解和掌握的特性之一。雖然掌握可變模板參數有一定難度,但是它卻是C++11中最有意思的一個特性,本文希望帶領讀者由淺入深的認識和掌握這一特性,同時也會通過一些實例來展示可變參數模版的一些用法。

可變模板參數的展開

可變參數模板和普通模板的語義是一樣的,只是寫法上稍有區別,聲明可變參數模板時需要在typename或class後面帶上省略號“...”。比如我們常常這樣聲明一個可變模版參數:template或者template,一個典型的可變模板參數的定義是這樣的:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

上面的可變模板參數的定義當中,省略號的作用有兩個:

  • 聲明一個參數包T... args,這個參數包中可以包含0到任意個模板參數;
  • 在模板定義的右邊,可以將參數包展開成一個一個獨立的參數。

上面的參數args前面有省略號,所以它就是一個可變模版參數,我們把帶省略號的參數稱為“參數包”,它裡面包含了0到N(N>=0)個模版參數。我們無法直接獲取參數包args中的每個參數的,只能通過展開參數包的方式來獲取參數包中的每個參數,這是使用可變模版參數的一個主要特點,也是最大的難點,即如何展開可變模版參數。

可變模版參數和普通的模版模板語義是一致的,所以可以應用於函數和類,即可變模板參數函數和可變模板參數類,然而,模版函數不支持偏特化,所以可變模板參數函數和可變模板參數類展開可變模版參數的方法還不盡相同,下面我們來分別看看他們展開可變模版參數的方法。

可變模板參數函數

一個簡單的可變模板參數函數:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

面的例子中,f()沒有傳入參數,所以參數包為空,輸出的size為0,後面兩次調用分別傳入兩個和三個參數,故輸出的size分別為2和3。由於可變模版參數的類型和個數是不固定的,所以我們可以傳任意類型和個數的參數給函數f。這個例子只是簡單的將可變模版參數的個數打印出來,如果我們需要將參數包中的每個參數打印出來的話就需要通過一些方法了。展開可變模版參數函數的方法一般有兩種:一種是通過遞歸函數來展開參數包,另外一種是通過逗號表達式來展開參數包。下面來看看如何用這兩種方法來展開參數包。

遞歸函數方式展開參數包

通過遞歸函數展開參數包,需要提供一個參數包展開的函數和一個遞歸終止函數,遞歸終止函數正是用來終止遞歸的,來看看下面的例子。

C++11新增的最強大的特性之一,泛化之美的可變模板參數

上例會輸出每一個參數,直到為空時輸出empty。展開參數包的函數有兩個,一個是遞歸函數,另外一個是遞歸終止函數,參數包Args...在展開的過程中遞歸調用自己,每調用一次參數包中的參數就會少一個,直到所有的參數都展開為止,當沒有參數時,則調用非模板函數print終止遞歸過程。遞歸調用的過程是這樣的:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

上面的遞歸終止函數還可以寫成這樣:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

修改遞歸終止函數後,上例中的調用過程是這樣的:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

當參數包展開到最後一個參數時遞歸為止。再看一個通過可變模版參數求和的例子:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

sum在展開參數包的過程中將各個參數相加求和,參數的展開方式和前面的打印參數包的方式是一樣的。

逗號表達式展開參數包

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

遞歸函數展開參數包是一種標準做法,也比較好理解,但也有一個缺點,就是必須要一個重載的遞歸終止函數,即必須要有一個同名的終止函數來終止遞歸,這樣可能會感覺稍有不便。有沒有一種更簡單的方式呢?其實還有一種方法可以不通過遞歸方式來展開參數包,這種方式需要藉助逗號表達式和初始化列表。比如前面print的例子可以改成這樣:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

這個例子將分別打印出1,2,3,4四個數字。這種展開參數包的方式,不需要通過遞歸終止函數,是直接在expand函數體中展開的, printarg不是一個遞歸終止函數,只是一個處理參數包中每一個參數的函數。這種就地展開參數包的方式實現的關鍵是逗號表達式。我們知道逗號表達式會按順序執行逗號前面的表達式,比如:d = (a = b, c);

這個表達式會按順序執行:b會先賦值給a,接著括號中的逗號表達式返回c的值,因此d將等於c。expand函數中的逗號表達式:(printarg(args), 0),也是按照這個執行順序,先執行printarg(args),再得到逗號表達式的結果0。同時還用到了C++11的另外一個特性——初始化列表,通過初始化列表來初始化一個變長數組, {(printarg(args), 0)...}將會展開成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... ),最終會創建一個元素值都為0的數組int arr[sizeof...(Args)]。由於是逗號表達式,在創建數組的過程中會先執行逗號表達式前面的部分printarg(args)打印出參數,也就是說在構造int數組的過程中就將參數包展開了,這個數組的目的純粹是為了在數組構造的過程展開參數包。我們可以把上面的例子再進一步改進一下,將函數作為參數,就可以支持lambda表達式了,從而可以少寫一個遞歸終止函數了,具體代碼如下:

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

上面的例子將打印出每個參數,這裡如果再使用C++14的新特性泛型lambda表達式的話,可以寫更泛化的lambda表達式了:expand([](auto i){cout<

結束語

C++11新增的最強大的特性之一,泛化之美的可變模板參數

更多C/C++學習資料,請私信我“代碼”,即可獲取

今天的內容就到這裡了哦,後期更新C++11中的可變參數模板類呦,使用可變模板參數的關鍵是如何展開參數包,展開參數包的過程是很精妙的,體現了泛化之美、遞歸之美,正是因為它具有神奇的“魔力”,所以我們可以更泛化的去處理問題。

更多精彩


分享到:


相關文章: