【可变参模板】可变参类模板
可变参类模板也和可变参函数模板一样,允许模板定义含有0到多个(任意个)模板参数。可变参类模板参数包的展开方式有多种,以下介绍几种常见的方法。
一、递归继承展开
1.1类型模板参数包的展开
首先先看下面的代码:
//类型模板展开
//泛化版本
template<typename...Args>
class myclasst {public:myclasst() {std::cout << "myclasst::myclasst()泛化版本执行了,this = " << this<< " sizeof...(Args) = " << (sizeof...(Args)) << "\n";}};template<typename First, typename... Others>
class myclasst<First, Others...> :private myclasst<Others...> { //继承偏特化版本public:First m_i;myclasst() :m_i(0) {std::cout << "myclasst::myclasst()偏特化版本执行了,this = " << this<< " sizeof...(Others) = " << (sizeof...(Others)) << "\n";}};void Test1() {myclasst<int, float, float>myc;
}
这里,我们定义了一个可变参类模板的泛化版本,通过 s i z e o f . . . sizeof... sizeof...计算出参数数量,我们还打印了 t h i s this this指针。
然后,我们又定义了一个偏特化类型,这个偏特化类型继承了另一个偏特化版本。可以发现,继承的参数列表参数减少了一个。
我们运行后,得到以下结果。
容易发现, t h i s this this指针是一致的,而且打印的参数是从少到多的。

因此,我们可以得到结论:
对于这种继承来展开的参数包,我们是递归到空参,调用泛化版本构造了这样的一个类,作为祖宗类,然后再通过这个祖宗不断通过继承链展开。
由于是继承关系,所以 t h i s this this指针显然是相同的。
相应的关系如下图所示:

当然,如果我们对泛化版本进行一个空参的偏特化,如下:
//使用特殊的特化版本
template<>
class myclasst<> {
public:myclasst() {std::cout << "myclasst<>::myclasst()特殊的特化版本执行了,this = " << this << "\n";}
};
可以发现,这次最开始实例化的模板是这个空参的特化版本,注意这里并不是全特化(可变参模板不存在全特化)

有时,如果我们不需要使用泛化版本(就像上面的样例),直接声明即可:
//直接声明
template<typename...Args>
class myclasst;
1.2非类型模板参数包的展开
参数包不仅可以是类型的,也可以是非类型的。
看下面的范例:
//非类型模板参数的展开//泛化版本
template<auto... FArgs>
class myclasst2 {
public:myclasst2() {std::cout << "myclasst2::myclasst2()偏特化版本执行了,this = " << this<< " sizeof...(Others) = " << (sizeof...(FArgs)) << "\n";}
};//偏特化版本
template<auto First, auto... Others>
class myclasst2<First, Others...> :public myclasst2<Others...> {
public:myclasst2() {std::cout << "myclasst2::myclasst2()偏特化版本执行了,this = " << this<< " sizeof...(Others) = " << (sizeof...(Others)) << ",First = " << First << "\n";}
};void Test2() {myclasst2<1, 2LL, 'a', 4, 5>myc;}
可以发现,这里的参数列表使用了 a u t o auto auto,这样更灵活一些。实际上,如果想要固定类型,如 ( i n t , c h a r , d o u b l e ) (int,char,double) (int,char,double)等类型,也可以直接使用。
对于非类型参数包的展开,大体上和类型参数包展开一致,这里直接调用,得到:

同样,对于参数的展开是递归的,从最后开始实例化
1.3 模板模板参数的展开
对于参数列表是模板模板参数,并且是一个可变参数列表的类型,我们同样可以展开,写法上类似,只是繁琐了些。
参考下方代码:
//模板模板参数的展开
template<typename T, template<typename>typename... Container> //泛化版本
class myclasst3 {
public:myclasst3() {std::cout << "myclasst3::myclasst3()泛化版本执行了,this = " << this << "\n";}
};//偏特化版本
template<typename T, template<typename>typename FirstContainer,template<typename>typename... OtherContainers>
class myclasst3<T, FirstContainer, OtherContainers...>:public myclasst3<T, OtherContainers...> {public:myclasst3() {std::cout << "myclasst3::myclasst3()偏特化版本执行了,this = " << this<< " sizeof...(Others) = " << (sizeof...(OtherContainers)) << "\n";}
};
同样是继承链展开,我们下面写一个辅助函数来查看调用的信息:
//查看类型的辅助类
template<typename T, template<typename>typename... Container>
class myclasst4 :public myclasst3<T, Container...> {public:myclasst4() {std::cout << "myclasst4::myclasst4()执行了,this = " << this << ", T的类型是:"<< typeid(T).name() << ",Container的参数个数是" << (sizeof...(Container)) << "\n";}};
void Test3() {myclasst4<int, std::vector, std::list, std::deque, std::stack>myc;}
这个辅助类继承自泛化版本,因此调用这个辅助类模板的时候,就会展开模板模板类型参数包:

