目前大部分主流編譯器的最新版本均支援了C++11標準(官方名為ISO/IEC14882:2011)大部分的文法特性,其中比較難理解的新文法特性可能要屬變長參數模板(variadic template)了。下面先介紹一下這個文法特性在C++11標準中的描述。
14.5.3 變長參數模板(Variadic templates)
1、一個模板形參包(template parameter pack)是一個接受零個或多個模板實參的模板形參。【例:
template<class ... Types> struct Tuple { };Tuple<> t0; // Types不含任何實參Tuple<int> t1; // Types含有一個實參:intTuple<int, float> t2; // Types含有兩個實參:int和floatTuple<0> error; // 錯誤:0不是一個類型
——例結束】
2、一個函數形參包(function parameter pack)是一個接受零個或多個函數實參的函數形參。【例:
template<class ... Types> void f(Types... args);f(); // OK:args不含有任何實參f(1); // OK:args含有一個實參:intf(2, 1.0); // OK:args含有兩個實參int和double
——例結束】
3、一個形參包要麼是一個模板形參包,要麼是一個函數形參包。
4、一個包擴充(expansion)由一個模式(pattern)和一個省略符號組成。包擴充的執行個體中一個列表中產生零個或多個模式的執行個體。模式的形式依賴於擴充所發生的上下文中。【譯者註:
template <typename... TS> // typename... TS為模板形參包,TS為模式static void MyPrint(const char* s, TS... args) // TS... args為函數形參包,args為模式{ printf(s, args...);}
】
包擴充會在以下上下文中發生:
——在一個函數形參包中(8.3.5);該模式是一個沒有省略符號的parameter-declaration。【譯者註:
template <typename... Types>void func(Types... args); // args為模式
】
——在一個模板形參包中,該包是一個包擴充(14.1):
——如果模板形參包是一個parameter-declaration;且該模式是沒有省略符號的parameter-declaration。【譯者註:
template <typename... Types> // Types為模式void func(Types... args);
】
——如果模板形參包是具有一個template-parameter-list的一個type-parameter;且該模式是相應的type-parameter且沒有省略符號。【譯者註:
// 這裡模板形參包的模式為Classestemplate <template <typename P, typename Q> class ... Classes>struct MyAStruct;
】
——在一個初始化器列表中(8.5);模式是一個initializer-clause。
——在一個base-specifier-list(條款10)中;模式是一個base-specifier。
——在一個mem-initializer-list(12.6.2)中;模式是一個mem-initializer。
——在一個template-argument-list(14.3)中,模式是一個template-argument。
——在一個dynamic-exception-specification(15.4)中;模式是type-id。
——在一個attribute-list中(7.6.1);模式是一個attribute。
——在一個alignment-specifier(7.6.2)中;模式是沒有省略符號的alignment-specifier。
——在一個capture-list(5.1.2)中,模式是一個capture。
——在一個sizeof...運算式(5.3.3)中,模式是一個identifier。
【例:
template<class ... Types> void f(Types ... rest);template<class ... Types> void g(Types ... rest) { f(&rest ...); // “&rest ...”是一個包擴充;“&rest”是其模式}
——例結束】
5、一個形參包,其名字出現在一個包擴充的模式之內,被其包擴充而擴充。一個形參包的名字的一次出現僅僅被最內部所封閉的包擴充而擴充。一個包擴充模式應該命名一個或多個形參包,一個嵌套的包擴充不會擴充它們;這樣的形參被稱為模式中不被擴充的形參包。所有被一個包擴充所擴充的形參包應該具有相同數量的所指定的實參。沒有被擴充的一個形參包的一個名字的一次出現是不良形式的。【例:
template<typename...> struct Tuple { };template<typename T1, typename T2> struct Pair { };template<class ... Args1> struct zip { template<class ... Args2> struct with { typedef Tuple<Pair<Args1, Args2> ... > type; }; // 譯者註:這裡是對Pair<Args1, Args2>進行擴充};// T1是Tuple<Pair<short, unsignd short>, Pair<int, unsigned> >typedef zip<short, int>::with<unsigned short, unsigned>::type T1;// 錯誤:對Args1和Args2指定了不同個數的實參typedef zip<short>::with<unsigned short, unsigned>::type t2;template <typename ... Args>void f(Args... args){}template<class ... Args>void g(Args ... args) { // OK:Args被函數形參包args擴充 f(const_cast<const Args*>(&args)...); // OK:“Args”與“args”被擴充 f(5 ...); // 錯誤:模式沒包含任何形參包 f(args); // 錯誤:形參包“args”沒被擴充 f(h(args ...) + args ...); // OK:第一個“args”在h內被擴充,第二個“args”在f內被擴充}
——例結束】
6、一個包擴充的執行個體,它不是一個sizeof...運算式,產生一個列表E1,E2,E3,...,EN,這裡,N是包擴充形參中元素的個數。每個Ei通過執行個體化該模式並用其第i個元素來代替每個包擴充形參來產生。所有Ei變為封閉列表中的元素。【註:列表的多樣性會根據上下文而有所不同:expression-list,base-specifier-list,template-argument-list,等等。——注結束】當N為零時,擴充的執行個體產生一個空列表。這樣的一個執行個體並不改變封閉構造的文法上的解釋,甚至在忽略整個列表會導致不良形式的情況下或會在文法上產生奇異性的情況下。【例:
template<class... T>struct X : T...{ // 譯者添加 X(T... args) { }};template<class... T> void f(T... values) { X<T...> x(values...);}template void f<>(); // OK:X<>沒有基類;x是類型X<>被值初始化的一個變數// 譯者添加:int main() { struct Y { }; struct Z { }; f<>(); // 使用template void f<>();其中使用X<> x(); // 使用template<class... T> void f(T... values); // 其內部使用X<Y, Z> x(Y(), Z()); // 而X<Y, Z>的定義為:struct X : Y, Z { X(Y arg1, Z arg2) { } }; f(Y(), Z());}
——例結束】
7、一個sizeof...運算式的執行個體(5.3.3)產生了包含在它所擴充的形參包中元素個數的一個整數常量。
上述就是C++11標準對變長模板形參的描述。下面我將給出一些程式碼範例來做進一步的描述協助大家更好地去理解,尤其是包擴充機制。
//============================================================================// Name : CPPTest.cpp// Author : Zenny Chen// Version :// Copyright : Your copyright notice// Description : Hello World in C++, Ansi-style//============================================================================#include <iostream>#include <typeinfo>using namespace std;#include <stdio.h>#include <stdarg.h>struct MyTest;// 普通的C函數變長形參static void MyCPrint(const char *s, ...){ char strBuffer[1024]; va_list ap; va_start(ap, s); vsprintf(strBuffer, s, ap); va_end(ap); printf(strBuffer);}template <typename... TS> // typename... TS為模板形參包,TS為模式static int MyPrint(const char* s, TS... args) // TS... args為函數形參包,args為模式{ return printf(s, args...);}template <typename... TS> // 模板形參包(template parameter pack)static void DummyIter(TS... args) // 函數形參包(function parameter pack){}template <typename T>static T Show(T t, int n){ cout << "The value is: " << t << ", and n = " << n << endl; return t;}template <typename... TS>static void Func(TS... args){ // 這裡,Show(args, sizeof...(args))為模式,因此Show(args, sizeof...(args))...被擴充 // 每個args執行個體的類型為其所對應TS模板實參的類型 // 這裡,Show(T, int)函數必須返回T類型,不能是void,由於void與TS...類型無法匹配 DummyIter(Show(args, sizeof...(args))...);}// 請大家注意一下以下兩種函數調用方式的不同!template <typename... Types>static void Foo(Types... args){ // 對DummyIter調用擴充MyPrint("The type is: %s\n", typeid(args).name()) DummyIter(MyPrint("The type is: %s\n", typeid(args).name()) ...); puts("============"); // 對MyPrint調用擴充args DummyIter(MyPrint("The first value is: %d, second is: %s, third is: %f\n", args...));}// 對C++11標準14.5.3條款中的第5項中例子的進一步描述template <typename... Types>struct VariadicStruct : Types...{ };template <typename... Types>static void ConstructStruct(void){ VariadicStruct<Types...>();}template void ConstructStruct<>(void); // OK:VariadicStruct<>沒有基類template <typename... Types>static void f(Types... args){ printf("The sample values are: %f, %f\n", args...);}// 特化不帶任何參數的ftemplate<> void f<>(){ cout << "No arguments!" << endl;}template <typename T1, typename T2>static auto h(T1 t1, T2 t2) -> decltype(t1 * t2){ return t1 * t2;}template <typename... Types>static void g(Types... args){ // 這裡,調用main函數中的g(10, 0.1)之後,會被展開為: // f(h(10, 0.1) + 10, h(10, 0.1) + 0.1); // 這裡有兩層包展開,首先對於f(),其模式為h(args...) + args // 然後對於h(),其模式為args // 因此,最右邊的省略符號其實是對整個(h(args...) + args)進行擴充 // 其等價於:f((h(args...) + args) ...); f(h(args...) + args ...);}extern "C" void cppTest(void){ MyCPrint("This is C print: %d, %s\n", 1, "Hello, world!"); MyPrint("This is my print: %d, %s\n", -1, "Hello, world!"); Func(-100, 0.5); puts(""); Foo(3, "Hello", 0.25); // 對C++11標準14.5.3條款中的第5項中例子的進一步描述 puts("\n"); struct A{}; struct B{}; ConstructStruct<A, B>(); // 在此函數內部構造了VariadicStruct<A, B> ConstructStruct<>(); // 在此函數內構造了VariadicStruct<>,它沒有基類 g(10, 0.1); g<>();}