【C++11】可变模板参数详解
个人主页:chian-ocean
文章专栏
C++ 可变模板参数详解
1. 引言
C++模板是现代C++编程中一个非常强大且灵活的工具。在C++11标准中,引入了可变模板参数(variadic templates),它为模板编程带来了革命性改变。它的出现允许我们编写更加通用和灵活的代码,解决了以往必须依赖递归继承或多个特化版本处理可变数量参数的复杂性。
可变模板参数与其他C++特性结合,能够产生极其灵活的编程模式。这篇文章将深入探讨可变模板参数的使用、背后的原理以及应用场景,帮助你理解和掌握这个高级C++编程技巧。
2. 什么是可变模板参数?
可变模板参数是指一个模板参数包,能够接受任意数量的模板参数。它的语法通过在参数名之前加上...
来表示。
template<typename... Args>
void foo(Args... args) {// 函数实现
}
在这个例子中,Args
是一个模板参数包,args
是一个函数参数包。这意味着你可以传递任意数量、任意类型的参数给foo
函数。
2.1 模板参数包展开
使用可变模板参数的关键在于展开参数包。展开可以是递归的,也可以通过其他方式逐个处理每个参数。
一个常见的技巧是使用递归模板调用:
template<typename T>
void print(T value) {std::cout << value << std::endl;
}template<typename T, typename... Args>
void print(T first, Args... rest) {std::cout << first << std::endl;print(rest...);
}
在这个例子中,print
函数的重载版本允许我们递归展开参数包。在递归的每一步,first
参数被打印出来,剩余参数被传递给下一次调用,直到展开完成。
3. 可变模板参数的应用场景
3.1 打印任意数量的参数
上面的例子展示了如何使用可变模板参数来打印任意数量的参数,这是一个典型的应用场景。可变模板参数的一个显著优点是它可以处理各种类型的参数,而不需要手动编写多个函数重载。
3.2 类型推导与 SFINAE
可变模板参数与C++中的类型推导机制紧密结合,可以编写出极其灵活的函数。例如,我们可以编写一个函数,自动推导传入参数的类型,并根据不同的类型执行不同的操作。
结合SFINAE(Substitution Failure Is Not An Error),我们可以对不同类型的参数进行筛选。
template<typename T>
std::enable_if_t<std::is_integral_v<T>, void> process(T value) {std::cout << "Integral type: " << value << std::endl;
}template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, void> process(T value) {std::cout << "Floating point type: " << value << std::endl;
}template<typename... Args>
void process_args(Args... args) {(process(args), ...); // 使用参数包展开
}
在这个例子中,我们通过std::enable_if_t
和SFINAE来筛选参数的类型。process_args
可以接受任意类型的参数,并针对整数类型和浮点数类型分别进行处理。
3.3 类型安全的 printf 替代方案
传统的printf
函数由于缺乏类型安全性,容易引发运行时错误。我们可以使用可变模板参数实现一个类型安全的printf
替代方案。
void my_printf(const char* format) {std::cout << format;
}template<typename T, typename... Args>
void my_printf(const char* format, T value, Args... args) {for (; *format != '\0'; ++format) {if (*format == '%' && *(++format) != '%') {std::cout << value;my_printf(format, args...); // 递归调用return;}std::cout << *format;}
}
这个my_printf
函数能够在编译时检查类型,避免了传统printf
的运行时错误风险。
3.4 元编程中的递归展开
可变模板参数在C++元编程中非常有用。例如,我们可以使用它来实现一个简单的元编程加法器,计算多个数值的和:
template<typename T>
T sum(T value) {return value;
}template<typename T, typename... Args>
T sum(T first, Args... rest) {return first + sum(rest...); // 递归求和
}
在这个例子中,sum
函数接受任意数量的参数,并通过递归的方式将所有参数相加。
3.5 结合lambda和可变参数
在C++14之后,我们还可以结合lambda表达式来简化对可变模板参数的操作。比如:
template<typename... Args>
void call_on_each(Args&&... args) {auto print = [](const auto& value) {std::cout << value << std::endl;};(print(std::forward<Args>(args)), ...); // 使用折叠表达式
}
这里使用了C++17中的折叠表达式,简化了对参数包的递归展开。call_on_each
可以对每个参数执行相同的操作。
4. 参数包的展开方式
在C++11及之后,有几种不同方式可以展开参数包。最常见的方式包括递归调用和折叠表达式。
4.1 递归调用
递归调用是最早的参数包展开方法。每次递归都会处理一个参数,并将剩下的参数传递给下一个递归调用。
template<typename T, typename... Args>
void recursive_func(T first, Args... rest) {std::cout << first << std::endl;if constexpr (sizeof...(rest) > 0) {recursive_func(rest...); // 递归调用}
}
这里我们使用了C++17中的if constexpr
,确保只有在参数包非空时才继续递归。
4.2 折叠表达式
C++17引入了折叠表达式,使得处理参数包更加简洁直观。折叠表达式是通过特定运算符展开参数包的一种新方式。
template<typename... Args>
void fold_func(Args... args) {(std::cout << ... << args) << std::endl; // 左折叠
}
在这个例子中,std::cout << ... << args
是一个左折叠表达式,它会展开为多个std::cout
输出操作。
4.3 初始化列表展开
另一种常见的展开参数包的方法是使用初始化列表:
template<typename... Args>
void init_list_func(Args... args) {(void)std::initializer_list<int>{(std::cout << args << std::endl, 0)...};
}
通过利用初始化列表,我们可以以更简洁的方式展开参数包,并应用某些操作,比如输出。
5. 实际应用中的性能与优化
尽管可变模板参数带来了极大的灵活性,但在实际应用中,我们仍然需要考虑其性能开销。
5.1 编译时优化
C++编译器在处理可变模板参数时,通常会进行大量的优化。例如,当展开参数包时,编译器可以通过内联展开的方式消除不必要的函数调用开销。因此,正确使用可变模板参数并不会带来明显的性能损失。
template<typename... Args>
void optimized_func(Args... args) {(std::cout << args << std::endl, ...);
}
在这个例子中,由于所有操作都是在编译时完成的,因此运行时几乎没有额外的开销。
5.2 避免递归的尾调用优化
在递归展开参数包时,确保递归函数使用尾调用优化(Tail Call Optimization,TCO)是提升性能的一个重要手段。通过设计函数,使其在递归调用时不依赖栈帧,可以有效地减少递归深度,避免栈溢出。
6. 深入分析与常见问题
6.1 参数包大小为0的情况
当传递的参数包大小为0时,如何处理是一个需要特别注意的问题。例如,如果我们设计了一个递归函数来展开参数包,我们需要考虑到递归的基准情况。
template<typename... Args>
void handle_empty() {if constexpr (sizeof...(Args) == 0) {std::cout << "No arguments provided!" << std::endl;} else {// 处理其他情况}
}
6.2 完美转发与参数包
当传递参数包时,结合完美转发可以避免不必要的拷贝和对象创建。使用std::forward
来确保参数的类型和值类别保持一致。
template<typename... Args>
void forward_func(Args&&... args) {process(std::forward<Args>(args)...); // 完美转发
}
完美转发保证了在展开参数包时,所有参数都以最优的方式传递,避免了潜在的性能损失。
7. 总结
C++的可变模板参数提供了一种处理任意数量和类型参数的简洁方式。通过理解参数包的展开方式、递归调用、折叠表达式等技巧,我们可以编写更加灵活和高效的代码。在实际项目中,结合SFINAE、完美转发等高级技巧,还可以进一步提升代码的性能和类型安全性。
希望本文帮助你对C++可变模板参数有更深的理解,能够在未来的项目中灵活运用这一强大的工具。
相关文章:

【C++11】可变模板参数详解
个人主页:chian-ocean 文章专栏 C 可变模板参数详解 1. 引言 C模板是现代C编程中一个非常强大且灵活的工具。在C11标准中,引入了可变模板参数(variadic templates),它为模板编程带来了革命性改变。它的出现允许我们…...

本地群晖NAS安装phpMyAdmin管理MySQ数据库实战指南
文章目录 前言1. 安装MySQL2. 安装phpMyAdmin3. 修改User表4. 本地测试连接MySQL5. 安装cpolar内网穿透6. 配置MySQL公网访问地址7. 配置MySQL固定公网地址8. 配置phpMyAdmin公网地址9. 配置phpmyadmin固定公网地址 前言 本文主要介绍如何在群晖NAS安装MySQL与数据库管理软件p…...

QTableWidget 接口详情
Qt Widgets->C Classes->QTableWidget Qt 5.12版本QTableWidget接口详情(机翻) QTableWidget类提供了一个带有默认模型的基于项的表视图。 属性 列数columnCount : int 行数rowCount : int 细节描述 QTableWidget类提供了一个带有默认模型的基…...

GESP CCF python四级编程等级考试认证真题 2024年9月
一、单选题(每题 2 分,共 30 分) 第 1 题 据有关资料,山东大学于1972年研制成功DJL-1计算机,并于1973年投入运行,其综合性能居当时全国第三位。DJL-1计算机运算控制部分所使用的磁心存储元件由磁心颗粒组成…...

oracle数据库名实例名服务名
Oracle数据库是一个复杂的系统,它包含多个组件,包括数据库服务器、实例和服务。 数据库名(DB_NAME):这是数据库的内部名称,通常在创建数据库时指定,并在整个数据库生命周期内保持不变。 实例名…...

python+appium+雷电模拟器安卓自动化及踩坑
一、环境安装 环境:window11 1.1 安装Android SDK AndroidDevTools - Android开发工具 Android SDK下载 Android Studio下载 Gradle下载 SDK Tools下载 这里面任选一个就可以,最终下载完主要要安装操作安卓的工具adb,安装这个步骤的前提是要…...

Python第七八次作业
1.输入一个大于0的正整数n,如果n 1 ,则返回1, 如果n是偶数,则返回 n // 2 ,如果n是奇数,则返回 3n 1,将所有的返回值存放到一个列表中,注意:n是第一个元素,其他的元素根…...

Leetcode——数组:螺旋矩阵59.螺旋矩阵
题目 思路 对于每层,从左上方开始以顺时针的顺序填入所有元素。假设当前层的左上角位于 (top,left),右下角位于 (bottom,right),按照如下顺序填入当前层的元素。 从左到右填入上侧元素,依次为 (top,left) 到 (top,right)。 从上到…...

C++类与对象-继承和多态(超全整理)
前言 前面讲类与对象上中下时,所讲的都是在单个类中相关的语法(初始化列表、this指针、静态成员、常函数和常对象......)或者使两个不同的类产生联系的语法(友元)。而本文虽然也是类与对象的内容,但和之前的…...

3.3 Thymeleaf语法
文章目录 引言Thymeleaf标签显示标签链接地址标签条件判断标签元素遍历标签 Thymeleaf表达式变量表达式选择变量表达式消息表达式链接表达式 Thymeleaf内置对象上下文对象上下文变量上下文区域请求对象响应对象会话对象日期对象 实战演练创建控制器创建模板页面 结语 引言 Thy…...

使用Dlib库实现人脸检测和关键点定位
目录 前言 一、安装Dlib库 二、人脸检测 三、人脸关键点定位 前言 Dlib是一个现代化的 C 工具包,提供了一些机器学习算法和工具,特别是在面部识别和人脸关键点检测方面非常流行。它具有易于使用的 Python 接口,并被广泛应用于计算机视觉项…...

DNS隧道流量分析
DNS隧道 DNS协议又称域名系统是互联网的基础设施,只要上网就会用到,因而DNS协议是提供网络服务的重要协议,在黑客进入内网后会使用DNS、ICMP、HTTP等协议隧道隐藏通信流量。本文通过DNS隧道实验并对流量进行分析,识别DNS隧道流量…...

HCIP-HarmonyOS Application Developer 习题(十一)
(填空)1、某开发者在使用HarmonyOs的分布式力时,分布式_____能力是其他分布式能力的基础。 答案:软总线 分析:分布式软总线是手机、平板、智能穿戴、智慧屏、车机等分布式设备的通信基座,为设备之间的互联互…...

使用Ollama测试OpenAI的Swarm多智能体编排框架
Ollama https://ollama.com/ ollama run qwen2.5Install Requires Python 3.10 pip install githttps://github.com/openai/swarm.git代码V1 # 导入Swarm和Agent类 from swarm import Swarm, Agent from openai import OpenAI # 实例化Swarm客户端 openai_client OpenAI…...

C# 完美操作 Active Directory 详细总结,轻松玩转域管理
前言 嗨,大家好! 在这个数据信息飞速发展的 21 世纪,数据安全成为了每个企业关注的焦点,保护企业数据安全日益成为企业工作中的重中之重。 域服务器,尤其是微软的 Active Directory(AD)&…...

PCL 点云配准 KD-ICP算法(精配准)
目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 加载点云函数 2.1.2 构建KD树函数 2.1.3 KD-ICP配准函数 2.1.4 点云可视化函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接: PCL点云算法…...

uniapp打包安卓apk步骤
然后安装在手机上就可以啦...

Springboot 整合 Java DL4J 实现安防监控系统
🧑 博主简介:历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,…...

【数据结构与算法】第1课—算法复杂度
文章目录 1. 数据结构2. 算法3. 算法效率4. 算法复杂度5. 算法时间复杂度5.1 大O的渐进表示法5.2 时间复杂度示例 6. 空间复杂度6.1 练习16.2 练习26.3 练习3 1. 数据结构 数据结构是计算机存储、组织数据的方式,指相互之间存在一种和多种特定关系的数据元素的集合&…...

利用高德API获取整个城市的公交路线并可视化(五)
如果说我比别人看得更远些,那是因为我站在了巨人的肩上。——牛顿 参考:使用高德API获取公交线路数据,无需代码_实时公交api-CSDN博客 记录于2024年10月,因数据获取受网站更新策略等影响可能会失效,故记录写作时间,同时拾人牙慧,优化了后半部分数据直接导出为csv和shp…...

DNS:互联网域名系统的核心
什么是 DNS? DNS(Domain Name System,域名系统)是互联网的一项基础服务,它负责将人类容易记忆的域名(如 www.example.com)转换成计算机可以识别的 IP 地址(如 192.0.2.1)…...

小猿口算炸鱼脚本
目录 写在前面: 一、关于小猿口算: 二、代码逻辑 1.数字识别 2.答题部分 三、代码分享: 补充:软件包下载 写在前面: 最近小猿口算已经被不少大学生攻占,小学生直呼有挂。原本是以为大学生都打着本…...

浅谈云原生--微服务、CICD、Serverless、服务网格
往期推荐 浅学React和JSX-CSDN博客 一文搞懂大数据流式计算引擎Flink【万字详解,史上最全】-CSDN博客 一文入门大数据准流式计算引擎Spark【万字详解,全网最新】_大数据 spark-CSDN博客 目录 1. 云原生概念和特点 2. 常见云模式 3. 云对外提供服务的…...

android app执行shell命令视频课程补充android 10/11适配-千里马android
(https://blog.csdn.net/learnframework/article/details/120103471) https://blog.csdn.net/learnframework/article/details/120103471 hi,有学员在学习跨进程通信专题课程时候,在实战app执行一个shell命令的项目时候,对课程本身的android …...

C++笔记-UTF8和UTF8-dom的区别
在文件格式上,UTF-8 和 UTF-8-BOM 是两种不同的编码方式,其中 UTF-8-BOM 包含字节顺序标记(BOM),而 UTF-8 则不包含。 UTF-8: UTF-8 是一种以字节为单位的可变长度字符编码,常用于以字节为单位…...

“探索Adobe Photoshop 2024:订阅方案、成本效益分析及在线替代品“
设计师们对Adobe Photoshop这款业界领先的图像编辑软件肯定不会陌生。如果你正考虑加入Photoshop的用户行列,可能会对其价格感到好奇。Photoshop的价值在于其强大的功能,而它的价格也反映了这一点。下面,我们就来详细了解一下Adobe Photoshop…...

网页复制粘贴助手,Chrome网页复制插件(谷歌浏览器复制插件)
一款解决网页限制复制问题的插件,当你遇到限制复制粘贴和右键的网页是不是很头痛?安装这个插件后,点下插件按钮就能解决了 碰到这种情况 也是非常头疼 chrome拓展-chrome插件-强制复制 当我们浏览网页的时候,看到感兴趣的内容就…...

【C++刷题】力扣-#118-杨辉三角
题目描述 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。在杨辉三角中,每个数是它正上方两个数的和。 示例 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]]题解 这个问题…...

Linux下的环境变量
目录 1.引言 1.1bash的部分工作 1.2main函数也有参数 1.3我们可以通过给main函数传入不同的参数,让同一份代码实现不同的功能 1.4先认识一个环境变量PATH,帮助Linux找到指令程序的地址 2.环境变量 2.1环境变量的概念 2.2见见其他的环境变量 2…...

Edge论文的创新点
创新点及其来源 1. 从灰度边缘重建RGB图像的方法(EdgRec) 基于的方法:传统的重建方法,如使用自动编码器或生成模型来重建正常样本的图像,并通过对原始图像和重建图像的比较来检测异常。 重建过程: 训练阶…...