尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C++模板教程:变参模板、折叠表达式与SFINAE
本文是 C 系列教程的第 18 篇。上一篇讲解了特化与类型萃取本篇深入模板高级技巧变参模板参数包、sizeof…、递归展开、C17 折叠表达式、SFINAE 与 enable_if、void_t 技巧、C20 concepts 预告。一、变参模板1.1 什么是变参模板变参模板Variadic Templates允许模板接受任意数量的参数C11 引入// 参数包typename... Argstemplatetypename...Argsvoidprint(Args...args);Args...是类型参数包。args...是函数参数包。sizeof...(Args)获取参数个数。1.2 参数包展开与递归#includeiostreamusingnamespacestd;// 递归终止条件空参数版本voidprintAll(){coutendl;}// 递归展开取出第一个参数剩余继续递归templatetypenameT,typename...RestvoidprintAll(T first,Rest...rest){coutfirst ;printAll(rest...);// 递归调用剩余参数}intmain(){printAll(1,2.5,hello,a);// 1 2.5 hello aprintAll(10);// 10return0;}1.3 sizeof… 获取参数个数#includeiostreamusingnamespacestd;templatetypename...ArgsvoidcountArgs(Args...args){cout参数个数: sizeof...(Args)endl;cout参数个数: sizeof...(args)endl;// 等价}intmain(){countArgs();// 0countArgs(1);// 1countArgs(1,2.5,three);// 3return0;}二、折叠表达式C172.1 折叠表达式语法折叠表达式对参数包中的全部元素应用二元运算符大大简化变参运算形式含义(pack op ...)右折叠a op (b op (c op init))(... op pack)左折叠((init op a) op b) op c(pack op ... op init)带初始值的右折叠(init op ... op pack)带初始值的左折叠2.2 折叠求和#includeiostreamusingnamespacestd;// C17 折叠一行实现任意个数求和templatetypename...Argsautosum(Args...args){return(args...0);// 右折叠初始值 0}// 左折叠templatetypename...ArgsautosumLeft(Args...args){return(0...args);}intmain(){coutsum(1,2,3,4,5)end l;// 15coutsum(1.5,2.5,3.0)endl;// 7coutsum()endl;// 0coutsumLeft(1,2,3)endl;// 6return0;}2.3 折叠打印对比递归#includeiostreamusingnamespacestd;// 用逗号运算符折叠打印templatetypename...ArgsvoidprintFold(Args...args){// (cout ... args)左折叠// 逐个输出((coutargs ),...);// 逗号折叠coutendl;}// 更实用的版本带分隔符templatetypename...ArgsvoidprintWithSep(constchar*sep,Args...args){// 第一个直接输出其余带分隔符((coutargs),...);// 简化版coutendl;}intmain(){printFold(1,2.5,hi,x);// 1 2.5 hi xprintWithSep(, ,1,2,3);return0;}2.4 折叠判断逻辑运算#includeiostreamusingnamespacestd;// 全部满足条件 折叠templatetypename...ArgsboolallPositive(Args...args){return((args0)...);}// 任一满足条件|| 折叠templatetypename...ArgsboolanyZero(Args...args){return((args0)||...);}intmain(){coutallPositive(1,2,3)endl;// 1coutallPositive(1,-2,3)endl;// 0coutanyZero(1,2,0,4)endl;// 1coutanyZero(1,2,3)endl;// 0return0;}三、SFINAE 基础3.1 什么是 SFINAESFINAESubstitution Failure Is Not An Error替换失败不是错误模板实例化时如果某个候选替换失败如类型不支持某操作编译器不会报错而是继续尝试其他候选。3.2 利用 SFINAE 做类型判断#includeiostream#includetype_traitsusingnamespacestd;// 通用版本不支持 operator 的类型走这里templatetypenameTvoidprintValue(constTvalue,...){cout不支持输出: (未知类型)endl;}// 精确版本支持 operator 的类型走这里templatetypenameT,typenamedecltype(coutdeclvalconstT())voidprintValue(constTvalue,int){cout值: valueendl;}intmain(){printValue(42,0);// 值: 42printValue(3.14,0);// 值: 3.14printValue(hello,0);// 值: helloreturn0;}3.3 enable_if 条件启用#includeiostream#includetype_traitsusingnamespacestd;// 仅当 T 是整数时启用templatetypenameTtypenameenable_ifis_integralT::value::typeprocess(T value){cout整数处理: valueendl;}// 仅当 T 是浮点时启用templatetypenameTtypenameenable_ifis_floating_pointT::value::typeprocess(T value){cout浮点处理: valueendl;}intmain(){process(42);// 整数处理: 42process(3.14);// 浮点处理: 3.14// process(hi); // 错误string 不满足任何版本return0;}3.4 enable_if 的两种写法#includeiostream#includetype_traitsusingnamespacestd;// 写法一返回类型中启用templatetypenameTtypenameenable_ifis_integralT::value,T::typesquare(T x){returnx*x;}// 写法二模板参数中启用C11 更通用templatetypenameT,typenametypenameenable_ifis_floating_pointT::value::typedoublesquareDouble(T x){returnx*x;}intmain(){coutsquare(5)endl;// 25int 版本coutsquareDouble(2.5)endl;// 6.25double 版本return0;}四、void_t 技巧4.1 void_t 检测特性#includeiostream#includetype_traitsusingnamespacestd;// void_t任何类型都映射为 voidC17 标准库提供templatetypename...usingvoid_tvoid;// 检测 T 是否有成员函数 size()templatetypenameT,typenamevoidstructHasSize:false_type{};templatetypenameTstructHasSizeT,void_tdecltype(declvalT().size()):true_type{};// 检测 T 是否有成员 typetemplatetypenameT,typenamevoidstructHasType:false_type{};templatetypenameTstructHasTypeT,void_ttypenameT::type:true_type{};intmain(){coutstring 有 size(): HasSizestring::valueendl;// 1coutint 有 size(): HasSizeint::valueendl;// 0structWithType{usingtypeint;};coutWithType 有 type: HasTypeWithType::valueendl;// 1coutint 有 type: HasTypeint::valueendl;// 0return0;}4.2 void_t 检测可调用性#includeiostream#includetype_traitsusingnamespacestd;templatetypename...usingvoid_tvoid;// 检测 T 是否支持 operatortemplatetypenameT,typenamevoidstructIsPrintable:false_type{};templatetypenameTstructIsPrintableT,void_tdecltype(coutdeclvalconstT()):true_type{};structMyStruct{};// 不支持输出intmain(){coutint 可打印: IsPrintableint::valueendl;// 1coutstring 可打印: IsPrintablestring::valueendl;// 1coutMyStruct 可打印: IsPrintableMyStruct::valueendl;// 0return0;}五、变参模板实战5.1 类型安全的 printf#includeiostreamusingnamespacestd;// 基础版本无参数voidmyPrintf(constchar*format){coutformatendl;}// 递归展开版本templatetypenameT,typename...ArgsvoidmyPrintf(constchar*format,T value,Args...args){while(*format){if(*format%*(format1)d){coutvalue;// 输出参数format2;myPrintf(format,args...);// 递归剩余return;}cout*format;}}intmain(){myPrintf(数字: %d,42);myPrintf(%d 加 %d 等于 %d,1,2,3);return0;}5.2 变参构造函数完美转发预览#includeiostream#includevectorusingnamespacestd;// 自定义容器支持任意参数构造templatetypenameTclassMyContainer{private:vectorTdata;public:// 变参构造函数templatetypename...ArgsMyContainer(Args...args){data.reserve(sizeof...(Args));(data.push_back(args),...);// C17 逗号折叠}voidshow()const{for(constautoitem:data)coutitem ;coutendl;}size_tsize()const{returndata.size();}};intmain(){MyContainerintc1(1,2,3,4,5);cout大小: c1.size()endl;// 5c1.show();// 1 2 3 4 5MyContainerstringc2(C,Python);c2.show();// C Pythonreturn0;}5.3 变参模板应用场景std::make_shared/make_unique参数转发给构造函数。std::tuple存储任意类型任意个数。printf 风格格式化。事件系统任意参数的通知。*工厂模式*参数转发。六、C20 concepts 预告6.1 concepts 简化约束C20 的 concepts 让模板约束更简洁直观#includeiostream#includeconceptsusingnamespacestd;// 定义概念必须是整数类型templatetypenameTconceptIntegralis_integral_vT;// 用概念约束模板参数templateIntegral TTadd(T a,T b){returnab;}// 简化写法templatetypenameTrequiresIntegralTTmultiply(T a,T b){returna*b;}intmain(){coutadd(3,4)endl;// 7coutmultiply(5,6)endl;// 30// add(3.5, 4.5); // 错误double 不满足 Integralreturn0;}6.2 concepts vs enable_if维度enable_ifconcepts可读性冗长晦涩直观清晰报错信息深奥难懂友好明确语法模板技巧标准语法标准C11C20七、实战任意类型最大值综合本篇知识实现支持任意参数个数的 max#includeiostream#includetype_traitsusingnamespacestd;// 两个参数版本递归终止templatetypenameTTmyMax(T value){returnvalue;}// 变参版本比较第一个和剩余的最大值templatetypenameT,typename...ArgsautomyMax(T first,Args...rest){autorestMaxmyMax(rest...);// 递归求剩余最大值returnfirstrestMax?first:restMax;}// 折叠表达式版本C17 更简洁templatetypename...ArgsautofoldMax(Args...args){return(args...);// 不适用}// 正确折叠版templatetypenameFirst,typename...RestautofoldMax2(First first,Rest...rest){autoresultfirst;((result(restresult?rest:result)),...);returnresult;}intmain(){coutmyMax(3,7,2)endl;// 7coutmyMax(1.5,3.2,2.8,4.1)endl;// 4.1coutfoldMax2(10,20,5,15)endl;// 20coutfoldMax2(3.5,2.5)endl;// 3.5return0;}总结本篇讲解了变参模板参数包、sizeof…、递归展开、C17 折叠表达式求和、打印、逻辑判断、SFINAE 与 enable_if条件启用、void_t 技巧特性检测、C20 concepts 预告并用任意类型最大值串联实战。重点掌握参数包递归展开模式、折叠表达式的四种形式、enable_if 的两种写法、void_t 检测原理。下一篇将讲解类型推导与完美转发auto/decltype/右值引用/完美转发敬请期待
RELATED

