C++11可變參數(shù)模板詳解
一,什么是可變參數(shù)模板
什么是可變?
可變就是可以不同。可變參數(shù),即:參數(shù)類型可變,參數(shù)個數(shù)可變。
- C++11之前,類模板和函數(shù)模板中只能包含固定數(shù)量的模板參數(shù)。
- C++11新增了可變參數(shù)模板,讓我們可以對模板的參數(shù)高度泛化,即:可以直接傳一個參數(shù)包給模板參數(shù),這個參數(shù)包可以接受任意數(shù)量和任意類型的參數(shù)。
二,基本語法
使用 typename... 或 class... 聲明模板參數(shù)包,...就代表是一個參數(shù)包:
template<typename T, typename... Args>
void myFunc(T first, Args... rest) { /*...*/ } // 使用模板的參數(shù)包
Args是模板的類型參數(shù)包,表示接收一組類型Args... rest是函數(shù)參數(shù)包,表示使用Args類型,接收一組參數(shù)(后面要跟...)。它將模板參數(shù)包展開為一組函數(shù)參數(shù)Args只是參數(shù)包的名字,可以取其他名字- 參數(shù)包代表可接收
0個或多個參數(shù)
三,可變參數(shù)模板的使用
參數(shù)包作為整體
我們在使用時,可以往myFunc里傳入不同個數(shù)和不同類型的參數(shù)。
編譯器在編譯的過程中,根據(jù)myFunc傳入的不同參數(shù),實例化出帶有不同參數(shù)的myFunc函數(shù)
示例:
template<class ...Args>
void Myfunc(Args...args)
{
cout << sizeof...(args) << endl;
}
int main()
{
int x1 = 1;
double x2 = 2.2;
std::string s1 = "hello world";
Myfunc(x1); // 輸出:1
Myfunc(x1, x2); // 輸出:2
Myfunc(x1, x2, s1); // 輸出:3
return 0;
}說明:
- 可以理解為,在編譯的時候,編譯器根據(jù)傳入的不同參數(shù)的
Myfunc實例化出了三個:Myfunc(int x1)、Myfunc(int x1, int x2)、Myfunc(int x1, int x2, string s1) sizeof...(args):sizeof...是一個整體運算符,用來求函數(shù)參數(shù)包中參數(shù)的個數(shù),這里的args就是一個整體,即:一組參數(shù)。
包展開
對于一個參數(shù)包,我們可以把他當(dāng)做一個整體進行使用,如上面的sizeof...(args),我們就是直接把args當(dāng)一個整體進行使用。
如果我們想要拿到里面的每一個參數(shù)就需要用到包展開。直接將參數(shù)包依次展開依次作為實參給?個函數(shù)去處理。
【注意,參數(shù)包可不能args[i]這樣下標(biāo)訪問使用。因為:參數(shù)包是在編譯時就確定的一組參數(shù)。在編譯時,編譯器會根據(jù)具體的模板實例化來處理參數(shù)包,而不像數(shù)組那樣在運行時存在于內(nèi)存中】
錯誤示范1(不能args[i])
假設(shè)你想打印出函數(shù)參數(shù)包里面的所有參數(shù)。
template<class ...Args>
void Print(Args...args)
{
cout << args... << endl;
}
你這樣肯定不行,會報錯:“args”: 未聲明的標(biāo)識符。因為參數(shù)列表里面args在編譯時早就被具體實例化成了有具體類型的函數(shù)了,如Print(1.1, 2)就被實例化成:Print(double x1, int x2);
普通一次展開
在使用遞歸展開的時候,我們先來看一個簡單的展開:
template<class T1, class T2, class...Args>
void ShowList(T1 x1, T2 x2, Args...args)
{
cout << x1 << endl;
cout << x2 << endl;
cout << sizeof...(args) << endl;
}
int main()
{
ShowList(1, 2.2, 3.3, "hello world");
return 0;
}輸出結(jié)果:

在這里,我們傳入一組參數(shù),其中1、2.2分別給了x1和x2,后面的3.3和"hello world"被打包給了args
遞歸展開
寫法三大要點:
- 遞歸終止函數(shù)
- 遞歸的包展開函數(shù),參數(shù)列表為:(單個參數(shù),參數(shù)包)【這樣原來傳入的參數(shù)包,第一個參數(shù)會被提出來,第2 - n個參數(shù)會被作為新參數(shù)包】。然后在這個函數(shù)內(nèi),就可以對這個提出來的參數(shù)進行操作
- 外殼函數(shù),即:在遞歸函數(shù)外面封一層,用于把參數(shù)包傳給遞歸包展開函數(shù)
示例:Print 打印出函數(shù)參數(shù)包里面的所有參數(shù)
// 遞歸終止函數(shù):當(dāng)參數(shù)列表為空時匹配這個函數(shù)
void ShowList()
{
cout << endl;
}
// 遞歸包展開函數(shù)
template<class T, class...Args>
void ShowList(T x, Args...args)
{
cout << x << " "; // 打印被展開的單個參數(shù)
ShowList(args...);
}
template<class...Args>
void Print(Args... args)
{
ShowList(args...);
}
int main()
{
Print(1, string("xxxxx"), 2.2);
return 0;
}args...:...是包展開操作符- 在調(diào)用到
ShowList的時候:最少傳入一個參數(shù)(因為有個T x) - 當(dāng)沒有參數(shù)傳入的時候,就會匹配到
ShowList()結(jié)束
運行結(jié)果:
1 xxxxx 2.2
具體編譯時實例化理解圖:

