当前位置: 首页 > news >正文

C++模板教程:变参模板、折叠表达式与SFINAE

本文是 C++ 系列教程的第 18 篇。上一篇讲解了特化与类型萃取,本篇深入模板高级技巧:变参模板(参数包、sizeof…、递归展开)、C++17 折叠表达式、SFINAE 与 enable_if、void_t 技巧、C++20 concepts 预告。

一、变参模板

1.1 什么是变参模板

变参模板(Variadic Templates)允许模板接受任意数量的参数,C++11 引入:

// 参数包:typename... Argstemplate<typename...Args>voidprint(Args...args);
  • Args...类型参数包
  • args...函数参数包
  • sizeof...(Args)获取参数个数。

1.2 参数包展开与递归

#include<iostream>usingnamespacestd;// 递归终止条件:空参数版本voidprintAll(){cout<<endl;}// 递归展开:取出第一个参数,剩余继续递归template<typenameT,typename...Rest>voidprintAll(T first,Rest...rest){cout<<first<<" ";printAll(rest...);// 递归调用剩余参数}intmain(){printAll(1,2.5,"hello",'a');// 1 2.5 hello aprintAll(10);// 10return0;}

1.3 sizeof… 获取参数个数

#include<iostream>usingnamespacestd;template<typename...Args>voidcountArgs(Args...args){cout<<"参数个数: "<<sizeof...(Args)<<endl;cout<<"参数个数: "<<sizeof...(args)<<endl;// 等价}intmain(){countArgs();// 0countArgs(1);// 1countArgs(1,2.5,"three");// 3return0;}

二、折叠表达式(C++17)

2.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 折叠求和

#include<iostream>usingnamespacestd;// C++17 折叠:一行实现任意个数求和template<typename...Args>autosum(Args...args){return(args+...+0);// 右折叠,初始值 0}// 左折叠template<typename...Args>autosumLeft(Args...args){return(0+...+args);}intmain(){cout<<sum(1,2,3,4,5)<<end l;// 15cout<<sum(1.5,2.5,3.0)<<endl;// 7cout<<sum()<<endl;// 0cout<<sumLeft(1,2,3)<<endl;// 6return0;}

2.3 折叠打印(对比递归)

#include<iostream>usingnamespacestd;// 用逗号运算符折叠打印template<typename...Args>voidprintFold(Args...args){// (cout << ... << args):左折叠// 逐个输出((cout<<args<<" "),...);// 逗号折叠cout<<endl;}// 更实用的版本:带分隔符template<typename...Args>voidprintWithSep(constchar*sep,Args...args){// 第一个直接输出,其余带分隔符((cout<<args),...);// 简化版cout<<endl;}intmain(){printFold(1,2.5,"hi",'x');// 1 2.5 hi xprintWithSep(", ",1,2,3);return0;}

2.4 折叠判断(逻辑运算)

#include<iostream>usingnamespacestd;// 全部满足条件(&& 折叠)template<typename...Args>boolallPositive(Args...args){return((args>0)&&...);}// 任一满足条件(|| 折叠)template<typename...Args>boolanyZero(Args...args){return((args==0)||...);}intmain(){cout<<allPositive(1,2,3)<<endl;// 1cout<<allPositive(1,-2,3)<<endl;// 0cout<<anyZero(1,2,0,4)<<endl;// 1cout<<anyZero(1,2,3)<<endl;// 0return0;}

三、SFINAE 基础

3.1 什么是 SFINAE

SFINAE(Substitution Failure Is Not An Error,替换失败不是错误):模板实例化时,如果某个候选替换失败(如类型不支持某操作),编译器不会报错,而是继续尝试其他候选。

3.2 利用 SFINAE 做类型判断

#include<iostream>#include<type_traits>usingnamespacestd;// 通用版本:不支持 operator<< 的类型走这里template<typenameT>voidprintValue(constT&value,...){cout<<"不支持输出: (未知类型)"<<endl;}// 精确版本:支持 operator<< 的类型走这里template<typenameT,typename=decltype(cout<<declval<constT&>())>voidprintValue(constT&value,int){cout<<"值: "<<value<<endl;}intmain(){printValue(42,0);// 值: 42printValue(3.14,0);// 值: 3.14printValue("hello",0);// 值: helloreturn0;}

3.

3 enable_if 条件启用

