可变参数模板

可變參數模板是模板編程時，模板參數（template parameter）的個數可變的情形。
已經支持可變參數模板的程式語言有D語言與C++（自C++11標準）。

聲明

C++11之前，模板（類模板與函數模板）在聲明時必須有 固定數量的模板參數。C++11允許模板定義有任意類型任意數量的模板參數。
例如，STL的類模板tuple可以有任意個數的類型名（typename）作為它的模板形參（template parameter）：
template<typename... Values> class tuple;
如實例化為具有3個類型實參（type argument）：  
tuple<int, std::vector<int>, std::map<<std::string>, std::vector<int>>> some_instance_name;
也可以有0個實參，如 tuple<> some_instance_name;也是可以的。
如果不希望可變參數模板有0個模板實參，可以如下聲明：
template<typename First, typename... Rest> class tuple;
可變參數模板也適用於函數模板，這不僅給可變參數函數（variadic functions，如printf）提供了類型安全的附加機制（add-on），還允許類似printf的函數處理不平凡對象。例如：
template<typename... Params> void printf(const std::string &str_format, Params... parameters);

使用方法

可變參數模板可遞歸使用。可變模板參數自身並不可直接用於函數或類的實現。例如，printf的C++11可變參數的替換版本實現：
void printf(const char *s) //已经没有额外的参数了，这里将要耗尽字符串s{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        std::cout << *s++;
    }}template<typename T, typename... Args>void printf(const char *s, T value, Args... args) //处理一对： （格式指示符，值参数）{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::cout << value;

                printf(s + 1, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        std::cout << *s++;
    }
    throw std::logic_error("extra arguments provided to printf");}
這是一個遞歸實現的模板函數。注意這個可變參數模板實現的printf調用自身或者在args...為空時調用基本實現版本。
沒有簡單機制去在可變模板參數的每個單獨值上疊代。幾乎沒有什麼方式可以把參數包轉為單獨實參來使用。通常這靠函數重載，或者當函數可以每次撿出一個實參時用啞擴展標記（dumb expansion marker）：
#include <iostream> template<typename type>type print(type param){
    std::cout<<param<<' ';
    return param;}template<typename... Args> inline void pass(Args&&...) {}template<typename... Args> inline void expand(Args&&... args) {
    pass( print(args)... );}int main(){
      expand(42, "answer", true);}
上例中的"pass"函數是必須的，因為參數包用逗號展開後只能作為被逗號分隔開的一組函數調用實參，而不是作為逗號運算符，從而"pass"函數所能接受的調用實參個數必須是可變的，也即"pass"函數必須是可變參數函數。print(args)...;編譯不能通過。 此外，上述辦法要求print的返回類型不能是void；且所有對print的調用在一個非確定的順序，因為函數實參求值的順序是不確定的。如果要避免這種不確定的順序，可以用大括號封閉的初始化器列表（initializer list），這保證了嚴格的從左到右的求值順序。為避免void返回類型帶來的麻煩，使用逗號運算符使得每個擴展元素總是返回1。例如： 
#include <iostream>template<typename T> void some_function(T value){
    std::cout<<value<<' ';}template<typename... Args> inline void expand(Args&&... args) {
    int arr[]{(some_function(args),1 )...};
    std::cout<<std::endl<<sizeof(arr)/sizeof(int); //也可以用sizeof...(Args)运算符}int main(){
      expand(42, "answer", true);}
另一種方法使用重載函數的遞歸的終結版（"termination versions"）函數。這更為通用，但要求更多努力寫更多代碼。一個函數要求某種類型的實參與參數包。另一個函數沒有參數。如下例：
int func() {} // termination versiontemplate<typename Arg1, typename... Args>int func(const Arg1& arg1, const Args&... args){
    process( arg1 );
    func(args...); // note: arg1 does not appear here!}
如果args...包含至少一個實參，則將調用第二個版本的函數；如果參數包為空將調用第一個「終結」版的函數。
可變參數模板可用於異常規範（exception specification）、基類列表（base class list）、構造函數初始化列表（constructor's initialization list）。例如：
template <typename... BaseClasses> class ClassName : public BaseClasses... {public:

    ClassName (BaseClasses&&... base_classes) : BaseClasses(base_classes)... {}};
這個例子中的解包算子將複製所有模板參數類型為ClassName的基類型。構造函數取每個基類的引用，並初始化每個基類。
對於函數模板，可變模板參數可以轉發（forward）。當與右值引用結合使用，這允許完美轉發（perfect forwarding）：
template<typename TypeToConstruct> struct SharedPtrAllocator {

    template<typename ...Args> std::shared_ptr<TypeToConstruct> construct_with_shared_ptr(Args&&... params) {
        return std::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
    }};
上例中，實參列表被解包給TypeToConstruct的構造函數。std::forward<Args>(params)的句法是以適當的類型轉發實參。解包算子將把轉發語法應用於每個參數。 
模板參數包中實參的個數可以如下確定：
template<typename ...Args> struct SomeStruct {
    static const int size = sizeof...(Args);};
例如SomeStruct<Type1, Type2>::size為2，SomeStruct<>::size為0。需要注意，sizeof...與sizeof是兩個不同的運算符。
Lambda捕獲例子：
template<class ...Args>void f(Args... args) {
    auto lm = [&, args...] { return g(args...); };
    lm();}

編譯器實現

GCC尚不支持lambda表達式包含為展開的參數包，因此下述語句編譯不通過：
   int arr[]{([&]{ std::cout << args << std::endl; }(), 1)...};
Visual C++ 2013支持上述風格的語句。當然，這裡的lambda函數不是必需的，通常的表達式即可：
  int arr[]{(std::cout << args << std::endl, 1)...};

例子

下述代碼實現了C++14引入的make_integer_sequence函數模板。它產生一個模板類，其模板參數為0,1,2,...,N。可用於生成或訪問std::tuple
#include <iostream> // using aliases for cleaner syntax template<unsigned...> struct seq { using type = seq; };template<class S1, class S2> struct concat;template<unsigned... I1, unsigned... I2>struct concat<seq<I1...>, seq<I2...>>
: seq<I1..., (sizeof...(I1) + I2)...> {};template<unsigned N>struct make_integer_sequence : concat<typename make_integer_sequence<N / 2>::type, typename make_integer_sequence<N - N / 2>::type>::type {};template<> struct make_integer_sequence<1> : seq<0> {};int printItem(unsigned k){
std::cout << k << ' ';
return 0;}template<unsigned... I1>void printTemplate(seq<I1...> a){
int nn[] = { printItem(I1)... };}int main(){
make_integer_sequence<10> a;
printTemplate(a);}
輸出為
0 1 2 3 4 5 6 7 8 9