淺析C++可變參數(shù)模板的展開方式
前言
可變參數(shù)模板(variadic templates)是C++11新增的強大的特性之一,它對模板參數(shù)進行了高度泛化,能表示0到任意個數(shù)、任意類型的參數(shù)。相比C++98/03這些類模版和函數(shù)模版中只能含固定數(shù)量模版參數(shù)的“老古董”,可變模版參數(shù)無疑是一個巨大的進步。
如果是剛接觸可變參數(shù)模板可能會覺得比較抽象,使用起來會不太順手,使用可變參數(shù)模板時通常離不開模板參數(shù)的展開,所以本文來列舉一些常用的模板展開方式,幫助我們來對可變參數(shù)模板有一個初步的了解。
可變參數(shù)模板的定義
可變參數(shù)模板和普通模板的定義類似,在寫法上需要在 typename
或 class
后面帶上省略號...
,以下為一個常見的可變參數(shù)函數(shù)模板:
template <class... T> void func(T... args) { //... }
上面這個函數(shù)模板的參數(shù) args
前面有省略號,所以它就是一個被稱為模板參數(shù)包(template parameter pack)的可變模版參數(shù),它里面包含了0到N個模版參數(shù),而我們是無法直接獲取 args
中的每個參數(shù)的,只能通過展開參數(shù)包的方式來獲取參數(shù)包中的每個參數(shù),這也是本文要重點總結(jié)的內(nèi)容。
參數(shù)包的展開
參數(shù)包展開的方式隨著c++語言的發(fā)展也在與時俱進,我們以實現(xiàn)一個可變參格式化打印函數(shù)為例,列舉一些常用的方式:
遞歸函數(shù)方式展開
#include <iostream> void FormatPrint() { std::cout << std::endl; } template <class T, class ...Args> void FormatPrint(T first, Args... args) { std::cout << "[" << first << "]"; FormatPrint(args...); } int main(void) { FormatPrint(1, 2, 3, 4); FormatPrint("good", 2, "hello", 4, 110); return 0; }
這種遞歸展開的方式與遞歸函數(shù)的定義是一樣的,需要遞歸出口和不斷調(diào)用自身,仔細看看這個函數(shù)模板是不是都滿足啦?遞歸出口就是這個無模板參數(shù)的 FormatPrint
,并且在有參模板中一直在調(diào)用自身,遞歸調(diào)用的過程時這樣的 FormatPrint(4,3,2,1)
-> FormatPrint(3,2,1)
-> FormatPrint(2,1)
-> FormatPrint(1)
-> FormatPrint()
,輸出內(nèi)容如下:
>albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]
逗號表達式展開
#include <iostream> template <class ...Args> void FormatPrint(Args... args) { (void)std::initializer_list<int>{ (std::cout << "[" << args << "]", 0)... }; std::cout << std::endl; } int main(void) { FormatPrint(1, 2, 3, 4); FormatPrint("good", 2, "hello", 4, 110); return 0; }
這種方式用到了C++11的新特性初始化列表(Initializer lists)以及很傳統(tǒng)的逗號表達式,我們知道逗號表達式的優(yōu)先級最低,(a, b)
這個表達式的值就是 b
,那么上述代碼中(std::cout << "[" << args << "]", 0)
這個表達式的值就是0,初始化列表保證其中的內(nèi)容從左往右執(zhí)行,args參數(shù)包會被逐步展開,表達式前的(void)
是為了防止變量未使用的警告,運行過后我們就得到了一個N個元素為0的初始化列表,內(nèi)容也被格式化輸出了:
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]
說到這順便提一下,可以使用sizeof...(args)
得到參數(shù)包中參數(shù)個數(shù)。
enable_if方式展開
#include <iostream> #include <tuple> #include <type_traits> template<std::size_t k = 0, typename tup> typename std::enable_if<k == std::tuple_size<tup>::value>::type FormatTuple(const tup& t) { std::cout << std::endl; } template<std::size_t k = 0, typename tup> typename std::enable_if<k < std::tuple_size<tup>::value>::type FormatTuple(const tup& t){ std::cout << "[" << std::get<k>(t) << "]"; FormatTuple<k + 1>(t); } template<typename... Args> void FormatPrint(Args... args) { FormatTuple(std::make_tuple(args...)); } int main(void) { FormatPrint(1, 2, 3, 4); FormatPrint("good", 2, "hello", 4, 110); return 0; }
C++11的enable_if
常用于構(gòu)建需要根據(jù)不同的類型的條件實例化不同模板的時候。顧名思義,當(dāng)滿足條件時類型有效??勺鳛檫x擇類型的小工具,其廣泛的應(yīng)用在 C++ 的模板元編程(meta programming)之中,利用的就是SFINAE原則,英文全稱為Substitution failure is not an error,意思就是匹配失敗不是錯誤,假如有一個特化會導(dǎo)致編譯時錯誤,只要還有別的選擇,那么就無視這個特化錯誤而去選擇另外的實現(xiàn),這里的特化概念不再展開,感興趣可以自行了解,后續(xù)可以單獨總結(jié)一下。
在上面的代碼實現(xiàn)中,基本思路是先將可變模版參數(shù)轉(zhuǎn)換為std::tuple
,然后通過遞增參數(shù)的索引來選擇恰當(dāng)?shù)?code>FormatTuple函數(shù),當(dāng)參數(shù)的索引小于tuple元素個數(shù)時,會不斷取出當(dāng)前索引位置的參數(shù)并輸出,當(dāng)參數(shù)索引等于總的參數(shù)個數(shù)時調(diào)用另一個模板重載函數(shù)終止遞歸,編譯運行輸入以下內(nèi)容:
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]
折疊表達式展開(c++17)
#include <iostream> template<typename... Args> void FormatPrint(Args... args) { (std::cout << ... << args) << std::endl; } int main(void) { FormatPrint(1, 2, 3, 4); FormatPrint("good", 2, "hello", 4, 110); return 0; }
折疊表達式(Fold Expressions)是C++17新引進的語法特性,使用折疊表達式可以簡化對C++11中引入的參數(shù)包的處理,可以在某些情況下避免使用遞歸,更加方便的展開參數(shù),如上述代碼中展示的這樣可以方便的展開參數(shù)包,不過輸出的內(nèi)容和之前的有些不一樣:
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++17
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
1234
good2hello4110
對比結(jié)果發(fā)現(xiàn)缺少了格式化的信息,需要以輔助函數(shù)的方式來格式化:
#include <iostream> template<typename T> string format(T t) { std::stringstream ss; ss << "[" << t << "]"; return ss.str(); } template<typename... Args> void FormatPrint(Args... args) { (std::cout << ... << format(args)) << std::endl; } int main(void) { FormatPrint(1, 2, 3, 4); FormatPrint("good", 2, "hello", 4, 110); return 0; }
這次格式化內(nèi)容就被加進來了:
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testtemplate.cpp --std=c++17
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
[1][2][3][4]
[good][2][hello][4][110]
這樣好像還是有點麻煩,我們可以把折疊表達式和逗號表達式組合使用,這樣得到的代碼就簡單多啦,也能完成格式化輸出的任務(wù):
#include <iostream> template<typename... Args> void FormatPrint(Args... args) { (std::cout << ... << (std::cout << "[" << args, "]")) << std::endl; } int main(void) { FormatPrint(1, 2, 3, 4); FormatPrint("good", 2, "hello", 4, 110); return 0; }
總結(jié)
Variadic templates
是C++11新增的強大的特性之一,它對模板參數(shù)進行了高度泛化Initializer lists
是C++11新加的特性,可以作為函數(shù)參數(shù)和返回值,長度不受限制比較方便Fold Expressions
是C++17新引進的語法特性,可以方便的展開可變參數(shù)模板的參數(shù)包可變參數(shù)模板的參數(shù)包在C++11的環(huán)境下,可以利用遞歸、逗號表達式、enable_if等方式進行展開
==>> 反爬鏈接,請勿點擊,原地爆炸,概不負(fù)責(zé)!<<==
到此這篇關(guān)于C++可變參數(shù)模板的展開方式的文章就介紹到這了,更多相關(guān)C++模板展開方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C/C++中memset,memcpy的使用及fill對數(shù)組的操作
這篇文章主要介紹了C/C++中memset,memcpy的使用及fill對數(shù)組的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12淺談C++日志系統(tǒng)log4cxx的使用小結(jié)詳解
本篇文章是對C++日志系統(tǒng)log4cxx的使用進行了詳細的分析介紹,需要的朋友參考下2013-05-05