相关推荐

Claude Code提示词精简:上下文工程新范式

Claude Code提示词精简:上下文工程新范式

关于 Claude Code 系统提示词被大幅精简这件事,最近讨论热度很高。核心点有两个:Claude Code,以及上下文工程。这次话题让人印象最深的变化是:以前大家默认“系统提示词写得越全,模型越不容易出错”;现在方…

📅 2026/10/26 7:08:17
开发者贡献识别:如何用数据公平衡量团队真实协作价值

开发者贡献识别:如何用数据公平衡量团队真实协作价值

1. 先想清楚:它到底在解决什么协作问题Meridian 这个名字,第一眼看上去不像一个具体的功能模块,更像一个工程文化层面的工具概念。标题里那句“A better way to recognize developer contributions”,核心不是“统计代码量”&…

📅 2026/10/26 7:08:36
EBR-bench:人类基线AI评测基准的解读与复现指南

EBR-bench:人类基线AI评测基准的解读与复现指南

从项目标题看,EBR-bench 属于一类“拿人类表现当参照系”的 AI 评测基准。这类基准通常会把人类在若干任务上的得分作为基线,再把主流模型拉上来跑同一批任务,最终对比结论往往是:AI 虽然在部分子项上逼近甚至超过人类&#xff0c…

📅 2026/10/21 15:14:21
MORE NEWS

