介绍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<>(); }