C++模板教程:变参模板、折叠表达式与SFINAE
本文摘要
变参模板(Variadic Templates)允许模板接受任意数量的参数// 参数包:typename... ArgsArgs...是类型参数包。args...是函数参数包。获取参数个数…
本文是 C++ 系列教程的第 18 篇。上一篇讲解了特化与类型萃取,本篇深入模板高级技巧:变参模板(参数包、sizeof…、递归展开)、C++17 折叠表达式、SFINAE 与 enable_if、void_t 技巧、C++20 concepts 预告。
一、变参模板
1.1 什么是变参模板
变参模板(Variadic Templates)允许模板接受任意数量的参数,C++11 引入:
// 参数包:typename... Argstemplate <typename... Args>void print(Args... args);Args...是类型参数包。args...是函数参数包。sizeof...(Args)获取参数个数。
1.2 参数包展开与递归
#include <iostream>using namespace std;
// 递归终止条件:空参数版本void printAll() { cout << endl;}
// 递归展开:取出第一个参数,剩余继续递归template <typename T, typename... Rest>void printAll(T first, Rest... rest) { cout << first << " "; printAll(rest...); // 递归调用剩余参数}
int main() { printAll(1, 2.5, "hello", 'a'); // 1 2.5 hello a printAll(10); // 10 return 0;}1.3 sizeof… 获取参数个数
#include <iostream>using namespace std;
template <typename... Args>void countArgs(Args... args) { cout << "参数个数: " << sizeof...(Args) << endl; cout << "参数个数: " << sizeof...(args) << endl; // 等价}
int main() { countArgs(); // 0 countArgs(1); // 1 countArgs(1, 2.5, "three"); // 3 return 0;}二、折叠表达式(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>using namespace std;
// C++17 折叠:一行实现任意个数求和template <typename... Args>auto sum(Args... args) { return (args + ... + 0); // 右折叠,初始值 0}
// 左折叠template <typename... Args>auto sumLeft(Args... args) { return (0 + ... + args);}
int main() { cout << sum(1, 2, 3, 4, 5) << endl; // 15 cout << sum(1.5, 2.5, 3.0) << endl; // 7 cout << sum() << endl; // 0 cout << sumLeft(1, 2, 3) << endl; // 6 return 0;}2.3 折叠打印(对比递归)
#include <iostream>using namespace std;
// 用逗号运算符折叠打印template <typename... Args>void printFold(Args... args) { // (cout << ... << args):左折叠 // 逐个输出 ((cout << args << " "), ...); // 逗号折叠 cout << endl;}
// 更实用的版本:带分隔符template <typename... Args>void printWithSep(const char *sep, Args... args) { // 第一个直接输出,其余带分隔符 ((cout << args), ...); // 简化版 cout << endl;}
int main() { printFold(1, 2.5, "hi", 'x'); // 1 2.5 hi x printWithSep(", ", 1, 2, 3); return 0;}2.4 折叠判断(逻辑运算)
#include <iostream>using namespace std;
// 全部满足条件(&& 折叠)template <typename... Args>bool allPositive(Args... args) { return ((args > 0) && ...);}
// 任一满足条件(|| 折叠)template <typename... Args>bool anyZero(Args... args) { return ((args == 0) || ...);}
int main() { cout << allPositive(1, 2, 3) << endl; // 1 cout << allPositive(1, -2, 3) << endl; // 0 cout << anyZero(1, 2, 0, 4) << endl; // 1 cout << anyZero(1, 2, 3) << endl; // 0 return 0;}三、SFINAE 基础
3.1 什么是 SFINAE
SFINAE(Substitution Failure Is Not An Error,替换失败不是错误):模板实例化时,如果某个候选替换失败(如类型不支持某操作),编译器不会报错,而是继续尝试其他候选。
3.2 利用 SFINAE 做类型判断
#include <iostream>#include <type_traits>using namespace std;
// 通用版本:不支持 operator<< 的类型走这里template <typename T>void printValue(const T &value, ...) { cout << "不支持输出: (未知类型)" << endl;}
// 精确版本:支持 operator<< 的类型走这里template <typename T, typename = decltype(cout << declval<const T &>())>void printValue(const T &value, int) { cout << "值: " << value << endl;}
int main() { printValue(42, 0); // 值: 42 printValue(3.14, 0); // 值: 3.14 printValue("hello", 0); // 值: hello return 0;}3.
3 enable_if 条件启用
#include <iostream>#include <type_traits>using namespace std;
// 仅当 T 是整数时启用template <typename T>typename enable_if<is_integral<T>::value>::typeprocess(T value) { cout << "整数处理: " << value << endl;}
// 仅当 T 是浮点时启用template <typename T>typename enable_if<is_floating_point<T>::value>::typeprocess(T value) { cout << "浮点处理: " << value << endl;}
int main() { process(42); // 整数处理: 42 process(3.14); // 浮点处理: 3.14 // process("hi"); // 错误!string 不满足任何版本 return 0;}3.4 enable_if 的两种写法
#include <iostream>#include <type_traits>using namespace std;
// 写法一:返回类型中启用template <typename T>typename enable_if<is_integral<T>::value, T>::typesquare(T x) { return x * x;}
// 写法二:模板参数中启用(C++11 更通用)template <typename T, typename = typename enable_if<is_floating_point<T>::value>::type>double squareDouble(T x) { return x * x;}
int main() { cout << square(5) << endl; // 25(int 版本) cout << squareDouble(2.5) << endl; // 6.25(double 版本) return 0;}四、void_t 技巧
4.1 void_t 检测特性
#include <iostream>#include <type_traits>using namespace std;
// void_t:任何类型都映射为 void(C++17 标准库提供)template <typename...>using void_t = void;
// 检测 T 是否有成员函数 size()template <typename T, typename = void>struct HasSize : false_type {};
template <typename T>struct HasSize<T, void_t<decltype(declval<T>().size())>> : true_type {};
// 检测 T 是否有成员 typetemplate <typename T, typename = void>struct HasType : false_type {};
template <typename T>struct HasType<T, void_t<typename T::type>> : true_type {};
int main() { cout << "string 有 size(): " << HasSize<string>::value << endl; // 1 cout << "int 有 size(): " << HasSize<int>::value << endl; // 0
struct WithType { using type = int; }; cout << "WithType 有 type: " << HasType<WithType>::value << endl; // 1 cout << "int 有 type: " << HasType<int>::value << endl; // 0 return 0;}4.2 void_t 检测可调用性
#include <iostream>#include <type_traits>using namespace std;
template <typename...>
using void_t = void;
// 检测 T 是否支持 operator<<template <typename T, typename = void>struct IsPrintable : false_type {};
template <typename T>struct IsPrintable<T, void_t<decltype(cout << declval<const T &>())>> : true_type {};
struct MyStruct {}; // 不支持输出
int main() { cout << "int 可打印: " << IsPrintable<int>::value << endl; // 1 cout << "string 可打印: " << IsPrintable<string>::value << endl; // 1 cout << "MyStruct 可打印: " << IsPrintable<MyStruct>::value << endl; // 0 return 0;}五、变参模板实战
5.1 类型安全的 printf
#include <iostream>using namespace std;
// 基础版本:无参数void myPrintf(const char *format) { cout << format << endl;}
// 递归展开版本template <typename T, typename... Args>void myPrintf(const char *format, T value, Args... args) { while (*format) { if (*format == '%' && *(format + 1) == 'd') { cout << value; // 输出参数 format += 2; myPrintf(format, args...); // 递归剩余 return; } cout << *format++; }}
int main() { myPrintf("数字: %d", 42); myPrintf("%d 加 %d 等于 %d", 1, 2, 3); return 0;}5.2 变参构造函数(完美转发预览)
#include <iostream>#include <vector>using namespace std;
// 自定义容器:支持任意参数构造template <typename T>class MyContainer {private: vector<T> data;
public: // 变参构造函数 template <typename... Args> MyContainer(Args... args) { data.reserve(sizeof...(Args)); (data.push_back(args), ...); // C++17 逗号折叠 }
void show() const { for (const auto &item : data) cout << item << " "; cout << endl; }
size_t size() const { return data.size(); }};
int main() { MyContainer<int> c1(1, 2, 3, 4, 5); cout << "大小: " << c1.size() << endl; // 5 c1.show(); // 1 2 3 4 5
MyContainer<string> c2("C++", "Python"); c2.show(); // C++ Python return 0;}5.3 变参模板应用场景
- std::make_shared/make_unique:参数转发给构造函数。
- std::tuple:存储任意类型任意个数。
- printf 风格格式化。
- 事件系统:任意参数的通知。
- *工厂模式 *:参数转发。
六、C++20 concepts 预告
6.1 concepts 简化约束
C++20 的 concepts 让模板约束更简洁直观:
#include <iostream>#include <concepts>using namespace std;
// 定义概念:必须是整数类型template <typename T>concept Integral = is_integral_v<T>;
// 用概念约束模板参数template <Integral T>T add(T a, T b) { return a + b;}
// 简化写法template <typename T>requires Integral<T>T multiply(T a, T b) { return a * b;}
int main() { cout << add(3, 4) << endl; // 7 cout << multiply(5, 6) << endl; // 30 // add(3.5, 4.5); // 错误!double 不满足 Integral return 0;}6.2 concepts vs enable_if
| 维度 | enable_if | concepts |
|---|---|---|
| 可读性 | 冗长晦涩 | 直观清晰 |
| 报错信息 | 深奥难懂 | 友好明确 |
| 语法 | 模板技巧 | 标准语法 |
| 标准 | C++11 | C++20 |
七、实战:任意类型最大值
综合本篇知识,实现支持任意参数个数的 max:
#include <iostream>#include <type_traits>using namespace std;
// 两个参数版本(递归终止)template <typename T>T myMax(T value) { return value;}
// 变参版本:比较第一个和剩余的最大值template <typename T, typename... Args>auto myMax(T first, Args... rest) { auto restMax = myMax(rest...); // 递归求剩余最大值 return first > restMax ? first : restMax;}
// 折叠表达式版本(C++17 更简洁)template <typename... Args>auto foldMax(Args... args) { return (args > ...); // 不适用}
// 正确折叠版template <typename First, typename... Rest>auto foldMax2(First first, Rest... rest) { auto result = first; ((result = (rest > result ? rest : result)), ...); return result;}
int main() { cout << myMax(3, 7, 2) << endl; // 7 cout << myMax(1.5, 3.2, 2.8, 4.1) << endl; // 4.1 cout << foldMax2(10, 20, 5, 15) << endl; // 20 cout << foldMax2(3.5, 2.5) << endl; // 3.5 return 0;}总结
本篇讲解了变参模板(参数包、sizeof…、递归展开)、C++17 折叠表达式(求和、打印、逻辑判断)、SFINAE 与 enable_if(条件启用)、void_t 技巧(特性检测)、C++20 concepts 预告,并用任意类型最大值串联实战。重点掌握:参数包递归展开模式、折叠表达式的四种形式、enable_if 的两种写法、void_t 检测原理。
下一篇将讲解类型推导与完美转发(auto/decltype/右值引用/完美转发),敬请期待!
C++模板教程:变参模板、折叠表达式与SFINAE
作者:Wang Shijie
本文链接:https://wsj-blog.pages.dev/posts/c-mu-ban-jiao-cheng-bian-can-mu-ban-zhe-die-biao-da-shi-yu/
本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
留言评论
评论资源加载失败,请重试。