C++11可變參數(shù)模板詳解
一,什么是可變參數(shù)模板
什么是可變?
可變就是可以不同??勺儏?shù),即:參數(shù)類型可變,參數(shù)個(gè)數(shù)可變。
- C++11之前,類模板和函數(shù)模板中只能包含固定數(shù)量的模板參數(shù)。
- C++11新增了可變參數(shù)模板,讓我們可以對(duì)模板的參數(shù)高度泛化,即:可以直接傳一個(gè)參數(shù)包給模板參數(shù),這個(gè)參數(shù)包可以接受任意數(shù)量和任意類型的參數(shù)。
二,基本語法
使用 typename... 或 class... 聲明模板參數(shù)包,...就代表是一個(gè)參數(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個(gè)或多個(gè)參數(shù)
三,可變參數(shù)模板的使用
參數(shù)包作為整體
我們?cè)谑褂脮r(shí),可以往myFunc里傳入不同個(gè)數(shù)和不同類型的參數(shù)。
編譯器在編譯的過程中,根據(jù)myFunc傳入的不同參數(shù),實(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;
}說明:
- 可以理解為,在編譯的時(shí)候,編譯器根據(jù)傳入的不同參數(shù)的
Myfunc實(shí)例化出了三個(gè):Myfunc(int x1)、Myfunc(int x1, int x2)、Myfunc(int x1, int x2, string s1) sizeof...(args):sizeof...是一個(gè)整體運(yùn)算符,用來求函數(shù)參數(shù)包中參數(shù)的個(gè)數(shù),這里的args就是一個(gè)整體,即:一組參數(shù)。
包展開
對(duì)于一個(gè)參數(shù)包,我們可以把他當(dāng)做一個(gè)整體進(jìn)行使用,如上面的sizeof...(args),我們就是直接把args當(dāng)一個(gè)整體進(jìn)行使用。
如果我們想要拿到里面的每一個(gè)參數(shù)就需要用到包展開。直接將參數(shù)包依次展開依次作為實(shí)參給?個(gè)函數(shù)去處理。
【注意,參數(shù)包可不能args[i]這樣下標(biāo)訪問使用。因?yàn)椋簠?shù)包是在編譯時(shí)就確定的一組參數(shù)。在編譯時(shí),編譯器會(huì)根據(jù)具體的模板實(shí)例化來處理參數(shù)包,而不像數(shù)組那樣在運(yùn)行時(shí)存在于內(nèi)存中】
錯(cuò)誤示范1(不能args[i])
假設(shè)你想打印出函數(shù)參數(shù)包里面的所有參數(shù)。
template<class ...Args>
void Print(Args...args)
{
cout << args... << endl;
}
你這樣肯定不行,會(huì)報(bào)錯(cuò):“args”: 未聲明的標(biāo)識(shí)符。因?yàn)閰?shù)列表里面args在編譯時(shí)早就被具體實(shí)例化成了有具體類型的函數(shù)了,如Print(1.1, 2)就被實(shí)例化成:Print(double x1, int x2);
普通一次展開
在使用遞歸展開的時(shí)候,我們先來看一個(gè)簡(jiǎn)單的展開:
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
遞歸展開
寫法三大要點(diǎn):
- 遞歸終止函數(shù)
- 遞歸的包展開函數(shù),參數(shù)列表為:(單個(gè)參數(shù),參數(shù)包)【這樣原來傳入的參數(shù)包,第一個(gè)參數(shù)會(huì)被提出來,第2 - n個(gè)參數(shù)會(huì)被作為新參數(shù)包】。然后在這個(gè)函數(shù)內(nèi),就可以對(duì)這個(gè)提出來的參數(shù)進(jìn)行操作
- 外殼函數(shù),即:在遞歸函數(shù)外面封一層,用于把參數(shù)包傳給遞歸包展開函數(shù)
示例:Print 打印出函數(shù)參數(shù)包里面的所有參數(shù)
// 遞歸終止函數(shù):當(dāng)參數(shù)列表為空時(shí)匹配這個(gè)函數(shù)
void ShowList()
{
cout << endl;
}
// 遞歸包展開函數(shù)
template<class T, class...Args>
void ShowList(T x, Args...args)
{
cout << x << " "; // 打印被展開的單個(gè)參數(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í)候:最少傳入一個(gè)參數(shù)(因?yàn)橛袀€(gè)T x) - 當(dāng)沒有參數(shù)傳入的時(shí)候,就會(huì)匹配到
ShowList()結(jié)束
運(yùn)行結(jié)果:
1 xxxxx 2.2
具體編譯時(shí)實(shí)例化理解圖:

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