當(dāng)然也可以編寫有參的結(jié)束函數(shù):
//遞歸終止函數(shù)
template<class T>
void ShowList(T x)
{
cout << x << endl;
}
當(dāng)ShowList參數(shù)個數(shù)為1的時候編譯器會找最匹配的,也就是調(diào)到這個ShowList(T x),然后執(zhí)行完以后,ShowList(T x)里面沒有再調(diào)用ShowList于是完成終止
錯誤示范2(if運行時判斷)
那在遞歸函數(shù)里面,利用sizeof...(args) == 0來終止可以嗎?
答案是不可以!?。?/p>
template<class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " "; //打印傳入的若干參數(shù)中的第一個參數(shù)
if (sizeof...(args) == 0)
{
return;
}
ShowList(args...); //將剩下參數(shù)繼續(xù)向下傳
}
int main()
{
ShowList(1, 2.2, 3.3, "hello world");
return 0;
}報錯:“ShowList”: 未找到匹配的重載函數(shù)
為什么 if (sizeof...(args) == 0) 無法終止遞歸(以下內(nèi)容由AI生成):
1. sizeof...(args) 是編譯期常量,但 if 是運行時判斷
sizeof...(args)的值在編譯時就已經(jīng)確定(比如args有 3 個參數(shù)時,sizeof...(args)就是3)。- 但
if (sizeof...(args) == 0)這個if語句本身是運行時執(zhí)行的,編譯器仍然會 生成所有分支的代碼,包括ShowList(args...)的調(diào)用。
2. 編譯器必須實例化 ShowList(args...)
即使 if 條件在運行時是 false,編譯器仍然要 確保 ShowList(args...) 的調(diào)用是合法的,因為:
- C++ 的模板實例化規(guī)則要求 所有可能調(diào)用的模板函數(shù)都必須有定義。
- 即使
if條件保證ShowList(args...)不會執(zhí)行,編譯器仍然會 嘗試實例化它。
3. 當(dāng) args... 為空時,ShowList() 無匹配版本
- 當(dāng)遞歸到最后一步,
args...為空時,ShowList(args...)就變成了ShowList()。 - 但代碼 沒有定義
ShowList()這個無參數(shù)版本,我們的ShowList(T value, Args... args),args可以為空,但是value必須給。所以編譯器報錯:error: no matching function for call to 'ShowList()'
解決方法:
方法1 : 像之前介紹的一樣,額外寫第一個終止函數(shù)。
if constexpr 編譯時判斷解決問題
方法 2:用 if constexpr(C++17 編譯時判斷)【已驗證有效】
template<class T, class... Args>
void ShowList(T value, Args... args) {
cout << value << " ";
if constexpr (sizeof...(args) > 0) { // 編譯時判斷
ShowList(args...); // 僅在 args 非空時實例化
}
// 否則自動終止
}
if constexpr是編譯期判斷,不會生成無效的ShowList()調(diào)用。
非遞歸展開
示例:
template <class T>
const T& GetArg(const T& x) {
cout << x << " "; // 打印參數(shù)
return x; // 返回原參數(shù)(保證類型不變)
}
template <class ...Args>
void Arguments(Args... args) {} // 空函數(shù),僅用于接收展開后的參數(shù)包
template <class ...Args>
void Print(Args... args) {
Arguments(GetArg(args)...); // 關(guān)鍵:展開參數(shù)包并調(diào)用 GetArg 處理每個參數(shù)
}
int main()
{
Print(1, 2.2, 3.3, "hello world");
return 0;
}GetArg參數(shù)包展開及使用函數(shù)GetArg(args)...:...讓編譯器遍歷args的每一個參數(shù),并對 每一個參數(shù) 調(diào)用GetArg,相當(dāng)于:Arguments(GetArg(arg1), GetArg(arg2), ..., GetArg(argN));GetArg必須要有返回值,用于讓Arguments接收到參數(shù)
Arguments空函數(shù)- 僅用于 接收展開后的參數(shù)包,確保
GetArg(args)...能正確展開。 - 在 C++ 中,參數(shù)包展開
GetArg(args)...必須發(fā)生在合法的上下文環(huán)境中,Arguments(GetArg(args)...)提供的就是這樣一個上下文?!究梢岳斫鉃椋托枰粋€能接受參數(shù)包的容器來接收GetArg(args)...的返回值】
- 僅用于 接收展開后的參數(shù)包,確保
到此這篇關(guān)于C++11可變參數(shù)模板的文章就介紹到這了,更多相關(guān)C++11可變參數(shù)模板內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++實現(xiàn)一個簡易的網(wǎng)絡(luò)緩沖區(qū)的實踐
這篇文章主要介紹了c++實現(xiàn)一個簡易的網(wǎng)絡(luò)緩沖區(qū)的實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12