#include<iostream>#include<type_traits>usingnamespacestd;// 仅当 T 是整数时启用template<typenameT>typenameenable_if<is_integral<T>::value>::typeprocess(T value){cout<<"整数处理: "<<value<<endl;}// 仅当 T 是浮点时启用template<typenameT>typenameenable_if<is_floating_point<T>::value>::typeprocess(T value){cout<<"浮点处理: "<<value<<endl;}intmain(){process(42);// 整数处理: 42process(3.14);// 浮点处理: 3.14// process("hi"); // 错误!string 不满足任何版本return0;}

3.4 enable_if 的两种写法

#include<iostream>#include<type_traits>usingnamespacestd;// 写法一:返回类型中启用template<typenameT>typenameenable_if<is_integral<T>::value,T>::typesquare(T x){returnx*x;}// 写法二:模板参数中启用(C++11 更通用)template<typenameT,typename=typenameenable_if<is_floating_point<T>::value>::type>doublesquareDouble(T x){returnx*x;}intmain(){cout<<square(5)<<endl;// 25(int 版本)cout<<squareDouble(2.5)<<endl;// 6.25(double 版本)return0;}

四、void_t 技巧

4.1 void_t 检测特性

#include<iostream>#include<type_traits>usingnamespacestd;// void_t:任何类型都映射为 void(C++17 标准库提供)template<typename...>usingvoid_t=void;// 检测 T 是否有成员函数 size()template<typenameT,typename=void>structHasSize:false_type{};template<typenameT>structHasSize<T,void_t<decltype(declval<T>().size())>>:true_type{};// 检测 T 是否有成员 typetemplate<typenameT,typename=void>structHasType:false_type{};template<typenameT>structHasType<T,void_t<typenameT::type>>:true_type{};intmain(){cout<<"string 有 size(): "<<HasSize<string>::value<<endl;// 1cout<<"int 有 size(): "<<HasSize<int>::value<<endl;// 0structWithType{usingtype=int;};cout<<"WithType 有 type: "<<HasType<WithType>::value<<endl;// 1cout<<"int 有 type: "<<HasType<int>::value<<endl;// 0return0;}

4.2 void_t 检测可调用性

#include<iostream>#include<type_traits>usingnamespacestd;template<typename...>usingvoid_t=void;// 检测 T 是否支持 operator<<template<typenameT,typename=void>structIsPrintable:false_type{};template<typenameT>structIsPrintable<T,void_t<decltype(cout<<declval<constT&>())>>:true_type{};structMyStruct{};// 不支持输出intmain(){cout<<"int 可打印: "<<IsPrintable<int>::value<<endl;// 1cout<<"string 可打印: "<<IsPrintable<string>::value<<endl;// 1cout<<"MyStruct 可打印: "<<IsPrintable<MyStruct>::value<<endl;// 0return0;}

五、变参模板实战

5.1 类型安全的 printf

#include<iostream>usingnamespacestd;// 基础版本:无参数voidmyPrintf(constchar*format){cout<<format<<endl;}// 递归展开版本template<typenameT,typename...Args>voidmyPrintf(constchar*format,T value,Args...args){while(*format){if(*format=='%'&&*(format+1)=='d'){cout<<value;// 输出参数format+=2;myPrintf(format,args...);// 递归剩余return;}cout<<*format++;}}intmain(){myPrintf("数字: %d",42);myPrintf("%d 加 %d 等于 %d",1,2,3);return0;}

5.2 变参构造函数(完美转发预览)

#include<iostream>#include<vector>usingnamespacestd;// 自定义容器:支持任意参数构造template<typenameT>classMyContainer{private:vector<T>data;public:// 变参构造函数template<typename...Args>MyContainer(Args...args){data.reserve(sizeof...(Args));(data.push_back(args),...);// C++17 逗号折叠}voidshow()const{for(constauto&item:data)cout<<item<<" ";cout<<endl;}size_tsize()const{returndata.size();}};intmain(){MyContainer<int>c1(1,2,3,4,5);cout<<"大小: "<<c1.size()<<endl;// 5c1.show();// 1 2 3 4 5MyContainer<string>c2("C++","Python");c2.show();// C++ Pythonreturn0;}

5.3 变参模板应用场景

  1. std::make_shared/make_unique:参数转发给构造函数。
  2. std::tuple:存储任意类型任意个数。
  3. printf 风格格式化
  4. 事件系统:任意参数的通知。
  5. *工厂模式
    *:参数转发。

六、C++20 concepts 预告

6.1 concepts 简化约束

C++20 的 concepts 让模板约束更简洁直观:

#include<iostream>#include<concepts>usingnamespacestd;// 定义概念:必须是整数类型template<typenameT>conceptIntegral=is_integral_v<T>;// 用概念约束模板参数template<Integral T>Tadd(T a,T b){returna+b;}// 简化写法template<typenameT>requiresIntegral<T>Tmultiply(T a,T b){returna*b;}intmain(){cout<<add(3,4)<<endl;// 7cout<<multiply(5,6)<<endl;// 30// add(3.5, 4.5); // 错误!double 不满足 Integralreturn0;}

6.2 concepts vs enable_if

维度enable_ifconcepts
可读性冗长晦涩直观清晰
报错信息深奥难懂友好明确
语法模板技巧标准语法
标准C++11C++20

七、实战:任意类型最大值

综合本篇知识,实现支持任意参数个数的 max:

#include<iostream>#include<type_traits>usingnamespacestd;// 两个参数版本(递归终止)template<typenameT>TmyMax(T value){returnvalue;}// 变参版本:比较第一个和剩余的最大值template<typenameT,typename...Args>automyMax(T first,Args...rest){autorestMax=myMax(rest...);// 递归求剩余最大值returnfirst>restMax?first:restMax;}// 折叠表达式版本(C++17 更简洁)template<typename...Args>autofoldMax(Args...args){return(args>...);// 不适用}// 正确折叠版template<typenameFirst,typename...Rest>autofoldMax2(First first,Rest...rest){autoresult=first;((result=(rest>result?rest:result)),...);returnresult;}intmain(){cout<<myMax(3,7,2)<<endl;// 7cout<<myMax(1.5,3.2,2.8,4.1)<<endl;// 4.1cout<<foldMax2(10,20,5,15)<<endl;// 20cout<<foldMax2(3.5,2.5)<<endl;// 3.5return0;}

总结

本篇讲解了变参模板(参数包、sizeof…、递归展开)、C++17 折叠表达式(求和、打印、逻辑判断)、SFINAE 与 enable_if(条件启用)、void_t 技巧(特性检测)、C++20 concepts 预告,并用任意类型最大值串联实战。重点掌握:参数包递归展开模式、折叠表达式的四种形式、enable_if 的两种写法、void_t 检测原理。

下一篇将讲解类型推导与完美转发(auto/decltype/右值引用/完美转发),敬请期待!

http://www.cnnetsun.cn/news/4322719.html

相关文章:

  • langchain入门基础
  • RAG Refresher Notebook:Jupyter 中从零跑通 RAG 实战全链路
  • Minecraft Overlay机制与末地通关测试全解析
  • 基于MATLAB的AGV视觉导航与二维码控制系统解析
  • Spring Security 实战指南:认证授权与过滤器链解析
  • Java开发者LLM应用实战:Spring AI、LangChain4j与RAG Agent路线
  • 基于TVA-World架构的具身智能协同机制研究
  • PicoPro Glitch演示与IDM一键下载集成实战指南
  • java复习笔记
  • HarmonyOS 鸿蒙负一屏场景入口与服务推荐
  • 网约车租车还是买车?用成本模型和计算器算出盈亏平衡点
  • 基于SpringBoot的龙云优选便利店销售管理系统毕业设计项目源码文档
  • 途虎养车数据分析笔试解析:SQL、Python与业务案例全攻略
  • 300W国产DC-DC升压方案:从拓扑选型到PCB布局的完整实践指南
  • 018-参考资料
  • 工业检测机器人软件中间层:打破数据孤岛的统一平台
  • 非常棒的推理项目FreeToken,据说非常快!
  • STM32H757驱动MIPI DSI竖屏:LVGL V9移植与动画实战
  • Claude Code Token不够用?六个实用技巧省下近一半成本
  • 智能体轨迹压缩成自动机:行为分析的新思路
  • Arduino IDE板级包路径配置与ESP32/ESP8266环境搭建实战
  • conda环境管理实战:从创建环境到Jupyter运行NumPy
  • 原生影视APP源码拆解:播放器内核与运营功能全解析
  • 多Agent协作实战:Hermes与DeepSeek Harness从配置到排错
  • TensorFlow vs PyTorch:深度学习框架选型与实战指南
  • 绿联DH4300 Plus评测:四盘位8G内存+NFC一碰连接的家庭私有云
  • 真人跑团综艺制作全流程:从TRPG规则到角色卡与发音统一
  • MATLAB极限学习机ELM多特征分类预测完整实战代码
  • 2025款马自达EZ-6澳洲全面测试:传统车企的电动化答卷
  • linux之域套接字