C++模板参数包展开 递归与折叠表达式

C++17的折叠表达式革新了模板参数包处理,相比C++17前依赖递归展开的繁琐方式,折叠表达式以更简洁、高效的语法直接对参数包进行聚合操作,显著提升代码可读性和编译效率。

c++模板参数包展开 递归与折叠表达式

C++模板参数包展开,说白了,就是让你能写出接受任意数量、任意类型参数的函数或类。这在泛型编程里简直是利器。在C++17之前,我们处理这种“可变参数”的模板时,基本都得靠递归。你得写一个处理单个参数的“基线”模板,再写一个处理参数包的递归模板,每次剥离一个参数,直到只剩一个。而C++17引入的折叠表达式(Fold Expressions),则像一道闪电,直接把很多原本需要递归才能完成的操作,用一行简洁的代码就搞定了,效率和可读性都提升了一大截。

解决方案

处理C++模板参数包的核心在于如何“遍历”或“应用”包里的每一个元素。

传统递归展开:在C++17之前,这是最常见的做法。基本思路是定义一个处理“空”参数包或者单个参数的基线函数(或类模板),然后定义一个处理非空参数包的递归函数。每次递归调用时,剥离参数包的第一个元素,将剩余的参数包传递给下一次递归。

#include #include // 基线函数:处理空参数包,终止递归void print_args() {    std::cout << "--- End of args ---" << std::endl;}// 递归函数:处理参数包templatevoid print_args(T head, Args... rest) {    std::cout << head << " "; // 处理当前参数    print_args(rest...);      // 递归调用处理剩余参数}// 另一个例子:计算和long long sum_all() {    return 0;}templatelong long sum_all(T head, Args... rest) {    return static_cast(head) + sum_all(rest...);}

这种模式虽然有效,但写起来有点啰嗦,尤其是一些简单的操作,比如求和、打印,都需要写一个基线和一个递归函数。

C++17 折叠表达式:C++17引入的折叠表达式极大地简化了参数包的处理。它允许你直接在表达式中使用省略号

...

,将二元运算符(或一元运算符)应用于参数包中的所有元素。

折叠表达式有四种形式:

立即学习“C++免费学习笔记(深入)”;

一元左折叠:

(... op pack)

,例如

(pack + ...)

一元右折叠:

(pack op ...)

,例如

(pack + ...)

二元左折叠:

(init op ... op pack)

,例如

(0 + ... + pack)

二元右折叠:

(pack op ... op init)

,例如

(pack + ... + 0)

我们用折叠表达式来重写上面的例子:

#include #include #include  // for std::accumulate if needed, but fold expressions are more direct// 打印所有参数 (使用逗号运算符)templatevoid print_args_fold(Args... args) {    // 逗号运算符的妙用,确保每个表达式都被求值    // (std::cout << args << " ", ...) 这是一个二元左折叠,但这里其实是展开了一系列独立的表达式    // 真正的折叠表达式,需要一个关联操作符    // 比如:((std::cout << args << " "), ...) 这种写法会编译错误    // 正确的打印方式通常是结合初始化列表或Lambda    // 更好的打印方式:    (void)((std::cout << args << " "), ...); // 确保每个表达式都被求值,且避免警告    std::cout << std::endl;}// 计算所有参数的和templateauto sum_all_fold(Args... args) {    // 这是一个二元左折叠 (0 + arg1 + arg2 + ...)    return (0 + ... + args);}// 逻辑与templatebool all_true(Bools... b) {    return (true && ... && b); // 二元右折叠}// 逻辑或templatebool any_true(Bools... b) {    return (false || ... || b); // 二元右折叠}

折叠表达式明显更简洁,也更符合现代C++的风格。编译器在处理折叠表达式时,通常也能生成更优化的代码,因为它不需要像递归那样层层实例化模板。

为什么在C++17之前,递归是处理参数包的“必经之路”?

说实话,在C++17之前,如果你想让一个函数或者一个类模板能处理不定数量的参数,递归几乎是唯一的、也是最直接的办法。这其实跟参数包的本质有关:它不是一个容器,你不能像遍历

std::vector

那样用

for

循环去迭代它。参数包本质上是一系列独立的、类型可能各异的参数的集合。

想象一下,编译器在处理模板时,它需要知道每个参数的具体类型和值(如果能确定的话)。当它遇到一个参数包

Args...

时,它并不知道这个包里有多少个参数,也不知道它们的类型。递归展开提供了一种机制,让编译器可以“逐步”地处理这些参数。

举个例子,你有一个

print_args(arg1, arg2, arg3)

的调用。当编译器看到

template void print_args(T head, Args... rest)

这个模板定义时,它会:

arg1

匹配到

T head

。把

arg2, arg3

匹配到

Args... rest