更多资讯

📰

Miniforge环境管理:Python开发高效工具指南

1. Miniforge开发环境概述Miniforge作为轻量级的conda替代方案,已经成为Python开发者的新宠。它继承了conda强大的环境管理能力,同时避免了Anaconda的商业许可问题。我在多个机器学习项目中切换使用Miniforge后,发现其启动速度和包解析效率确…

📰

Claude本地记忆系统:向量数据库+语义分块实现可控长期记忆

1. 项目概述:这不是一个工具,而是一次对AI记忆机制的深度解剖“claude-mem”这个名称一出现,很多刚接触大模型的朋友第一反应是:“这是Claude官方新出的记忆插件?还是某个第三方开发的增强版客户端?”——我…

📰

Java项目Docker镜像制作与优化实战

1. JAVA项目Docker镜像制作实战指南作为现代Java开发者必备的容器化技能,将Java应用打包为Docker镜像已成为持续交付的基础环节。我在金融和电商领域部署过上百个Java容器,发现90%的线上问题都源于镜像构建阶段的不规范操作。本文将分享经过生产验证的标…

📰

e2e测试本质:真实用户旅程的端到端验证

1. “e2e”不是缩写游戏,而是软件交付链上最锋利的那把验收刀很多人第一次在项目会议里听到“e2e”三个字母,下意识会去翻缩写词典——End-to-End?Enterprise-to-Enterprise?Even-to-Even?其实都不对。它根本不是个需要…

📰

面向对象编程:类与对象的高级特性与实战应用

1. 类和对象深度解析在编程领域,类和对象是面向对象编程(OOP)的基石概念。作为一名有十年开发经验的程序员,我发现很多初学者虽然能写出简单的类和对象,但对它们的深层特性和应用场景理解不够透彻。今天我就来分享一些在实际项目中积累的关于…

📰

SAP MM供应商寄售全流程详解:从主数据到MRKO结算避坑指南

简介:面向SAP MM模块顾问与供应链从业者,系统梳理供应商寄售流程的配置与操作要点。内容从物料主数据设置采购类型与特殊采购类别开始,依次覆盖供应商主数据创建、信息记录维护、后台激活寄售、采购申请与采购订单转换、收料、寄售库存转移至…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