c++11——可变参数模板
在c++11之前,类模板和函数模板只能含有固定数量的模板参数,c++11增加了可变模板参数特性:允许模板定义中包含0到任意个模板参数。声明可变参数模板时,需要在typename或class后面加上省略号"..."。
省略号的作用有两个:
1. 声明一个参数包,这个参数包中可以包含0到任意个模板参数
2. 在模板定义的右边,可以将参数包展开成一个一个独立的参数
1. 可变参数模板函数
可变参数模板函数的定义如下:
template<class... T>
void f(T... args){
cout << sizeof...(args) << endl; //sizeof...(args) 取得可变参数的个数
}
f();
f(1, 2);
f(1, 2.3, "hello");
参数包可以包含0个或者多个参数,如果需要用参数包中的参数,则一定要将参数包展开。有两种展开参数包的方法:(1)通过递归的模板函数来展开参数包;(2)通过逗号表达式和初始化列表方式展开参数包。
展开参数包
(1)递归函数方式展开参数包
需要提供一个参数包展开的函数和一个递归终止函数,二者同名。递归终止函数的参数可以为0,1,2或者多个(一般用到0个或1个),当参数包中剩余的参数个数等于递归终止函数的参数个数时,就调用递归终止函数,则函数终止。
#include<iostream>
using namespace std;
//递归终止函数
void print(){
cout << "empty" << endl;
}
//展开函数
template<class T, class... Args>
void print(T head, Args... rest){
cout << "parameter = " << head << endl;
print(rest...);
} int main(){
print(1,2,3,4);
return 0;
}
//当调用print(1,2,3,4)时,先后调用print(2,3,4), print(3,4),print(4),最终调用print()终止。
如果递归终止函数为
template<typename T>
void print(T a){
cout << a << endl;
}则函数调用到 print(4)就终止。
还可以通过type_traits方式来定义同名但不同参数的函数,分别实现递归终止和展开函数,从而展开参数包
//相当于递归终止函数
template<typename I = 0, typename Tuple>
typename std::enable_if<I==std::tuple_size<Tuple>::value>::type printtp(Tuple t){ }
//相当于展开函数
template<typename I = 0, typename Tuple>
typename std::enable_if<I < std::tuple_size<Tuple>::value>::type printtp(Tuple t){
std::cout << std::get<I>(t) << std::endl; //打印出元组中的第i个
printtp<I+1>(t);
} template<typename... Args>
void print(Args... args){
printtp(std::make_tuple(args...);
}
(2)初始化列表方式展开参数包
递归函数展开参数包,需要有一个同名的终止函数来终止递归,可以使用初始化列表的方式避免多定义一个同名的终止函数。
template<typename T>
void printarg(T a){
cout << a << endl;
} template<class...Args>
void expand(Args... args){
int arr[] = {(printarg(args), 0)...};
//或者改进为 std::initializer_list<int>{(printarg(args), 0)...};
}
//{(printarg(args), 0)...}会被展开为{(printarg(arg1), 0), (printarg(arg2), 0), (printarg(arg3), 0)}
expand(1,2,3,"hello");
这种展开参数包的方式,不需要通过递归终止函数,而是直接在expand函数体内展开,printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种处理方式的关键是逗号表达式。
逗号表达式会按顺序执行前面的表达式,比如d = (a = b, c);b先赋值给a,接着括号中的逗号表达式返回c的值,因此d被赋值为c
c++11中使用列表初始化方法来初始化一个边长的数组,可以使用 int arr[] = {args...};其中args为一个变长的参数集合。template<typename... Args>
void print(Args... args){
int arr[] = { (args, 0)... }; //具名的args...(或者匿名的...) 代表所有的可变参数集合,可以将args和...分开,此时args表示...中每一个参数。
}
还可以通过lambda表达式来改进上述的列表初始化方式:
template<typename... Args>
void expand(Args... args){
std::initializer_list<int>{([&]{cout << args << endl;}(), 0)...};
}
2. 可变参数模板类
tuple是一个可变参数模板类:
template<class... Types>
class tuple;
这个可变参数模板类可以携带任意类型任意个数的模板参数
std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1,2.4);
std::tuple<> tp;
可变参数模板类的参数展开
(1)模板递归和特化方式展开参数包
可变参数模板类的展开一般需要2~3个类,包括类声明和特化的模板类。如下方式定义了一个基本的可变参数模板类:
template<typename... Args> //前向声明
struct Sum; template<typename First, typename... Rest> //类的定义
struct Sum<First, Rest...>{
enum {value = Sum<First>::value + Sum<Rest...>::value};
}; template<typename Last> //递归终止类,模板参数不一定为1个,可能为0个或者2个
struct Sum<Last>{
enum{value = sizeof(Last)};
};
这个Sum类的作用是在编译期计算出参数包中参数类型的size之和。 或者可通过std::integral_constant来修改一下:
template<typename... Args> //前置声明
struct Sum; template<typename First, typename... Rest> //递归定义
struct Sum<First, Rest...>: std::integral_constant<int, std::integral_constant<int, sizeof(First)>::value + Sum<Rest...>::value{
}; template<typename Last> //递归终止
struct Sum<Last>: std::integral_constant<int, sizeof(Last)>{
};
(2)继承方式展开参数包
//整型序列的定义
template<int...>
struct IndexSeq{}; //继承方式,开始展开参数包
template<int N, int ... Indexes>
struct MakeIndexes: MakeIndexes<N -1, N -1, Indexes...>{}; //模板特化,终止展开参数包的条件
template<int... Indexes>
struct MakeIndexes<0, Indexes...>{
typedef IndexSeq<Indexes...> type;
};
int main(){
using T = MakeIndexes<3>::type; //输出为 struct IndexSeq<0,1,2>
cout << typeid(T).name() << endl;
return 0;
}; //MakeIndexes如果不通过继承递归方式生成,可以通过using来实现。
template<int N, int ...Indexes>
struct MakeIndexes{
using type = MakeIndexes<N-1,N-1,Indexes>::type;
}; template<int...Indexes>
struct MakeIndexes<0, ...Indexes>{
using type = IndexSeq<Indexes...>;
};
可以使用上述的IndexSeq来展开并打印可变模板参数,比如:
template<int ...Indexes, typename ...Args>
void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup){
print(std::get<Indexes>(tup)...);
} template<typename ...Args>
void print(Args... args){
print_helper(typename MakeIndexes<sizeof...(args)>::type(), std::make_tuple(args...));
}
c++11——可变参数模板的更多相关文章
- C++11 可变参数模板
在C++11之前, 有两个典型的受制于模板功能不强而导致代码重复难看的问题, 那就 function object 和 tuple. 拿 function objects 来说, 需要一个返回类型参数 ...
- C++ 11可变参数接口设计在模板编程中应用的一点点总结
概述 本人对模板编程的应用并非很深,若要用一句话总结我个人对模板编程的理解,我想说的是:模板编程是对类定义的弱化. 如何理解“类定义的弱化”? 一个完整的类有如下几部分组成: 类的名称: 类的成员变量 ...
- [C++11]C++可变参数模板
可变参数模板 原文链接: http://blog.csdn.net/xiaohu2022/article/details/69076281 普通模板只可以采取固定数量的模板参数.然而,有时候我们希望模 ...
- c++ 可变参数模板
可变参数模板,自己尝试了个例子,如下: // variadicTemplates.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #incl ...
- C++11新特性之五——可变参数模板
有些时候,我们定义一个函数,可能这个函数需要支持可变长参数,也就是说调用者可以传入任意个数的参数.比如C函数printf(). 我们可以这么调用. printf(); 那么这个函数是怎么实现的呢?其实 ...
- C++ 标准库,可变参数模板。可变参数数量,可变参数类型【转】
#include <iostream> // 可变模板参数 // 此例:可以构造可变数量,可变类型的函数输入. // 摘自:https://www.cnblogs.com/qicosmos ...
- C++学习之可变参数的函数与模板
所谓可变参数指的是函数的参数个数可变,参数类型不定的函数.为了编写能处理不同数量实参的函数,C++11提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标 ...
- C++ 实现可变参数的三个方法
有时我们无法提前预知应该向函数传递几个实参.例如,我们想要编写代码输出程序产生的错误信息,此时最好用同一个函数实现该项功能,以便对所有错误的处理能够整齐划一.然而,错误信息的种类不同,所以调用错误输出 ...
- c++之可变参数格式化字符串(c++11可变模板参数)
本文将使用 泛型 实现可变参数. 涉及到的关见函数: std::snprintf 1.一个例子 函数声明及定义 1 // 泛型 2 template <typename... Args> ...
随机推荐
- 【Unity】计时器
看了好些方法,终于找到一个超级好用的计时器,立马转载马住了! http://www.gimoo.net/t/1602/56bfcc8a26757.html 运行效果如下: 思路:记录当前游戏时间然后进 ...
- 抽取、转换和装载介绍(三)ETL系统的34个子系统
这部分对ETL系统的架构中34个关键子系统进行分类.ETL其实可以分为下面四个步骤: 抽取.在ETL环境中从源系统收集原始数据并且在对任何对数据的重要重构发生之前都将数据写入磁盘.子系统1到子系统3都 ...
- [Makefile]多目录Makefile写法
最近需要写一个测试程序,这个测试程序需要集成一些功能,写在同一个文件看上去很不好,多个文件的Makefile又不是很熟,于是分享下面这篇文章 先介绍下,调试Makefile时,想知道某个变量的值,怎么 ...
- contiki 无线测试 1个中心节点 13个从节点
1 DATA recv '25.00 degres' from 2423:7c02:5525:4f2b2 DATA recv '27.71 degres' from 24f7:af03:5525:4f ...
- spring 加载bean过程源码简易解剖(转载)
这一篇主要是讲用载入bean的过程.其实就是IOC.低调 低调.. 我把重要的都挑出来了.一步步往下看就明白spring载入bean.xml里面bean的原理 . 感觉像候杰的 MFC深入浅出,哈哈. ...
- Ajax-ajax实例3-动态树形列表
项目结构: 项目演示: 技术要点: 1.3.2 技术要点在基本原理的介绍中,了解到通过在父节点内动态创建子节点,并利用样式表缩进完成树形列表的基本框架.除了这一点外,还有下面一些问题需要考虑.1 .将 ...
- 12款优秀 jQuery Ajax 分页插件和教程
12款优秀 jQuery Ajax 分页插件和教程 在这篇文章中,我为大家收集了12个基于 jQuery 框架的 Ajax 分页插件,这些插件都提供了详细的使用教程和演示.Ajax 技术的出现使得 W ...
- [随想感悟] 面试时,问哪些问题能试出一个 Android 应用开发者真正的水平?【转自知乎】
这几年面过的各种Android开发也有三位数了,failed的不敢说,pass的基本都没有看走眼,来得晚了也想说说我的体会. 一般面试时间短则30分钟,多则1个小时,这么点时间要全面考察一个人难度很大 ...
- C++对析构函数的误解
C++析构前言 析构函数在什么时候会自动被调用,在什么时候需要手动来调用,真不好意思说偶学过C++…今日特此拨乱反正. C++析构误解正文 对象在构造的时候系统会分配内存资源,对一些数据成员进行初始化 ...
- jenkins配置过程遇到的问题
jenkins 搭建完成后,可以浏览器访问: http://localhost:8081/jenkins, 新建任务过程中遇到以下问题: 1. 源码管理不现实git, 只显示无 解决: 插件管理 - ...