C++ 模板元编程一直是"工程师对机器"的对抗赛。你用 std::enable_if、if constexpr、折叠表达式这些工具在编译期辗转腾挪,每多一层抽象就多一层模板参数、多一层 typename...。
P2996(Reflection for C++26)试图终结这种状态。它给 C++ 引入一组真正的**编译期内省(introspection)和代码注入(injection)**机制,让你能在编译期遍历类的成员、获取名称/类型、生成新代码——而不再依赖宏、外部代码生成器、或 Boost.Hana 这种"用模板模拟反射"的黑科技。
背景:C++ 为什么需要反射
在 Java、C#、Python 这类语言里,反射是标准库的一部分:你能在运行时获取一个类型的所有字段、调用任意方法、甚至动态创建对象。C++ 一直缺少这个能力,因为两个核心约束:
- 零开销原则:不能为不用反射的人付出任何运行时成本
- 编译期模型:C++ 的类型信息主要在编译期可用,运行时大多已擦除
C++ 社区的回应是"用模板元编程模拟反射"——std::tuple、std::is_same_v、std::decay_t、type traits……这些本质上都是用模板在编译期"计算"出类型信息。但它们是碎片化的:你需要为每种具体的元操作写一个 trait,没有统一的 API 来遍历"某个类型的所有成员"。
P2996 的方案很直接:编译期反射,零运行时开销。所有的反射操作都在 consteval 上下文中执行,生成代码片段(splicer)注入到 AST 中。
核心机制:^^ 与 [::]
P2996 引入了两个核心语法:
^^(reflection operator):把语法结构反射成std::meta::info类型的值[::](splicer):把std::meta::info“拼接"回语法结构中
最简单的例子:
| |
你看,^^int 产生的 r 是一个反射值(类型是 std::meta::info),它不代表 int 本身,而是关于 int 的元信息。typename [:r:] 把这个元信息重新拼回一个类型声明——C++ 标准的术语叫 splicing。
同一个反射值可以用于不同的上下文:
| |
为什么用单一的 std::meta::info 类型
一个常见疑问是:为什么不按语言元素分类定义反射类型?比如 std::meta::variable、std::meta::type、std::meta::function?
论文明确指出这是故意的设计选择:如果把语言设计编码到类型系统中,会让未来的语言演化极其困难——C++11 扩展了 variable 的语义包含引用,如果当时已经固定了 std::meta::variable 类型,就会面临破坏性变更。单一 opaque 类型让委员会保留了对语言演化的所有自由度。
遍历类的成员
反射的真正威力在于遍历。以 std::meta::nonstatic_data_members_of(^^T) 为例,它返回一个 vector<meta::info>,包含类型 T 的所有非静态数据成员。配合 C++26 的另一个核心提案 P1306 Expansion Statements,你可以这样遍历:
| |
template for 是 P1306 引入的编译期展开循环:它的循环体在编译期被展开成 N 份,每一份用循环变量的具体值替换。上面的代码等价于编译期展开为:
| |
实现一个通用序列化
有了反射和 expansion statements,很多过去需要宏或外部工具的任务变得非常简单。比如 enum → string:
| |
再比如 struct → JSON:
| |
这不需要任何宏、不需要外部代码生成器、也不需要 Boost——所有逻辑在编译期一次 resolve,运行时只是一个普通的函数调用,没有任何反射开销。
更激进的例子:编译期 struct 变换
反射不仅能读,还能写——也就是代码注入(code injection)。你可以根据现有类型在编译期构造出新的类型:
| |
这叫 compile-time type generation,已在 EDG 和 Bloomberg Clang fork 中实现。
实现状态
目前有两个可用的编译器实现,都可以在 Compiler Explorer 上在线测试:
| 实现方 | 编译器 | 状态 |
|---|---|---|
| EDG (Edison Design Group) | EDG C++ Front End | 覆盖 P2996 大部分特性,可直接使用 |
| Bloomberg | Clang fork (clang-p2996) | 开源实现,已支持模板/命名空间 splicer |
论文 P2996R13(2025-06-20)是当前最新版。P1306(Expansion Statements)也已进入 CWG 审查阶段。两者组合使用可以覆盖绝大多数反射场景。
与已有元编程方案的对比
| 方案 | 类型遍历 | 成员名获取 | 代码注入 | 编译器支持 |
|---|---|---|---|---|
| C++17 type traits | 无(需手写) | 无 | 无 | 所有编译器 |
C++20 requires + concepts | 部分(条件约束) | 无 | 无 | 所有编译器 |
| Boost.Hana | 有(模板模拟) | 无 | 无 | 所有主流编译器 |
Clang __reflection 扩展 | 部分 | 有 | 有限 | Clang only |
| P2996 | 有 | 有 | 有 | EDG / Bloomberg Clang |
| P2996 + P1306 | 有 | 有 | 有(含展开) | EDG / Bloomberg Clang |
今日可执行动作
- 在线体验:打开 Compiler Explorer,选择 “EDG (experimental reflection)” 编译器,复制上面的
enum_to_string例子跑一遍 - 本地编译:如果你用 Clang,克隆 Bloomberg 的 clang-p2996,按 README 构建后测试
- 阅读原文:阅读 P2996R13 的第 3 节(Examples)和第 4 节(Proposed Features),里面有 17 个完整的可运行示例
参考
- P2996R13: Reflection for C++26
- P1306R4: Expansion Statements
- Bloomberg clang-p2996 (GitHub)
- P2996 on Compiler Explorer (EDG) — 选择 EDG experimental reflection 编译器