介紹C++11標準的變長參數模板
目前大部分主流編譯器的最新版本均支持了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含有一個實參:int
Tuple< int , float > t2; // Types含有兩個實參:int和float
Tuple<0> error; // 錯誤:0不是一個類型
|
——例結束】
2、一個函數形參包(function parameter pack)是一個接受零個或多個函數實參的函數形參。【例:
template < class ... Types> void f(Types... args);
f(); // OK:args不含有任何實參
f(1); // OK:args含有一個實參:int
f(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且沒有省略號。【譯者註:
// 這裏模板形參包的模式為Classes
template < 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...); } // 特化不帶任何參數的f template<> 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<>(); }
介紹C++11標準的變長參數模板