2016年12月13日

[C++] 不定數量的樣本參數 (variadic template)

在很早已前的 C 就有提供不定數量參數 (variadic argument) 的功能了,比方說像 printf/scanf 系列的 function,他們能接受數量不固定的參數。而 C++11 把這個功能也放到 template 來用了,所以現在也有 variadic template 的功能,有些資料的關鍵字是 parameter pack,不過其實內容是一樣的。



variadic template 長得像這樣 (其實跟 variadic argument 滿像的):

template <typename ... Args> void func(Args ... args);

重點就在於 <> 中的 typename,以前寫 template 時這邊必須要是寫死的,現在可以用 ... 來表示不定數量的參數型態。這樣有甚麼好處呢?比方說早期 STL 只有像是 std::pair 能同時允許 2 個 element 擁有不同型態,如果想要 2 個以上的 element 可以是相同或不同的型態就辦不到。而 C++11 後的 STL 多了一個 std::tuple 可以接受不定數量且不定型態的 element,像是 std::tuple<int, char>、std::tuple<std::string, bool, char> 這樣,數量跟型態都是不固定的,這其中就是用到 variadic template 的效果。我們可以看到 std::tuple 宣告其實長得像這樣:
template <class... Types> class tuple;
實際使用上的話,因為這個寫法其實就是把所有參數都綁起來了,所以沒辦法單獨存取,因此實際撰寫上就必須要利用 overloading 的效果讓 compiler 可以把被綁成一坨的參數解開。舉個例子,假設說我們想要寫一個 function print() 可以接受任意數量且任意型態的參數,然後把這些參數都印出來,中間都用 , 區隔開,像下面這樣
print(1, 2.5, 'x', "123");
輸出會是
1 , 2.5 , x , 123
但如果是
print(1);
結果就要是
1
像這樣的需求我們可以先把 print 的特例抓出來:參數數量是 0 或 1 個。在這種情況下 1 個的時候不要有 ,;而沒有給任何參數時當然就是甚麼都不要印,所以我們可以寫成:
void print() {}

template <typename T> void print(const T& arg)
{
    std::cout << arg;
}
當然,當參數數量多於一個時,我們可以寫成
template <typename T, typename ... Args> void print(const T& t, Args&& ... args)
{
    std::cout << t << " , ";
    print(args...);
}
可以特別注意到上面的寫法,第一個參數的型態 T 是有被另外獨立出來的,這樣可以確保 template 在推導參數型態時把第一個參數 t 獨立,而不會跟其他參數綁住,如此一來我們就能直接存取 t。

那剩餘的參數要怎麼印呢? 可以看到 std::cout 下面那行的寫法:
print(args...);
對,就是用遞迴。首先,第一個參數 t 被排除掉了,其他所有的參數就可以寫成 args... 來代表 (... 是正式語法,不要真的省略掉阿 XD),當 args... 實際上只剩 1 個時就會直接呼叫到上面寫得只有一個參數的特化版本去了。

當然,如果有個 function 本身就是能接受不定數量參數的就不用自己寫遞迴了,直接呼叫就好

沒有留言:

張貼留言