。在函数体内部,

print_args(rest...)

又会触发一次新的模板实例化,这次

arg2

head

arg3

rest

。这个过程一直持续,直到

rest

为空,这时会匹配到

void print_args()

这个基线函数,从而终止递归。

这种“剥洋葱”式的处理方式,是C++模板元编程处理可变参数的经典模式。它虽然能解决问题,但缺点也很明显:

冗余代码: 总是需要一个基线函数来终止递归,这增加了代码量。可读性: 对于不熟悉模板元编程的人来说,递归模板的理解门槛较高。编译时间: 每次递归调用都会导致一次新的模板实例化,层数深了,编译时间可能会显著增加。而且,每次实例化都会在符号表中留下痕迹,可能导致最终可执行文件体积增大(尽管现代编译器在这方面做了很多优化)。

所以,在折叠表达式出现之前,尽管有这些不便,递归仍然是处理参数包的“唯一王道”,因为它提供了一种在编译时动态“解包”参数的有效机制。

C++17的折叠表达式如何革新了参数包处理?

C++17的折叠表达式,在我看来,简直是参数包处理领域的一次“语法糖革命”,但它的影响力远超简单的语法糖。它通过引入一种全新的、更直接的语法,让编译器能够以更高效的方式处理参数包。

核心在于,折叠表达式允许你直接在表达式内部对参数包进行“聚合”操作。不再需要显式的递归调用和基线函数。编译器知道如何将

(init op ... op pack)

(pack op ... op init)

这样的表达式直接展开成一系列连续的操作。

比如,

sum_all_fold(1, 2, 3)

调用

(0 + ... + args)

,编译器会直接将其展开为

(0 + 1 + 2 + 3)

。这与递归展开的

0 + (1 + (2 + 3))

逻辑上等价,但编译器的处理路径可能完全不同,通常会更扁平、更高效。

折叠表达式的优势体现在:

极简的代码: 大幅减少了模板元编程的样板代码。一个简单的求和、逻辑运算或者打印,现在只需要一行代码就能搞定,而不再需要一个基线函数和递归函数对。增强可读性: 代码意图更加清晰。

(... + args)

比起一堆递归模板看起来更直观,一眼就能看出是在对参数包进行求和操作。潜在的编译优化: 由于编译器可以直接理解折叠表达式的意图,它有机会生成更优化的代码,减少模板实例化的深度和数量。这有助于缩短编译时间,并可能生成更紧凑的二进制代码。语义的丰富性: 它不仅仅是简单的数学运算,还可以结合逗号运算符实现序列操作(如打印),结合位运算符实现位掩码等。这让参数包的应用场景变得更加灵活和强大。

折叠表达式的引入,让C++的泛型编程能力更上一层楼,它让原本复杂、晦涩的模板元编程变得更加平易近人,也更高效。对于日常开发中需要处理可变参数的场景,折叠表达式几乎成了首选。

在实际项目中,何时选择递归,何时偏爱折叠表达式?

在实际项目中,选择递归还是折叠表达式,其实是个挺有意思的权衡问题。C++17之后,我的个人偏好是:能用折叠表达式解决的问题,就绝不考虑递归。但总有些情况,折叠表达式力所不及,或者递归能提供更清晰的解决方案。

优先选择折叠表达式的场景:

简单的聚合操作: 当你需要对参数包中的所有元素执行一个单一的、关联性的操作时,比如求和、求积、逻辑与/或、最大/最小值等。

