介绍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-listbase-specifier-listtemplate-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标准对变长模板形参的描述。下面我将给出一些代码示例来做进一步的描述帮助大家更好地去理解,尤其是包扩展机制。// CPPTemplateTest.cpp : Defines the entry point for the console application.

//

#include "stdafx.h"

//============================================================================
// 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_s(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);

	puts("");

	Foo(3, "Hello", 0.25, "123");

	// 对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<>();
}
int main()
{
	cppTest();
	return 0;
}

  

  详细可以参考:https://www.cnblogs.com/zenny-chen/archive/2013/02/03/2890917.html

给自己做的笔记

原文地址:https://www.cnblogs.com/alinh/p/9025836.html

时间: 2024-10-18 23:54:19

介绍C++11标准的变长参数模板的相关文章

C++11 新特性之 变长参数模板

template <typename ... ARGS> void fun(ARGS ... args) 首先明确几个概念 1,模板参数包(template parameter pack):它指模板参数位置上的变长参数,例如上面例子中的ARGS 2,函数参数包(function parameter pack):它指函数参数位置上的变长参数,例如上面例子中的args 一般情况下 参数包必须在最后面,例如: template <typename T, typename ... Args>

C++11 变长参数的宏定义以及__VA_ARGS__

[1]变长参数的宏定义以及__VA_ARGS__ 在C99标准中,我们就已经可以使用变长参数的宏定义. 变长参数的宏定义是个神马?就是在宏定义的参数列表中最后一个参数为省略号. 而现在C++ 11中,使用预定义宏__VA_ARGS__可以在宏定义的实现部分替换省略号所代表的字符串. 原书示例: #include <stdio.h> #define LOG(...) { \ fprintf(stderr, "%s: Line %d:\t", __FILE__, __LINE_

(一)预定义宏、__func__、_Pragma、变长参数宏定义以及__VA_ARGS__

作为第一篇,首先要说一下C++11与C99的兼容性. C++11将 对以下这些C99特性的支持 都纳入新标准中: 1) C99中的预定义宏 2) __func__预定义标识符 3) _Pragma操作符 4) 不定参数宏定义以及__VA_ARGS__ 5) 宽窄字符串连接 这些特性并不像语法规则一样常用,并且有的C++编译器实现也都先于标准地将这些特性实现,因此可能大多数程序员没有发现这些不兼容.但将这些C99的特性在C++11中标准化无疑可以更广泛地保证两者的兼容性.我们来分别看一下. 这次,

Objective-C中实现变长参数问题

版权声明:本文为博主原创文章,未经博主允许不得转载. ObjC中没有提供直接的变长参数方法,需要使用C标准库中的av_list方法,简单使用如下: -(void)somethingForyou:(NSString *)vString,....{ va_list varList; id arg; NSMutableArray *argsArray = [[NSMutableArray alloc]inti]; if(vString){ va_start(varList,vString); whil

【Unix环境高级编程】编写变长参数函数

文件的格式输入输出函数都支持变长参数.定义时,变长参数列表通过省略号'...'表示, 因此函数定义格式为: type 函数名(parm1, parm2,parmN,...); Unix的变长参数通过va_list对象实现,定义在文件'stdarg.h'中,变长参数的应用模板如下所示: #include <stdarg.h> function(parmN,...){ va_list pvar; ................................. va_start(pvar,par

如何定义变长参数个数的函数

定义参数个数不确定的函数,需用到头文件stdarg.h,该头文件是专门为变长参数函数所用. 参数变长函数的声明:void function(int intVal, ...),当然参数类型可以为double或其他,返回类型也可以自己修改. 方法: 先用头文件stdarg.h中的宏va_list定义一个指向参数的指针ap,va_list ap: 再用宏va_start初始化指针ap,va_start(ap,知名变量名intVal): 再用va_arg使指针ap指向下一参数,并且取出该参数,va_ar

【小白学Lua】之Lua变长参数和unpack函数

一.简介 Lua的变长参数和unpack函数在实际的开发中应用的还挺多的,比如在设计print函数的时候,需要支持对多个变量进行打印输出,这时我们就需要用到Lua中的变长参数和unpack函数了. 二.Lua变长参数与unpack函数 Lua中支持可变参数,用 ... 表示.比如定义下面的这样一个函数: local function func1(...) end 当然它也支持在变长参数前面添加固定参数: local function func1(var,...) --dosomething en

C语言--变长参数

一.  实现原理 首先变长参数的实现依赖于cdecl调用,因为其规定了出栈方为函数调用方,从而解决被调用函数无法确定参数个数,其次cdecl规定参数入栈顺序为从右到左.所以第一个不定参数位于栈顶 二. 宏源码讲解  (va ---> variable-argument(可变参数)) 头文件 stdarg.h 2.1 va_list #define va_list char * 定义了一个指针arg_ptr, 用于指示可选的参数. 2.2 va_start(arg_ptr, argN) #defi

Java语法糖初探(三)--变长参数

变长参数概念 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用.形如 function(T -args).但是需要明确的一点是,java方法的变长参数只是语法糖,其本质上还是将变长的实际参数 varargs 包装为一个数组. 看下面的例子: 12345678910111213 public class VariVargs { public static void main(String []args) { tes