使用C++11變長引數模板 處理任意長度、型別之引數
變長模板、變長引數是依靠C++11新引入的引數包的機制實現的。
一個簡單的例子是std::tuple的宣告:
template <typename... Elements>
class tuple;
這裡的三個點“...”表示這個模板引數是變長的。
有了這個強大的工具,我們可以編寫更加豐富的函式,例如任意型別引數的printf等。由於這個技術還比較新,還沒有見到成熟的用法用例,我把我嘗試的一些結果總結如下,希望對大家有幫助。
1,引數包
考慮到這個知識點很多朋友都不熟悉,首先明確幾個概念:
1,模板引數包(template parameter pack):
它指模板引數位置上的變長引數(可以是型別引數,也可以是非型別引數),例如上面例子中的 Elements。
2,函式引數包(function parameter pack):
它指函式引數位置上的變長引數,例如下面例子中的args,(ARGS是模板引數包):
template <typename ... ARGS>
void fun(ARGS ... args)
在很多情況下它們是密切相關的(例如上面的例子),而且很多概念和用法也都一致,在不引起誤解的情況下,後面我在討論時會將他們合併起來討論,或只討論其中一個(另一個於此相同)。
注意:模板引數包本身在模板推導過程中被認為是 一個 特殊的型別(函式引數包被認為是 一個 特殊型別的引數)。
一個包可以打包任意多數量的引數(包含0個)。
一般情況下 引數包必須在最後面,例如:
template <typename T, typename ... Args>
void fun(T t,Args ... args);//合法
template <typename ... Args, typename T>
void fun(Args ... args,T t);//非法
有一個新的運算子:sizeof...(T) 可以用來獲知引數包中打包了幾個引數,注意 不是 引數所佔的位元組數之和。 2,解包
在實際使用時,拿到一個複合而成的包對沒有並沒有什麼用,我們通常需要獲得它裡面內一個元素的內容。解包是把引數包展開為它所表示的具體內容的動作。
解包時採用“包擴充套件表示式”,就是包名加上三個點,如“Args...”。
例如:
假設我們有一個模板類Base:
template <typename ... Args>
class D1 : public Base<A...>{};
或
template <typename ... Args>
class D2 : public Base<A>...{};
解包用兩種常見的形式:
1,直接解包(上面第一個)
D1<X,Y,Z> 相當於 D1:public Base<X,Y,Z>
2,先參與其他表示式再解包(上面第二個)
D2<X,Y,Z> 相當於 D2: public Base<X>, Base<Y>, Base<Z>
直觀上理解就是在...所在的位置將包含了引數包的表示式展開為若干個具體形式。
3,函式例項
一個常用的技巧是:利用模板推導機制,每次從引數包裡面取第一個元素,縮短引數包,直到包為空。
template <typename T>
void fun(const T& t){
cout << t << '\n';
}
template <typename T, typename ... Args>
void fun(const T& t, Args ... args){
cout << t << ',';
fun(args...);//遞迴解決,利用模板推導機制,每次取出第一個,縮短引數包的大小。
}
下面我以打印出一組引數為例,簡單介紹一下變成引數函式怎麼用。
方法一:
template <typename ... T>
void DummyWrapper(T... t){};
template <class T>
T unpacker(const T& t){
cout<<','<<t;
return t;
}
template <typename T, typename... Args>
void write_line(const T& t, const Args& ... data){
cout << t;
DummyWrapper(unpacker(data)...);//直接用unpacker(data)...是非法的,(可以認為直接逗號並列一堆結果沒有意義),需要包裹一下,好像這些結果還有用
cout << '\n';
}
推薦這種方法,雖然寫起來麻煩一點,但是它在執行期的效率比較高(沒有遞迴,順序搞定,DummyWrapper的引數傳遞會被編譯器優化掉),而且編譯期的代價也不是很高(對於相同型別的子元素,unpacker<T>只需要特化出一份即可,但DummyWrapper需要根據引數型別特化很多版本)。
template <typename T>
void _write(const T& t){
cout << t << '\n';
}
template <typename T, typename ... Args>
void _write(const T& t, Args ... args){
cout << t << ',';
_write(args...);//遞迴解決,利用模板推導機制,每次取出第一個,縮短引數包的大小。
}
template <typename T, typename... Args>
inline void write_line(const T& t, const Args& ... data){
_write(t, data...);
}
這種方法思路直觀,書寫便捷。但是執行時有遞迴,效率有所下降。編譯時也需要生成不少版本的_write。