C++11 可變引數模板

CCCSR發表於2020-11-24

介紹

一個可變引數模板是一個接受可變數目引數的模板函式或模板類。可變數目的引數成為引數包。存在兩種引數包:模板引數包 ,表示零個或多個模板引數;函式引數包,表示零個或多個函式引數。

用一個省略號來指出一個模板引數或函式參數列示一個包。在模板引數列表中,class…或typename…指出接下來的參數列示零個或多個型別的列表;一個型別名後面跟著的省略號表示零個或多個該給定型別的非型別引數的列表。在函式引數列表中,如果一個引數的型別是一個模板引數包,則此引數也是一個函式引數包。例如:

//Args是一個模板引數包;rest是一個函式引數包
//Args表示零個或多個模板型別引數
//rest表示零個或多個函式引數
template<typename T,typename...Args>
void f(const T &t,const Args&...rest);

上例中的f是一個可變引數函式模板,有一個名為T的型別引數和一個名為Args的模板引數包。這個包表示零個或多個額外的型別引數。f的函式引數列表包含一個const&型別的引數,指向T的型別,還包含一個名為rest的函式引數包,此包表示零個或多個函式引數。

編譯器會從函式的實參推斷模板引數型別。對於一個可變引數模板,編譯器還會推斷包中引數的數目。例如對於下面給定的呼叫:

int i = 0; double d = 3.14; string s = "how are you";
f(i,s,42,d);
f(s,42,"hi");
f(d,s);
f("hi");

編譯器會例項化如下四個版本:
void f(const int&,const string&,const int&,const double&);
void f(const string&,const int&,const char[3]&);
void f(const double&,const string&);
void f(const char[3]&);
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在每個例項中,T的第一個型別都從第一個實參型別推斷出來。剩下的實參提供函式額外實參的數目和型別。

sizeof…運算子

當需要知道包中有多少元素時,可以使用sizeof…運算子。類似sizeof,sizeof…也返回一個常量表示式,而且不會對其實參求值:

template<typename...Args>
void g(Args...args) {
	cout << sizeof...(Args) << endl;//型別引數的數目
	cout << sizeof...(args) << endl;//函式引數的數目
}

編寫可變引數函式模板

可變引數函式通常是遞迴的。第一步呼叫處理包中的第一個實參,然後用剩餘實參呼叫自身。如下的print函式,每次遞迴呼叫將第二個引數列印到第一個實參表示的流中。為了終止遞迴,還需要定義一個非可變引數的print函式,它接受一個流和一個物件:

//用來終止遞迴併列印最後一個元素的函式
//此函式必須在可變引數版本的print定義之前宣告
template<typename T>
void print(ostream& os, const T& t) {
	os << t << endl;
}
//包中除最後一個元素之外的其他元素都會呼叫這個版本的print
template<typename T,typename...Args>
void print(ostream& os, const T& t, const Args&...rest) {
	os << t << ' ';//列印第一個實參
	print(os, rest...);//遞迴呼叫,列印其他實參
}

(在vs2019中,如果在作用域中沒有找到終止遞迴的函式不會導致無限遞迴,而是在編譯時直接報錯)
在這裡插入圖片描述

對於print(cout, i, s,d, 42);第二個函式的遞迴執行如下

呼叫trest
print(cout, i, s,d, 42);is,d,42
print(cout, s,d, 42);sd,42
print(cout, d, 42);d42
print(cout, 42); //呼叫非可變引數版本

相關文章