0%

Variadic templates

這次介紹另一個C++11重要的特性Variadic templates

從printf說起

相信每個寫程式的人,就算沒用過printf,也聽過printf的名字,printf的徒子徒孫大概跟Unix的子孫一樣多。而一般的printf使用方式就類似如此。

1
2
int my_printf(const char * format, ...);
my_printf("%d + %d = %d", a, b, a + b);

在設計一個通用函數的時候,無法知道後面參數有多少個,因此需要一個支持不定參數的機制。
從上面的程式碼看出,我們支援不定參數的語法就是...來表示。
而在C語言如何實做這樣的機制,可以參考MSDN上的範例

Marco也支援不定參數

在C99標準裡,Macro支持不定參數,不過Visual Studio至今不支援C99。
我們可以寫類似這樣的Macro

1
#define dprintf(enable, ...) dprintf_impl(__FILE__, __LINE__, enable, __VA_ARGS__)

在參數列的最後面寫 …,然後就可以用 VA_ARGS 代表 … 所傳入的參數。

如何讓Template支援不定參數

從C++98談起

在實作Command Design Pattern的時候,常常需要把外部函數的參數原封不動的傳遞至內部函數,解決方法大概就像這樣。

1
2
3
4
5
6
7
8
9
10
11
12
#define P1 typename T1
#define A1 T1 &&v1
#define V1 v1
#define P2 typename T1, typename T2
#define A2 T1 &&v1, T2 &&v2
#define V2 v1, v2
#define P3 typename T1, typename T2, typename T3
#define A3 T1 &&v1, T2 &&v2, T3 &&v3
#define V3 v1, v2, v3
template <P1> void outer(A1) { inner(V1); }
template <P2> void outer(A2) { inner(V2); }
template <P3> void outer(A3) { inner(V3); }

這方案的缺點大概有以下幾點

  • 可代入的參數數量有限 (雖然可以手動擴充)
  • 程式碼難以維護,核心的程式碼不多,但是重複的程式碼很多
  • 編譯速度緩慢
  • 極度依賴Preprocessor
    加上C++11的新特性之後,問題變得更複雜了。

C++11時期

來個最簡單的範例

1
2
3
4
5
6
7
8
9
template <typename... Args>
class VariadicTemplate {};
template <typename T, typename ...Args>
class VariadicTemplate1 {};

VariadicTemplate<> a; // VariadicTemplate1接受無任何型別
VariadicTemplate<int> b;
VariadicTemplate1<> c; // VariadicTemplate1至少需要一個型別,編譯器會報錯
VariadicTemplate1<double, int, string> d;

在Args左邊出現...時,表示Args是一個Template type parameter pack,如上面的最後一行,T就是double,而Args就是int, string,除了類別之外,非類別的template paramter也可以這樣使用。如下

1
2
3
template <unsigned ...dims>
class Array {};
Array<3, 4, 5> arr;

而Function template也可以像Class Template一樣使用不定參數

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

這裡的Args不是Type,args也不是一個value,所以以下的程式碼會出問題

1
2
3
typedef Args MyList;
MyList var;
auto copy = args;

而sizeof也跟著Variadic templates而新增新特性,sizeof...可以印出Args到底有多少個參數

1
2
3
4
5
6
template <typename... Args>
struct VariadicTemplate {
static const unsigned short int size = sizeof...(Args);
};
cout << VariadicTemplate<>::size << endl; // 0
cout << VariadicTemplate<int, int, int>::size << endl; // 3

如何解決之前的問題

用新的特性同時解決perfect forwarding跟Variadic templates

1
2
template <typename ...Ts>
void outer(Ts&& ...args) { inner(std::forward<Ts>(args)...); }

如何抽取單一個型別與參數

透過Template specification來實作

1
2
3
4
5
6
7
8
template <typename T>
void print(T &&v);
template <typename T, typename ...Args>
void print(T &&v, Args ...args)
{
print(std::forward<T>(v));
print(std::forward<Args>(args)...);
}

還有其他未介紹到的特性,基於所知有限<無法完全說明,可以參考Variadic Templates (Revision 3) DraftVariadic Templates are Funadic