1. 程式人生 > >使用C++11變長引數模板 處理任意長度、型別之引數

使用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。