templateauto add_all(Args... args) {    return (args + ...); // 自动推断返回类型,非常方便}

序列化或打印: 结合逗号运算符,折叠表达式可以很方便地实现参数包的逐个处理,例如打印到流中。

templatevoid print_to_console(Args... args) {    // (void) 是为了避免某些编译器对未使用表达式的警告    ((std::cout << args << " "), ...);    std::cout << std::endl;}

类型检查或断言: 比如检查所有参数是否都满足某个条件。

templateconstexpr bool all_integers() {    return (std::is_integral_v && ...);}

现代C++项目: 如果你的项目基于C++17或更高标准,并且团队成员都熟悉新特性,那么折叠表达式无疑是更现代、更简洁的选择。

仍然需要考虑递归的场景:

C++17之前的项目: 这点是硬性限制,如果项目编译器不支持C++17,那你就只能老老实实写递归了。非关联性或复杂逻辑: 有些操作不是简单的“两两合并”就能完成的。例如,你需要根据每个参数的类型或值,执行完全不同的逻辑,或者在处理过程中需要维护某种状态,而这种状态又不能简单地通过折叠表达式的初始化值来传递。例子: 假设你要实现一个自定义的

variant

访问器,根据每个参数的类型,决定调用不同的重载函数,并且可能在处理完一个参数后,根据其结果影响下一个参数的处理方式。这种情况下,递归通常能提供更精细的控制。例子: 模拟一个栈操作,每次处理一个参数,并将其“压入”或“弹出”一个结构。这种操作可能需要递归的上下文来传递中间状态。编译时调试: 有时候,递归模板的错误信息可能比折叠表达式更“直白”(虽然都挺吓人的),因为编译器会列出每次模板实例化的详细信息。这在某些极端复杂的模板元编程错误排查时,可能会提供一些额外的线索。当然,这只是很小的一个点,通常不足以成为选择递归的主要理由。某些特定场景下的可读性: 极少数情况下,如果一个递归模式已经非常成熟和被广泛理解,并且折叠表达式的等价写法会显得过于“聪明”或难以理解,那么坚持递归也未尝不可。但这很罕见,通常折叠表达式会更清晰。

总的来说,对于大多数日常的参数包处理需求,折叠表达式是首选,它带来了代码的简洁性、可读性和潜在的性能优势。只有当遇到无法用折叠表达式优雅解决的复杂逻辑,或者受限于C++标准版本时,才应该考虑回到递归的怀抱。

以上就是C++模板参数包展开 递归与折叠表达式的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1472205.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月18日 19:26:15
下一篇 2025年12月18日 19:26:24

相关推荐

  • CSS mask属性无法获取图片:为什么我的图片不见了?

    CSS mask属性无法获取图片 在使用CSS mask属性时,可能会遇到无法获取指定照片的情况。这个问题通常表现为: 网络面板中没有请求图片:尽管CSS代码中指定了图片地址,但网络面板中却找不到图片的请求记录。 问题原因: 此问题的可能原因是浏览器的兼容性问题。某些较旧版本的浏览器可能不支持CSS…

    2025年12月24日
    900
  • 为什么设置 `overflow: hidden` 会导致 `inline-block` 元素错位?

    overflow 导致 inline-block 元素错位解析 当多个 inline-block 元素并列排列时,可能会出现错位显示的问题。这通常是由于其中一个元素设置了 overflow 属性引起的。 问题现象 在不设置 overflow 属性时,元素按预期显示在同一水平线上: 不设置 overf…

    2025年12月24日 好文分享
    400
  • 网页使用本地字体:为什么 CSS 代码中明明指定了“荆南麦圆体”,页面却仍然显示“微软雅黑”?

    网页中使用本地字体 本文将解答如何将本地安装字体应用到网页中,避免使用 src 属性直接引入字体文件。 问题: 想要在网页上使用已安装的“荆南麦圆体”字体,但 css 代码中将其置于第一位的“font-family”属性,页面仍显示“微软雅黑”字体。 立即学习“前端免费学习笔记(深入)”; 答案: …

    2025年12月24日
    000
  • 为什么我的特定 DIV 在 Edge 浏览器中无法显示?

    特定 DIV 无法显示:用户代理样式表的困扰 当你在 Edge 浏览器中打开项目中的某个 div 时,却发现它无法正常显示,仔细检查样式后,发现是由用户代理样式表中的 display none 引起的。但你疑问的是,为什么会出现这样的样式表,而且只针对特定的 div? 背后的原因 用户代理样式表是由…

    2025年12月24日
    200
  • inline-block元素错位了,是为什么?

    inline-block元素错位背后的原因 inline-block元素是一种特殊类型的块级元素,它可以与其他元素行内排列。但是,在某些情况下,inline-block元素可能会出现错位显示的问题。 错位的原因 当inline-block元素设置了overflow:hidden属性时,它会影响元素的…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 为什么使用 inline-block 元素时会错位?

    inline-block 元素错位成因剖析 在使用 inline-block 元素时,可能会遇到它们错位显示的问题。如代码 demo 所示,当设置了 overflow 属性时,a 标签就会错位下沉,而未设置时却不会。 问题根源: overflow:hidden 属性影响了 inline-block …

    2025年12月24日
    000
  • 为什么我的 CSS 元素放大效果无法正常生效?

    css 设置元素放大效果的疑问解答 原提问者在尝试给元素添加 10em 字体大小和过渡效果后,未能在进入页面时看到放大效果。探究发现,原提问者将 CSS 代码直接写在页面中,导致放大效果无法触发。 解决办法如下: 将 CSS 样式写在一个单独的文件中,并使用 标签引入该样式文件。这个操作与原提问者观…

    2025年12月24日
    000
  • 为什么我的 em 和 transition 设置后元素没有放大?

    元素设置 em 和 transition 后不放大 一个 youtube 视频中展示了设置 em 和 transition 的元素在页面加载后会放大,但同样的代码在提问者电脑上没有达到预期效果。 可能原因: 问题在于 css 代码的位置。在视频中,css 被放置在单独的文件中并通过 link 标签引…

    2025年12月24日
    100
  • 为什么在父元素为inline或inline-block时,子元素设置width: 100%会出现不同的显示效果?

    width:100%在父元素为inline或inline-block下的显示问题 问题提出 当父元素为inline或inline-block时,内部元素设置width:100%会出现不同的显示效果。以代码为例: 测试内容 这是inline-block span 效果1:父元素为inline-bloc…

    2025年12月24日
    400
  • 构建模拟:从头开始的实时交易模拟器

    简介 嘿,开发社区!我很高兴分享我的业余项目 Simul8or – 一个实时日间交易模拟器,旨在为用户提供一个无风险的环境来练习交易策略。该项目 100% 构建在 ASP.NET WebForms、C#、JavaScript、CSS 和 SQL Server 技术堆栈上,没有外部库或框架。从头开始构…

    2025年12月24日
    300
  • 什么是功能类优先的 CSS 框架?

    理解功能类优先 tailwind css 是一款功能类优先的 css 框架,用户可以通过组合功能类轻松构建设计。为了理解功能类优先,我们首先要区分语义类和功能类这两种 css 类名命名方式。 语义类 以前比较常见的 css 命名方式是根据页面中模块的功能来命名。例如: 立即学习“前端免费学习笔记(深…

    2025年12月24日
    000
  • SCSS – 增强您的 CSS 工作流程

    在本文中,我们将探索 scss (sassy css),这是一个 css 预处理器,它通过允许变量、嵌套规则、mixins、函数等来扩展 css 的功能。 scss 使 css 的编写和维护变得更加容易,尤其是对于大型项目。 1.什么是scss? scss 是 sass(syntropically …

    2025年12月24日
    000
  • css3选择器优化技巧

    CSS3 选择器优化技巧可提升网页性能:减少选择器层级,提高浏览器解析效率。避免通配符选择器,减少性能损耗。优先使用 ID 选择器,快速定位目标元素。用类选择器代替标签选择器,精确匹配。使用属性选择器,增强匹配精度。巧用伪类和伪元素,提升性能。组合多个选择器,简化代码。利用 CSS 预处理器,增强代…

    2025年12月24日
    300
  • 花 $o 学习这些编程语言或免费

    → Python → JavaScript → Java → C# → 红宝石 → 斯威夫特 → 科特林 → C++ → PHP → 出发 → R → 打字稿 []https://x.com/e_opore/status/1811567830594388315?t=_j4nncuiy2wfbm7ic…

    2025年12月24日
    000
  • css代码规范有哪些

    CSS 代码规范对于保持一致性、可读性和可维护性至关重要,常见的规范包括:命名约定:使用小写字母和短划线,命名特定且描述性。缩进和对齐:按特定规则缩进、对齐选择器、声明和值。属性和值顺序:遵循特定顺序排列属性和值。注释:解释复杂代码,并使用正确的语法。分号:每个声明后添加分号。大括号:左大括号前换行…

    2025年12月24日
    200
  • css和c的区别是什么

    区别是:1、C语言是一门面向过程、抽象化的通用程序设计语言、计算机编程语言,广泛应用于底层开发;2、CSS是一种用来表现HTML或XML等文件样式的计算机语言,可以做到网页和内容进行分离的一种样式语言。 本教程操作环境:windows7系统、CSS3&&HTML5版、Dell G3电…

    2025年12月24日
    000
  • HTML5怎么制作广告_HTML5用动画与交互制横幅或弹窗广告吸引点击【制作】

    可利用HTML5结合CSS3动画、Canvas、Web Animations API、Intersection Observer和video标签制作互动广告:一用@keyframes实现横幅入场动画;二用Canvas绘制并响应悬停;三用Web Animations API控制弹窗时序;四用Inter…

    2025年12月23日
    000
  • html5怎么找颜色_html5用取色器或CSS命名如red快速找对应颜色【查找】

    可通过浏览器开发者工具取色、CSS命名颜色对照表、在线十六进制颜色查找工具及CSS自定义属性验证四种方法快速定位颜色值对应的实际色彩效果。 如果您在HTML5开发中需要快速定位某个颜色值对应的实际色彩效果,可以通过取色器工具或CSS预定义颜色名称来识别。以下是查找颜色的具体操作方法: 一、使用浏览器…

    2025年12月23日
    000
  • html5能否插入xml文档_html5xml嵌入与节点解析展示【攻略】

    需用JavaScript加载解析XML:一、XMLHttpRequest异步获取并解析;二、DOMParser解析内联XML字符串;三、fetch API配合DOMParser处理;四、XMLSerializer序列化调试;五、getElementsByTagNameNS处理命名空间。 如果您希望在…

    2025年12月23日
    200

发表回复

登录后才能评论
关注微信