介绍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标准对变长模板形参的描述。下面我将给出一些代码示例来做进一步的描述帮助大家更好地去理解,尤其是包扩展机制。// 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
给自己做的笔记
介绍C++11标准的变长参数模板的更多相关文章
- C++11变长参数模板
[C++11变长参数模板] C++03只有固定模板参数.C++11 加入新的表示法,允许任意个数.任意类别的模板参数,不必在定义时将参数的个数固定. 实参的个数也可以是 0,所以 tuple<& ...
- C++中的变长参数
新参与的项目中,为了使用共享内存和自定义内存池,我们自己定义了MemNew函数,且在函数内部对于非pod类型自动执行构造函数.在需要的地方调用自定义的MemNew函数.这样就带来一个问题,使用stl的 ...
- c++11变长参数函数模板
By francis_hao Mar 25,2018 一个最简单的实例大概是这个样子: #include <iostream>using namespace std; /*变长参 ...
- 《OOC》笔记(3)——C语言变长参数va_list的用法
<OOC>笔记(3)——C语言变长参数va_list的用法 C语言中赫赫有名的printf函数,能够接受的参数数目不固定,这就是变长参数.C#里也有params这个关键字用来实现变长参数. ...
- (一)预定义宏、__func__、_Pragma、变长参数宏定义以及__VA_ARGS__
作为第一篇,首先要说一下C++11与C99的兼容性. C++11将 对以下这些C99特性的支持 都纳入新标准中: 1) C99中的预定义宏 2) __func__预定义标识符 3) _Pragma操作 ...
- Java语法糖初探(三)--变长参数
变长参数概念 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用.形如 function(T …args).但是需要明 ...
- c(++)变长参数之整形(非字符串类型类似)
0.序言 变长参数,接触的第一个可变长参数函数是 printf , 然后是 scanf .他们的原型如下: printf: _Check_return_opt_ _CRT_STDIO_INLI ...
- Scala 变长参数
如果Scala定义变长参数 def sum(i Int*), 那么调用sum时,可以直接输入sum(1,2,3,4,5) 但是不可以sum(1 to 5) 必须要将1 to 5 强制为seq sum( ...
- 【Unix环境高级编程】编写变长参数函数
文件的格式输入输出函数都支持变长参数.定义时,变长参数列表通过省略号'...'表示, 因此函数定义格式为: type 函数名(parm1, parm2,parmN,...); Unix的变长参数通过v ...
随机推荐
- 【Orleans开胃菜系列2】连接Connect源码简易分析
[Orleans开胃菜系列2]连接Connect源码简易分析 /** * prism.js Github theme based on GitHub's theme. * @author Sam Cl ...
- linux下tomcat指定jdk和配置运行参数
一.指定运行jdk 1)set classpath.sh和catalina.sh中写入: export JAVA_HOME=/usr/local/java/jdk1.8.0_121 export JR ...
- 如何使用淘宝 NPM 镜像,安装CNPM的方法
npm 版本需要大于 3.0 前提:安装好npm 环境:Linux 直接在linux下输入命令: npm install -g cnpm --registry=https://registry.npm ...
- 再探Redux Middleware
前言 在初步了解Redux中间件演变过程之后,继续研究Redux如何将中间件结合.上次将中间件与redux硬结合在一起确实有些难看,现在就一起看看Redux如何加持中间件. 中间件执行过程 希望借助图 ...
- 如何将maven项目打包上传到私服
比如我们想要把项目通过maven生产源码包和文档包并发布到自己的私服上,有两个maven插件可以做到这些工作,一个是maven-source-plugin,另一个是maven-javadoc-plug ...
- ElasticSearch 2 (19) - 语言处理系列之故事开始
ElasticSearch 2 (19) - 语言处理系列之故事开始 摘要 全文搜索是精度(尽可能少的返回不相关文档)和召回(尽可能多的返回相关文档)的战场.尽管只精确匹配用户查询的词肯定会是精确的, ...
- Linux搭建好apache后,只有本地能访问,局域或外网不能访问
由于防火墙的访问控制导致本地端口不能被访问. 解决方法: 1,直接关闭防火墙 systemctl stop firewalld.service #停止防火墙服务 systemctl disable ...
- RANCHER2.0 的简单使用
1. RANCHER2.0 能够管理 k8s 集群 也能够用来搭建 k8s 集群 但是因为网络问题 只测试了如何去管理集群 还没有去 测试 安装集群. 2. 创建rancher 服务的方法 dock ...
- 【转】Mysql事务,并发问题,锁机制
转自:http://www.cnblogs.com/fidelQuan/p/4549068.html 1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成 ...
- Git从零开始(二)
前面提交了一个test1.txt文件,接下来看看这个文件接下来的命运. 一.继续修改并提交 在test1.txt中修改后, git status 查看git的状态, 会提示我们文件test1.txt被 ...