这里使用了 t y p e i d typeid typeid来打印类型名,可以发现实例化出的容器类都是 i n t int int类型。
二、通过递归组合方式来展开参数包
2.1 递归组合展开
前面的展开方式基于继承,而递归组合方式是基于构造来展开的。
先看看下面的代码:
//通过递归组合展开//泛化版本
template<typename... args>
class myclassT {
public:myclassT() {std::cout << "myclassT::myclassT()泛化版本执行了,this = " << this << "\n";}
};//偏特化版本
template<typename First, typename...Others>
class myclassT<First, Others...> {
public:First m_i;myclassT<Others...>m_o;myclassT() :m_i(0) {std::cout << "myclassT::myclassT()偏特化版本执行了,this = " << this << "<sizeof...(Others) = "<< sizeof...(Others) << "\n";}//递归构造组合myclassT(First parf, Others... paro) :m_i(parf), m_o(paro...) {std::cout << "------------begin------------\n";std::cout << "myclassT::myclassT(parf,...paro)执行了,this = " << this << "\n";std::cout << "m_i = " << m_i << "\n";std::cout << "------------end------------\n\n";}};
我们省去了繁琐的继承链,通过在泛化版本内部设置成员变量以及构造函数的方式来展开参数包。

我们会进行递归的构造,直到参数列表为空,调用泛化版本结束递归。

同样的,也能用特殊的特化版本来结束递归:
//特殊的特化版本
template<>
class myclassT<> {
public:myclassT() {std::cout << "myclassT::myclassT()特殊的特化版本执行了,this = " << this << "\n";}
};
运行测试代码:
void Test4() {myclassT<int, float, double, char>myc(1, 2.0f, 3.0, '4');
}
得到以下结果:

同样是递归展开,所以参数的构造也是从低向上,从右到左。而这一次,由于不是继承关系,所以 t h i s this this指针也不同。

非类型参数包和模板模板类型参数包的展开类似,这里就不赘述了。
三、通过元组和递归调用展开参数包
3.1元组的基本使用
元组 t u p l e tuple tuple是 C + + 11 C++11 C++11引入的类型,在元组内可以定义不同的类型,下面是元组的基本使用方法:
//元组的基本使用方法
void Test5() {//定义不同类型的元素std::tuple<int, char, float>mytuple(0, '1', 2.0f);//取出元组第i个元素,使用std::get<>函数模板std::cout << std::get<0>(mytuple) << "\n";//后一个类型可以自动推导std::cout << std::get<1>(mytuple) << "\n";std::cout << std::get<2>(mytuple) << "\n";//使用std::get<T>返回元组类型为T的值,如果存在多个相同类型会报错std::cout << "int类型为:" << std::get<char>(mytuple) << "\n";}
其中 s t d : : g e t < > std::get<> std::get<>是一个函数模板,可以去除元组第 k k k个类型的数,也可以指定类型取出相应类型的值。
注意的是,如果是取出指定类型的值,必须是唯一的,不然将无法编译通过。
简单测试,运行后得到:

3.2使用元组来递归展开
借助元组可以定义不同类型的这个特点,我们可以进行参数包的展开:
//使用元组来递归展开
template<int mycount, int mymaxcount, typename...T>
class myclassT2 {
public:static void mysfunc(const std::tuple<T...>& t) {std::cout << "获取第" << mycount + 1 << "个元素,val = " << std::get<mycount>(t) << "\n";myclassT2<mycount + 1, mymaxcount, T...>::mysfunc(t); //自递归,每次取出后一个数}
};//偏特化版本,用于结束递归
template<int mymaxcount, typename...T>
class myclassT2<mymaxcount, mymaxcount, T...> {
public:static void mysfunc(const std::tuple<T...>& t) {std::cout << "调用特化版本结束递归!\n";}
};//辅助调用模板
template<typename...T>
void myfunctuple(const std::tuple<T...>& t) {myclassT2<0, sizeof...(T), T...>::mysfunc(t); //从0开始展开}
这里, m a x c o u n t maxcount maxcount是需要展开的参数包的总大小,通过 s i z e o f . . . sizeof... sizeof...求得,mycount$是目前展开了多少个参数,初始为0 0 0 0,需要递归 + 1 +1 +1。
因此,当 m a x c o u n t = m y c o u n t maxcount = mycount maxcount=mycount时,需要调用一个特化版本来结束递归。
我们写了一个辅助模板,用于展开:

成员函数使用静态的,这样在编译期间就能确定,不使用静态的也行,只是这样需要创建类对象。
调用测试函数:
void Test6() {std::tuple<float, int, int>mytuple(1.0f, 2, 3);myfunctuple(mytuple);
}
成功展开了参数包:

相关文章:
【可变参模板】可变参类模板
可变参类模板也和可变参函数模板一样,允许模板定义含有0到多个(任意个)模板参数。可变参类模板参数包的展开方式有多种,以下介绍几种常见的方法。 一、递归继承展开 1.1类型模板参数包的展开 首先先看下面的代码: /…...
Linux 递归删除大量的文件
一般情况下 在 Ubuntu 中,递归删除大量文件和文件夹可以通过以下几种方式快速完成。常用的方法是使用 rm 命令,配合一些适当的选项来提高删除速度和效率。 1. 使用 rm 命令递归删除 最常见的方式是使用 rm 命令的递归选项 -r 来删除目录及其所有内容。…...
设计一个算法,找出由str1和str2所指向两个链表共同后缀的起始位置
假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,例如,’loading’和’being’的存储映像如下图所示。 设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为 data…...
Python中如何判断一个变量是否为None
在Python中,判断一个变量是否为None是一个常见的需求,特别是在处理可选值、默认值或者是在函数返回结果可能不存在时。虽然这个操作本身相对简单,但围绕它的讨论可以扩展到Python的哲学、类型系统、以及如何在不同场景下优雅地处理None值。 …...
表观遗传系列1:DNA 甲基化以及组蛋白修饰
1. 表观遗传 表观遗传信息很多为化学修饰,包括 DNA 甲基化以及组蛋白修饰,即DNA或蛋白可以通过化学修饰添加附加信息。 DNA位于染色质(可视为微环境)中,并不是裸露的,因此DNA分子研究需要跟所处环境结合起…...
Android 跳转至各大应用商店应用详情页
测试通过机型品牌: 华为、小米、红米、OPPO、一加、Realme、VIVO、IQOO、荣耀、魅族、三星 import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import …...
Pywinauto鼠标操作指南
Pywinauto是一个强大的Python库,用于自动化Windows桌面应用程序的测试。它提供了一系列工具和API来模拟用户输入,包括键盘、鼠标事件,以及与各种窗口控件交互的能力。本文将详细介绍如何使用Pywinauto来执行鼠标操作,并通过一些示…...
VRAY云渲染动画怎么都是图片?
动画实际上是由一系列连续的静态图像(帧)组成的,当这些帧快速连续播放时,就形成了动画效果。每一帧都是一个单独的图片,需要单独渲染。 云渲染农场的工作方式: 1、用户将3D场景文件和动画设置上传到云渲染…...
共享内存(C语言)
目录 一、引言 二、共享内存概述 1.什么是共享内存 2.共享内存的优势 三、共享内存的实现 1.创建共享内存 2.关联共享内存 3.访问共享内存 4.解除共享内存关联 5.删除共享内存 四、共享内存应用实例 五、总结 本文将深入探讨C语言中的共享内存技术,介绍其原理、…...
《JavaEE进阶》----16.<Mybatis简介、操作步骤、相关配置>
本篇博客讲记录: 1.回顾MySQL的JDBC操作 2..Mybatis简介、Mybatis操作数据库的步骤 3.Mybatis 相关日志的配置(日志的配置、驼峰自动转换的配置) 前言 之前学习应用分层时我们知道Web应用程序一般分为三层,Controller、Service、D…...
HuggingFists算子能力扩展-PythonScript
HuggingFists作为一个低代码平台,很多朋友会关心如何扩展平台算子能力。扩展平台尚不支持的算子功能。本文就介绍一种通过脚本算子扩展算子能力的解决方案。 HuggingFists支持Python和Javascript两种脚语言的算子。两种语言的使用方式相同,使用者可以任选…...
WInform记录的添加和显示
1、程序 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace ComboBoxApp {public part…...
★ C++基础篇 ★ string类的实现
Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C基础篇第五章下篇----string类的模拟实现 ~ 上篇:★ C基础篇 ★ string类-CSDN博客 C基础篇专栏:★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 目录 一 基础结构 二 迭代器 …...
rman compress
级别 初始 备完 耗时 low 1804 3572 0:10 High 1812 3176 2:00 MEDIUM 1820 3288 0:13 BASIC 1828 3444 0:56 ---不如MEDIUM,时间还长 NO COMPRESS 1820 5924 0:5 R…...
创建一个Oracle版本的JDK的Docker镜像
背景说明 OpenJDK 和Oracle JDK 一般情况下我们选择OpenJDK,两者针对大部分场景都可以满足,有些地方例如反射技术获得某些包路径下的类对象等,有时候选择OpenJDK会导致空指针异常。 两者在底层实现方面有部分区别。 创建镜像 这里是Linux…...
Harmony OS DevEco Studio 如何导入第三方库(以lottie为例)?-- HarmonyOS自学2
在做鸿蒙开发时,离不开第三方库的引入 一.有哪些支持的Harmony OS的 第三方库? 第三方库下载地址: 1 tpc_resource: 三方组件资源汇总 2 OpenHarmony三方库中心仓 二. 如何加入到DevEco Studio工程 以 lottie为例 OpenHarmony-TPC/lot…...
JAVA数据导出为Excel
目录 一、导入依赖 二、使用的相关类 1、XSSFWorkbook 构造方法 创建表 操作表 保存表 样式和格式 日期处理 密码保护 其他 2、XSSFSheet 获取属性和信息 行操作 列操作 表的属性 合并单元格 保护表 页眉和页脚 注释 其它 3、XSSFRow 获取属性和信息 单…...
【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣876, 2095, 234
1. 力扣876:链表的中间节点 1.1 题目: 给你单链表的头结点 head ,请你找出并返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点。 示例 1: 输入:head [1,2,3,4,5] 输出:[3,4,…...
第十五届蓝桥杯图形化省赛题目及解析
第十五届蓝桥杯图形化省赛题目及解析 一. 单选题 1. 运行以下程序,角色会说( )? A、29 B、31 C、33 D、35 正确答案:C 答案解析: 重复执行直到m>n不成立,即重复执行直到m<n。所有当m小于或者 等于n时&…...
linux下NTP服务器实战(chrony软件)
linux下NTP服务器实战(chrony软件) 记录linux下NTP服务器搭建及相关管理操作,使用chrony软件包安装部署。相比ntp服务,Chrony服务适用于更高精度、更高稳定性、自动化等场景。 1. 安装 chrony 在大多数Linux发行版上,chrony可以通过包管理…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
算法—栈系列
一:删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...
第22节 Node.js JXcore 打包
Node.js是一个开放源代码、跨平台的、用于服务器端和网络应用的运行环境。 JXcore是一个支持多线程的 Node.js 发行版本,基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。 本文主要介绍JXcore的打包功能。 JXcore 安装 下载JXcore安装包&a…...
机器学习复习3--模型评估
误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为:误差(error)。 误差定义: ①在训练集上的误差称为训练误差(training error)或经验误差(empirical error&#x…...
GC1808:高性能音频ADC的卓越之选
在音频处理领域,高质量的音频模数转换器(ADC)是实现精准音频数字化的关键。GC1808,一款96kHz、24bit立体声音频ADC,以其卓越的性能和高性价比脱颖而出,成为众多音频设备制造商的理想选择。 GC1808集成了64倍…...
设计模式-3 行为型模式
一、观察者模式 1、定义 定义对象之间的一对多的依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。 描述复杂的流程控制 描述多个类或者对象之间怎样互相协作共同完成单个对象都无法单独度完成的任务 它涉及算法与对象间职责…...
