C++26 最新标准
C++26 草案综述:特性冻结状态、静态反射、契约、std::execution、pack indexing 等已并入草案的特性与编译器支持现状。
学习目标
- 明确 C++26 的标准进程:2025-06 特性冻结,正式标准预计 2026 年底前后发布
- 认识已并入 C++26 草案的核心语言与库特性,能读懂相关示例
- 理解「标准已定稿」与「编译器已实现」的区别,学会用特性宏做防御性检测
前置知识
- C++ 概述与现代标准:了解 C++ 标准的三年一版节奏
- C++23 新特性:C++26 的很多特性建立在 C++23 之上(如 mdspans 之于 linalg)
C++26 处于什么阶段
C++ 遵循「三年一版」的发布节奏。C++26 的时间线如下:
timeline
title C++26 关键时间线
2025-06 : 特性冻结(feature complete): 不再接纳新特性提案
2025-2026 : 委员会草案(CD)评审与措辞修正
2026 年底前后 : 预计发布正式标准 ISO/IEC 14882
两个关键结论:
- 标准本体尚未正式发布。在 ISO 正式出版之前,C++26 的一切内容都应以「标准草案」为准; 本文档只描述已并入草案的特性,并标注提案编号(P 编号)便于溯源。
- 定稿不等于可用。即使标准发布,编译器完整实现通常还要再滞后数年。例如 C++23 的
import std;至今(2026 年)在各工具链上的落地仍然参差不齐。写作本文时的现状是: GCC 14/15、Clang 18-20、MSVC 对 C++26 特性只提供部分或实验性支持,其中静态反射 (P2996)尚无主流编译器的完整实现(仅有实验分支)。
已并入草案的语言特性
契约(Contracts,P2900)
是什么:契约允许把「函数对调用方的承诺」直接写进函数签名——前置条件(pre)描述
「调用方必须保证什么」,后置条件(post)描述「函数保证返回时什么成立」,contract_assert
用于函数体内部的中间断言。可以把契约理解为「写在代码里的接口说明书」,违约即程序缺陷。
为什么需要:目前验证参数的做法要么是抛异常(把调用方 bug 当运行时错误),要么是
assert(Release 模式下消失)。契约让这一语义标准化,并允许编译期/运行期选择检查强度。
// C++26 草案契约语法(P2900):语境关键字,不是 [[属性]]
// 注意:早期提案中的 [[pre: ...]] 属性写法已被废弃,检索旧资料时注意区分
int divide(int a, int b)
pre(b != 0) // 前置条件:调用方必须保证 b 非零
post(r : r * b == a) // 后置条件:返回值 r 满足恒等式
{
contract_assert(b != 1 || a != b); // 函数体内的契约断言(示例语义)
return a / b;
}
divide(6, 2); // 正常:契约成立
divide(6, 0); // 违反前置条件:属程序缺陷,按检查级别处理
契约的检查级别(off / observe / enforce / quick-enforce)决定了违约时的行为—— 是继续运行仅记录、还是直接终止程序。这属于构建期的全局策略,由实现定义的机制控制。
静态反射(P2996)
是什么:反射让程序「看见自己的结构」。C++26 走的是静态反射路线:所有反射查询都 发生在编译期,没有运行时类型元数据,因此不增加二进制体积、不影响运行性能。 可以类比:运行时反射像「带说明书出厂」,静态反射像「编译器在装配时已经把说明书上的 信息直接焊进了产品」。
核心语法(草案状态):
// C++26 静态反射(P2996):目前尚无主流编译器完整实现,仅示意
#include <meta>
struct Point { int x; int y; };
constexpr auto r = ^^Point; // ^^T 产生反射元信息,类型为 std::meta::info
// std::string_view name = std::meta::identifier_of(r); // 查询名字 "Point"
// members_of(r) 产生成员的反射片段,[: :] 是「片段展开」运算符,
// 用于把编译期的反射信息重新注入回代码位置。
典型用途:序列化、ORM 字段映射、枚举名转字符串、依赖注入容器——过去需要宏、代码生成器 或第三方反射库的场景。再次强调:截至特性冻结,没有主流编译器完整实现 P2996,生产环境 请等待落地,勿基于实验分支开发业务代码。
Pack indexing(P2662)
按索引直接访问参数包,不再需要递归展开或 std::get:
#include <print>
template <typename... Ts>
void demo(Ts... args) {
// C++26:包索引语法 包名...[索引]
// 索引为 0 时等价于取第一个参数;类型版本写作 Ts...[0]
std::println("first = {}", args...[0]);
}
int main() {
demo(10, 3.14, "hi"); // 输出:first = 10
}
C++26 之前的等价写法是 std::get<0>(std::tie(args...)) 或模板递归,包索引让这类代码
直观得多。GCC 14+、Clang 19+ 已有较好支持,属于 C++26 中较早能用的特性。
占位符变量 _(P2169)
_ 成为合法的「有意不用」占位符,多次声明互不冲突,专治未使用变量警告:
#include <map>
#include <string>
std::map<std::string, int> ages = {{"A", 1}, {"B", 2}};
int total = 0;
for (auto& [name, _] : ages) { // 第二个绑定明确「不用」,且不会互相冲突
total += 1; // 只关心条目数量
}
// 多个 _ 可以同时存在;如需取地址,也有 std::ignore 之外的正式语义
注意区分:_ 是「每个作用域各自独立」的普通名字(可被后续声明覆盖语义),不是魔法符号;
需要「丢弃返回值并保证求值」时仍有 std::ignore 的场景。
= delete(“原因”)(P2573)
删除函数时可以附带理由,编译器会把它放进错误信息,省去翻文档:
struct NonCopyable {
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete("此类型持有系统句柄,禁止拷贝");
NonCopyable& operator=(const NonCopyable&) = delete("同理,禁止拷贝赋值");
};
NonCopyable a;
NonCopyable b = a; // 编译错误信息会直接显示上面写的删除原因
#embed(P1967)
编译期把二进制文件内容嵌入数组,替代 xxd/xxd -i 之类的代码生成步骤:
// C++26(与 C23 同款):把资源文件直接嵌入可执行文件
static constexpr unsigned char logo[] = {
#embed "assets/logo.png"
};
static constexpr std::size_t logo_size = sizeof(logo);
对嵌入式与游戏开发尤其有用:资源打进二进制、无需运行时文件 IO、构建链更短。
已并入草案的库特性
std::execution(P2300)
基于 sender/receiver 的异步执行框架,是这批库特性里工程影响最大的一项。它要解决的 问题是:异步操作之间的组合与调度没有标准抽象——回调地狱、取消传播、线程池选型 全靠各自为政的库。
// C++26 std::execution(P2300,草案):示意代码,API 以最终标准为准
using namespace std::execution;
// just 构造一个携带数据的 sender;then 串接延续;sync_wait 同步等待结果
auto snd = just(42)
| then([](int x) { return x * 2; })
| then([](int x) { return x + 1; });
auto result = sync_wait(std::move(snd)); // 得到 optional<tuple<int>>
// result 中的值为 85
心智模型:sender 是「未来的工作」本身,receiver 是「工作完成后交给谁」,调度器 (scheduler)决定「在哪个执行上下文跑」。三者可自由组合,取消(cancellation)是一等公民。
std::inplace_vector
固定容量、存储在自身内部的向量(不堆分配)。容量编译期给定,超出即断言/异常, 适合嵌入式、实时路径、容器内嵌缓冲:
// C++26 草案:最多 8 个 int,全部内联存储,无堆分配
// std::inplace_vector<int, 8> buf;
// buf.push_back(1); buf.push_back(2);
与 std::array 的区别:inplace_vector 有动态大小(size 可变),只是容量固定;
与 std::vector 的区别:绝不分配堆内存,迭代器稳定性规则也不同(挪动会使迭代器失效)。
std::hive
以「桶(bucket)」组织元素的容器,特点:元素地址稳定(增删不影响其他元素地址)、 支持大规模 O(1) 稀疏删除(erase 标记后批量回收)。适合粒子系统、实体组件等 「频繁增删且按地址引用」的场景。迭代顺序未指定,这是换取地址稳定性的代价。
std::simd 与 std::linalg
std::simd(基于std::experimental::simd的标准化版本):可移植的数据并行类型, 编译器自动映射到 SSE/AVX/NEON 指令,让向量化代码不再依赖编译器 intrinsics;std::linalg:构建在 C++23std::mdspan之上的线性代数参考接口(矩阵乘、点积、 范数等),面向与 BLAS 生态的互操作,定位是「标准化的接口层」而非高性能实现。
调试与并发支持
#include <debugging>
if (std::is_debugger_present()) { // 检测是否被调试器附加
std::breakpoint(); // 触发调试器断点(未被调试时通常无操作)
}
hazard_pointer 与 rcu 也已并入草案,为无锁数据结构提供标准化的内存回收策略,
细节见 内存序与无锁编程 的并发回收讨论。
特性支持检测:特性宏
依赖草案特性时,用特性宏做防御性检测比判断编译器版本可靠得多:
#if defined(__cpp_pack_indexing)
# include <print> // 有该宏的编译器一般也能提供 <print>
template <typename... Ts>
auto first(Ts... args) { return args...[0]; }
#else
template <typename... Ts>
auto first(Ts... args) { return args; } // 回退实现
#endif
常用 C++26 特性宏(示例):__cpp_contracts(契约)、__cpp_pack_indexing、
__cpp_deleted_function(=delete 理由)、__cpp_lib_execution 的取值随实现更新。
查询权威支持情况请认准 cppreference 的 Compiler Support 页面,不要依赖记忆中的
「某版本支持某特性」。
常见陷阱与误区
误区一:把「特性冻结」当「标准发布」。 特性冻结只是说明清单不再增加,之后还要经历 多轮草案评审。任何「C++26 已发布」的表述(在正式 ISO 出版前)都是错误的。
误区二:照博客代码写生产业务。 草案特性的示例代码在特性定稿前就可能变化——契约的 属性写法到关键字写法就是前车之鉴。核心代码用特性宏隔离草案特性。
误区三:混淆 std::execution 与 C++17 并行算法。 std::execution::par 执行策略
(<execution>)是 C++17 的并行算法机制,与 P2300 的 sender/receiver 框架是两回事,
只是共用「execution」这个单词。
误区四:误以为反射会有运行时开销。 P2996 是纯编译期反射,运行时零开销; 有开销的是用它生成代码后产生的代码本身。
小结
初学者记住这三点:
- C++26 还没正式发布(预计 2026 年底前后),现在的它只是「特性已冻结的标准草案」;
- 这一版的重头戏:契约、静态反射、std::execution、pack indexing、
#embed; - 学习方法:理解要解决的问题 > 背语法。语法在草案期仍可能微调,问题本身是稳定的。
进阶者还需注意:
- 特性宏(
__cpp_*/__cpp_lib_*)是依赖草案特性的唯一可靠开关; - 反射(P2996)尚无主流编译器完整实现,不要基于实验分支开发业务;
std::execution与 C++17 并行算法、std::linalg与 BLAS 实现库的定位区别要分清;- 关注 WG21 提案原文(open-std.org)而非二手博客,提案编号是检索的最佳入口。