<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
可變引數模板(variadic templates)是C++11新增的強大的特性之一,它對模板引數進行了高度泛化,能表示0到任意個數、任意型別的引數。相比C++98/03這些類模版和函數模版中只能含固定數量模版引數的“老古董”,可變模版引數無疑是一個巨大的進步。
如果是剛接觸可變引數模板可能會覺得比較抽象,使用起來會不太順手,使用可變引數模板時通常離不開模板引數的展開,所以本文來列舉一些常用的模板展開方式,幫助我們來對可變引數模板有一個初步的瞭解。
可變引數模板和普通模板的定義類似,在寫法上需要在 typename
或 class
後面帶上省略號...
,以下為一個常見的可變引數函數模板:
template <class... T> void func(T... args) { //... }
上面這個函數模板的引數 args
前面有省略號,所以它就是一個被稱為模板引數包(template parameter pack)的可變模版引數,它裡面包含了0到N個模版引數,而我們是無法直接獲取 args
中的每個引數的,只能通過展開引數包的方式來獲取引數包中的每個引數,這也是本文要重點總結的內容。
引數包展開的方式隨著c++語言的發展也在與時俱進,我們以實現一個可變參格式化列印函數為例,列舉一些常用的方式:
#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; }
這種遞迴展開的方式與遞迴函數的定義是一樣的,需要遞迴出口和不斷呼叫自身,仔細看看這個函數模板是不是都滿足啦?遞迴出口就是這個無模板引數的 FormatPrint
,並且在有參模板中一直在呼叫自身,遞迴呼叫的過程時這樣的 FormatPrint(4,3,2,1)
-> FormatPrint(3,2,1)
-> FormatPrint(2,1)
-> FormatPrint(1)
-> FormatPrint()
,輸出內容如下:
>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)以及很傳統的逗號表示式,我們知道逗號表示式的優先順序最低,(a, b)
這個表示式的值就是 b
,那麼上述程式碼中(std::cout << "[" << args << "]", 0)
這個表示式的值就是0,初始化列表保證其中的內容從左往右執行,args引數包會被逐步展開,表示式前的(void)
是為了防止變數未使用的警告,執行過後我們就得到了一個N個元素為0的初始化列表,內容也被格式化輸出了:
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)
得到引數包中引數個數。
#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
常用於構建需要根據不同的型別的條件範例化不同模板的時候。顧名思義,當滿足條件時型別有效。可作為選擇型別的小工具,其廣泛的應用在 C++ 的模板超程式設計(meta programming)之中,利用的就是SFINAE原則,英文全稱為Substitution failure is not an error,意思就是匹配失敗不是錯誤,假如有一個特化會導致編譯時錯誤,只要還有別的選擇,那麼就無視這個特化錯誤而去選擇另外的實現,這裡的特化概念不再展開,感興趣可以自行了解,後續可以單獨總結一下。
在上面的程式碼實現中,基本思路是先將可變模版引數轉換為std::tuple
,然後通過遞增引數的索引來選擇恰當的FormatTuple
函數,當引數的索引小於tuple元素個數時,會不斷取出當前索引位置的引數並輸出,當引數索引等於總的引數個數時呼叫另一個模板過載函數終止遞迴,編譯執行輸入以下內容:
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<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中引入的引數包的處理,可以在某些情況下避免使用遞迴,更加方便的展開引數,如上述程式碼中展示的這樣可以方便的展開引數包,不過輸出的內容和之前的有些不一樣:
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
對比結果發現缺少了格式化的資訊,需要以輔助函數的方式來格式化:
#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; }
這次格式化內容就被加進來了:
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]
這樣好像還是有點麻煩,我們可以把摺疊表示式和逗號表示式組合使用,這樣得到的程式碼就簡單多啦,也能完成格式化輸出的任務:
#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; }
Variadic templates
是C++11新增的強大的特性之一,它對模板引數進行了高度泛化Initializer lists
是C++11新加的特性,可以作為函數引數和返回值,長度不受限制比較方便Fold Expressions
是C++17新引進的語法特性,可以方便的展開可變引數模板的引數包可變引數模板的引數包在C++11的環境下,可以利用遞迴、逗號表示式、enable_if等方式進行展開
==>> 反爬連結,請勿點選,原地爆炸,概不負責!<<==
到此這篇關於C++可變引數模板的展開方式的文章就介紹到這了,更多相關C++模板展開方式內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45