日期:2026-07-14 · 基线:ce34a70(0.0.90 已含 #210 方言旗标全图化) 输入:用户需求——"mcpp.toml 里指定/enable 一项即可启用 C++26 反射等实验特性,跨编译器"; review 反馈(2026-07-14)——"收敛到:必然当前编译器最新 C++ 标准 + 可开启的所有实验特性,核心是语言特性 + 标准库" 前置文档:
2026-07-13-post-089-roadmap-and-std-dialect-flags-design.md(§1.3/§1.5)、2026-07-13-single-pr-090-implementation-plan.md(S1)、2026-06-01-cpp-standard-first-class-design.md本文是 post-089 路线图 §1.5 预留位的续篇:"reflection = true这类糖等用户需求明确再加"——需求现已明确。 review 定案(2026-07-14):值命名 =c++fly(§11-Q1);index 收录策略 = v1 warn 观察(§11-Q2);GCC16 契约旗标已实机定案(§11-Q3)。
新增一个版本无关的 standard 值 c++fly(命名已定案,§11-Q1):
[package]
standard = "c++fly" # 一键:工具链最新 -std= 档位 + 它能开的全部实验特性门语义 = ①resolved 工具链支持的最新 -std= 档位(GCC16→-std=c++26,Clang→-std=c++2c,
MSVC→/std:c++latest)②该工具链支持的全部实验性语言特性门(GCC16→-freflection 等)
③标准库实验门(libc++→-fexperimental-library;libstdc++/MSVC STL 无需)。
判定由工具链层新数据表 cppfly.cppm 按〈编译器族 × 版本 × stdlib〉给出,产物汇入
0.0.90 已建成的图全局方言旗标通道——机制零新管线,新增的只是"意图 → 档位+旗标"这层数据。
不支持的特性软跳过并打印 summary(该值的本义就是"这套工具链能玩的都给我")。
用户体验对比:
# 0.0.90 现状:自己查档位、自己查旗标拼写,换 Clang/MSVC 即坏
[package]
standard = "c++26"
[build]
dialect_cxxflags = ["-freflection"]
# 本设计:一行,意图完整,跨编译器
[package]
standard = "c++fly"| 件 | 位置 | 作用 |
|---|---|---|
known-list is_dialect_flag() |
src/manifest/types.cppm:453-467 |
-freflection/-fcontracts/-fchar8_t/-D_GLIBCXX_USE_CXX11_ABI= 识别 |
逃生舱 [build] dialect_cxxflags |
src/manifest/toml.cppm:648 |
用户显式声明图全局旗标 |
合并去重 dialect_flags(bc) |
src/manifest/types.cppm:469-479 |
显式 ∪ 自动提升 |
| 图全局注入 | plan.cppm:328-331 → flags.cppm:304-310 |
进全局 $cxxflags(全图 TU) |
| scan/std-BMI 同步 | prepare.cppm:2590-2607(stdFlagAndDialect) |
扫描与 std/std.compat 预构建同方言 |
| 指纹自动失效 | std-module.json 哈希完整命令串 | 旗标变更 → BMI 重建,零新缓存逻辑 |
| e2e | 98_reflection_import_std.sh |
gcc16 + -freflection + import std 回归 |
| standard 归一化 | types.cppm:481-539(normalize_cpp_standard) |
c++23/26/2c/gnu++/latest 白名单,level 数值化 |
各家 -std= 拼写 |
dialect.cppm:116-123(std_flag_for) |
GNU -std=<canonical> / MSVC /std:c++latest |
- 无"最新档位"概念:
c++latest是静态映射(canonical 固定,GNU 侧落到-std=c++26),不随 resolved 工具链取其真正的最新档;更没有"档位 + 实验门"的捆绑。 - 无能力/版本校验:代码里没有任何
version >= N门控(provider.cppm的capabilities_for()只按 CompilerId+triple,未用Toolchain::version);gcc@15+-freflection只能等编译器报未知选项。 - 无一键全开:每个特性一个旗标,用户要自己攒清单、自己跟进编译器演进。
- 拼写族问题悬空:
dialect_cxxflags存 GNU 拼写;MSVC 原生后端(0.0.90 S2-S6) 落地后同一 manifest 在 cl.exe 上是非法选项。
standard决定-std=档位(23/26/latest),是语言基线;- 实验特性是基线之上的附加门:同一
-std=c++26下,GCC 16 的反射仍需-freflection(libstdc++<meta>整体被__cpp_impl_reflection守卫,该宏仅由此旗标定义—— post-089 文档 §1.1)。 - 初版设计曾把两轴拆开暴露(
standard+ 具名std_features列表);review 反馈明确 场景是"前沿尝鲜"整体模式——要的是"必然最新档 + 全部能开的门"这一个捆绑值, 细粒度选择不是当前需求(§4-C 记录取舍,§10 保留延伸位)。
| 特性(提案) | GCC 16.1 | Clang 上游 22/23 | Bloomberg clang-p2996 | MSVC v145 (VS2026 18.6) |
|---|---|---|---|---|
最新 -std= 档位 |
-std=c++26 |
-std=c++2c |
-std=c++26 |
仅 /std:c++latest(无 /std:c++26) |
| 反射 P2996+P3394 | ✅ 需 -freflection(实验) |
❌ 无 | ✅ -freflection/-freflection-latest(含 P1306/P3096/P3394/P3491) |
❌ 无,无公开 ETA |
| 契约 P2900R14 | ✅ 实验(随 -std=c++26 默认启用,无需附加旗标;-fcontracts 仅用于在更早标准下强开;evaluation-semantic 由 -fcontract-evaluation-semantic= 控制。已实机定案,§11-Q3) |
❌ 无 | — | ❌ 无 |
| 展开语句 P1306 | ✅ 随 -std=c++26 |
Clang 23 partial | 经 -freflection-latest |
❌ |
| 标准库实验门 | 无需(libstdc++ 不设门) | libc++ 需 -fexperimental-library |
同 | 无需(MSVC STL 随 /std:c++latest) |
要点:
- 反射已于 2025 Sofia 会议进入 C++26;GCC 16.1(2026-04 发布)是唯一上游落地, 且仍标注 experimental;Clang 上游没有,只有 Bloomberg fork;MSVC 没有。
- 即 v1 语言特性侧的现实收益面 = GCC ≥ 16(mcpp 生态已有 gcc16 系工具链包,winlibs
MinGW 亦然);Clang 侧 v1 收益 = 最新档 +
-fexperimental-library;Clang/MSVC 缺失的 特性由 summary 如实呈现,而非编译器原始报错。 - 该矩阵是易变数据——这正是它必须住在 mcpp 工具链层数据表里、而不是让每个用户 manifest 各自硬编码旗标的根本原因(Clang 落地 P2996 后,mcpp 发版加一行,全生态受益)。
来源:gcc.gnu.org/projects/cxx-status.html、gcc-16/changes.html、clang.llvm.org/cxx_status.html、 github.com/bloomberg/clang-p2996、learn.microsoft.com(/std 文档、VS2026 release notes)、 libcxx docs(UsingLibcxx)。
| 系统 | 机制 | 对本设计的启示 |
|---|---|---|
| MSVC | /std:c++latest = "下一标准草案的全部已实现特性 + 部分实验特性"一键聚合 |
与本设计同构的厂商级先例:一个档位值捆绑"最新 + 实验" |
| libc++ | -fexperimental-library 一个旗标聚合全部不稳定库件 |
库侧聚合开关成立;mcpp 把它并进同一个值 |
| Rust nightly | #![feature(x)] 具名注册表 + stable 拒绝 |
具名粒度模型,对应 §10 的延伸位;其"注册表 + 工具链门控"内核被本设计沿用 |
CMake import std |
CMAKE_EXPERIMENTAL_CXX_IMPORT_STD = 按版本变化的 UUID,升级即失效 |
反面参照:故意摩擦表达"无稳定性承诺"。mcpp 用 summary 警示而非 UUID 摩擦(§5.4) |
| xmake/meson/bazel | 只有 -std= 档位设定,无实验特性抽象 |
空白区,mcpp 的差异化点 |
三个结构性问题:钉死 26 → 每个标准期一个新值,gnu++26-fly/c++latest-fly 组合爆炸; GCC 17 若默认启用反射,"26-fly" 对它失义;canonical 白名单每档 ×2。 判定:否决钉版本的拼法;其"一键"诉求由方案 D 的版本无关值承接。
"全部"本就是随工具链漂移的移动目标,这一点方案 D 同样存在(见 §5.5 如何面对);
B 真正的问题是放错了地方:它与 standard 分居两表、且允许 standard = "c++23" +
experimental = true 这种自相矛盾的组合(实验特性几乎全部要求最新档)。review 反馈
"必然当前编译器最新 C++ 标准"指明二者不该独立配置。判定:否决独立布尔键。
粒度最细、显式具名可做硬错诊断,Rust nightly 同构。review 反馈指出其错位:名字 "体现不出是开最新支持的",且细粒度选择不是当前需求——用户场景是整体尝鲜模式, 不是逐特性挑选。判定:不作为 v1 表面;其内核(注册表 + 版本门控)全部保留为 方案 D 的实现机制;若日后出现"库硬依赖单一特性"的需求,再把列表作为高级表面加回 (§10),届时零机制返工。
一个值 = 最新档 + 全部实验门(语言 + 标准库)。关键设计判断:
- 版本无关命名消解了 A 的全部问题:不钉 26,永远指"当前工具链的最前沿", 特性毕业(变默认)后该值语义自动收窄到"剩余的实验门",名字永不失义; 白名单只加一个值。
- 放在
standard键上消解了 B 的问题:与档位天然捆绑,不存在矛盾组合; 用户"enable 一个项"的诉求被字面满足。 c++latest与它的分工清晰:latest= 最新已定档(仍是确定性构建),c++fly= latest + 实验门(明示放弃跨工具链确定性,见 §5.5)。- 机制上它只是方案 C 注册表的一个"全选 + 取最新档"查询,实现同一张表。
[package]
standard = "c++fly" # 唯一新增;不需要任何其他键normalize_cpp_standard(types.cppm:481)增一个值:canonical"c++fly",level = 1000(>c++latest的 999,语义"latest 之上"),新增CppStandardConfig::experimental = true字段。白名单错误消息同步补拼写。- 不做 gnu 变体(
gnu++fly):实验模式下 GNU 扩展无额外意义,YAGNI。 - parse 期无其他新键、无形态校验负担。
模块名对齐 manifest 值(review 拍板):收敛后本模块的单一职责就是"c++fly 在各
编译器上的支持情况与映射",以值为名最可检索、职责边界自解释("职责命名、禁口袋名");
若日后加回具名列表表面(§10 延伸位),注册表仍住这里,届时再评估是否更名。
三张数据表 + 一个查询点:
// 表 1:族 × 版本 → 最新已支持 -std= 档位(canonical)
struct LatestStdRule { CompilerId family; int minMajor; std::string_view canonical; };
// GCC≥14→"c++26"、GCC13→"c++23"、Clang≥17→"c++2c"、MSVC→哨兵(std_flag_for 出 /std:c++latest)
// 表 2:实验性语言特性门(版本门控)
struct StdFeatureRule { CompilerId family; int minMajor; std::string_view flags; };
struct StdFeature { std::string_view name; // "reflection"
std::string_view paper; // "P2996" —— summary/文档用
std::span<const StdFeatureRule> rules; }; // 无该族行 = 不支持
// 表 3:标准库实验门(stdlib 维度,非 compiler 维度)
struct StdlibGateRule { std::string_view stdlibId; // "libc++"
std::string_view flags; }; // "-fexperimental-library"
struct CppFlyResolution {
std::string stdCanonical; // 档位(交给既有 std 通道)
std::vector<std::string> flags; // 全部实验门旗标(该族拼写,去重)
std::vector<EnabledInfo> enabled; // 名字 + 旗标 —— summary 用
std::vector<SkipInfo> skipped; // 名字 + 原因 —— summary 用
};
CppFlyResolution
cppfly::resolve(const Toolchain& tc); // 唯一查询点;首个 Toolchain::version 消费者v1 表内容(刻意最小):
| 表 | 内容 |
|---|---|
| LatestStd | GCC≥14→c++26 / GCC13→c++23 / Clang≥17→c++2c / MSVC→latest 哨兵 |
| 语言特性 | reflection(P2996:GCC≥16→-freflection)、contracts(P2900:GCC≥16→空串,随 c++26 档位默认启用,§11-Q3 已定案——空串行"支持但无需附加旗标"从第一天就有真实客户) |
| 标准库门 | libc++→-fexperimental-library(libstdc++/MSVC STL 无行 = 无需) |
毕业演化:某族新版本默认启用某特性后,把该行 flags 改为空串(summary 仍列为
enabled,佐证"该值语义自动收窄");Clang 落地 P2996 后加一行。manifest 永不用改。
toolchain resolve 之后(此时才知道族/版本/stdlib):
cfg.experimental?
→ r = cppfly::resolve(tc)
→ std 档位:std_flag_for 按 r.stdCanonical 出旗标(签名扩为接收 Toolchain 或预解析结果;
顺带审计现状 c++latest 在 GNU 侧的 canonical→旗标路径,见 §11-Q5)
→ effectiveDialect = dialect_flags(bc) ∪ r.flags // 既有合并函数扩一个入参,去重
effectiveDialect 在单一 helper里计算,喂给现有两个消费点——
plan.cppm:328-331(→ BuildPlan::dialectFlags → flags.cppm:304-310 全局 $cxxflags)
和 prepare.cppm:2590-2607(stdFlagAndDialect → P1689 扫描 + std/std.compat BMI 预构建)。
两点必须同源,否则重演"scan/BMI 少旗标"一类分裂 bug。下游(指纹失效、ninja 重建、
compile_commands.json)全部自动正确,零新管线。
拼写族问题(§1.2-4)顺带解决:cppfly::resolve 按 resolved 族输出拼写,
注入通道的天然是对的;raw dialect_cxxflags 逃生舱维持"用户自负拼写"。
c++fly 的本义是"这套工具链能玩的都给我"——逐特性软判定,不硬错
(硬错会让该值在 Clang/MSVC 上不可用,违背本义)。作为交换,构建头部必打印 summary
(非 verbose 也打;呼应 CMake"无稳定性承诺"的意图但不设 UUID 摩擦):
c++fly on gcc@16.1.0: -std=c++26; enabled reflection (-freflection), contracts; skipped: (none)
c++fly on llvm@22.1.8: -std=c++2c; enabled experimental-library (-fexperimental-library); skipped reflection (clang: unsupported), contracts (clang: unsupported)
mcpp why toolchain / self doctor 输出追加同一份解析结果(与 0.0.90 §1.3d 的
dialectFlags 输出并列)。生态衔接:summary/doctor 里对 skipped 特性给出可执行 hint
(如 hint: reflection needs [toolchain] linux = "gcc@16")——mcpp 自分发工具链,
这个修复闭环是 CMake/meson 给不了的。
- 该值按设计放弃跨工具链确定性(同一 manifest 在 gcc16/llvm22 上档位与旗标不同)—— 这正是"尝鲜模式"的定义,由 summary 保证可解释性。适用场景:playground、实验分支、 跟进标准演进的库的 CI 矩阵;不适用:追求可复现分发的包(§11-Q2:index 收录是否 警告/拒绝该值)。
- 图语义零新规则:
standard本就 root 统治全图(prepare.cppm:172,依赖 manifest 的 standard 被 root 覆盖),c++fly继承之;实验旗标走图全局方言通道, 与档位天然同图一致。依赖自己声明c++fly而 root 没写 → 与今天任何 standard 不一致的处理相同(root 赢),不新增分歧面。
无新逻辑:档位旗标与实验旗标都进全局 $cxxflags 与 std-BMI 命令串,ninja 命令行
变更与 std-module.json 命令哈希已覆盖失效。注意:指纹的输入是 resolve 后的
旗标串(非 canonical 名),所以换工具链/升级 mcpp 表数据 → 命令串变 → 自动重建,
"移动目标"不会产生陈腐缓存。
- 单测(表驱动,仿
test_toolchain_dialect.cpp):cppfly::resolve:gcc13/15/16 × clang17/22 × msvc,断言档位、enabled/skipped 切分、旗标拼写;stdlib 维度(libc++ vs libstdc++ vs MSVC STL);normalize_cpp_standard("c++fly"):canonical/level/experimental 字段; 白名单错误消息含新拼写;- 与
dialect_cxxflags合并去重(用户手写-freflection+ experimental 不重复)。
- e2e:
98系增变体:manifest 仅standard = "c++fly",gcc16 上断言全图 TU、 scan、std-BMI 命令串含-std=c++26与-freflection,构建运行绿 (# requires: gcc,host gcc ≥ 16 才跑,遵守既有 host-aware 纪律);- llvm 工具链 + 同 manifest → 构建照常,断言
-std=c++2c、-fexperimental-library(libc++ 时)与 skipped summary 文案(软路径回归,不依赖 gcc16,CI 可跑); - 既有
standard = "c++23"/"c++26"/"c++latest"工程全量回归(字节不变)。
| 步 | 内容 | 触点 |
|---|---|---|
| S1 | cppfly.cppm 三表 + cppfly::resolve + 单测 |
src/toolchain/(新) |
| S2 | normalize_cpp_standard 增值 + CppStandardConfig::experimental + std_flag_for 版本化(含 c++latest GNU 路径审计 §11-Q5)+ 单测 |
types.cppm、dialect.cppm |
| S3 | prepare 接线:experimental 分支 → resolve → 方言合并 helper(两消费点同源)→ summary/doctor 输出 | prepare.cppm、plan.cppm、flags.cppm(仅合并点) |
| S4 | e2e ×3(§6) | tests/e2e/ |
| S5 | CHANGELOG、README manifest 一节 | docs |
预估含测试 400–700 行;S1/S2 可并行,S3 依赖两者。
纯增量:不写 c++fly 的 manifest 行为逐字节不变;c++latest 语义不动
(最新已定档,确定性构建);dialect_cxxflags 与 cxxflags known-list 自动提升
继续工作(定位:注册表没有的新旗标 / 单点精细控制的逃生舱,README 如此表述);
98 号 e2e 原变体保留(逃生舱回归)。无格式版本升级。xpkg 路径
(xpkg.cppm:1226 一带)v1 不加该值(index 包无此需求,见 §11-Q2)。
standard = "c++fly"一行,gcc@16 上:全图 TU、扫描、std BMI 均带-std=c++26 -freflection(契约随档位默认启用,无附加旗标,§11-Q3), 反射示例工程构建运行绿,manifest 零手写旗标。- 同一 manifest 切 llvm@22:构建照常,档位
-std=c++2c,libc++ 下带-fexperimental-library,summary 列出 skipped reflection/contracts 及 hint。 - summary 在两种工具链上均打印且内容与实际命令串一致(std-module.json 可核对)。
c++latest/c++26/c++23工程全量 e2e 无回归,行为字节不变。- gcc@15 上 experimental:档位
-std=c++26,reflection 因版本门控进 skipped (而非编译器报未知选项)。
- 具名特性列表表面(
std_features = ["reflection"],初版方案 C):机制(注册表) 本 PR 全部就绪,列表表面等"库硬依赖单一特性、需要硬错诊断"的需求出现再加,零返工。 gnu++fly变体(§5.1,YAGNI)。- 契约评估语义的语义化配置(GCC 拼写
-fcontract-evaluation-semantic=ignored|observed|enforce|quick_enforce,以 gcc16 man page 为准):属"特性的参数" 而非"开关",走dialect_cxxflags直传。 - clang-p2996 / 未来 llvm-p2996 fork 打包进 mcpp 生态:机制已就绪(表里加行), 打包/分发是独立 issue;落地后 Clang 侧 experimental 自动获得反射。
- 把任意依赖 cxxflags 全图化(0.0.72 包所有制不变,与 post-089 §1.5 一致)。
- Q1 值命名(已定案:
c++fly):用户 review 拍板——短、有项目气质,且 "fly" 不与任何真实-std=拼写冲突。落选备选记录:c++experimental(自文档化但长)、c++next、c++edge。版本无关性(§4-D 核心论证)与拼写无关。 - Q2 index 收录策略(已定案:v1 warn 观察):发布到 index 的包声明
c++fly时收录侧输出警告(非确定性语义提示),不拒绝;观察生态用法后再定是否收紧。 落点在 mcpp-index 收录校验,独立小 PR,不进本 PR 范围。 - Q3 GCC 16 契约的确切 enable 旗标(已定案,2026-07-14 实机探测):本机
xim-x-gcc/16.1.0cc1plus-fsyntax-only探测结论——-std=c++26单独即定义__cpp_contracts且pre()语法可编译(-std=c++23下报语法错,探针有效);-fcontracts的作用是在更早标准下强开(-std=c++23 -fcontracts亦定义宏)。 对照:反射不对称——-std=c++26单独不定义__cpp_impl_reflection,必须-freflection。故表数据:contracts → 空串,reflection →-freflection。 (0.0.90 known-list 里的-fcontracts保留不动:它仍是合法的图全局方言旗标, 只是本设计的 experimental 展开不需要发它。) - Q4 CI:硬路径 e2e 需要 host gcc ≥ 16;若 CI 镜像未达,首版仅本地跑
(
# requires: gcc门已有),llvm 软路径 e2e 不受影响。 - Q5 c++latest GNU 路径审计:
std_flag_for(dialect.cppm:122)GNU 分支为stdPrefix + canonical,而c++latest的 canonical 即 "c++latest"(GCC 不识别-std=c++latest)——实现 S2 时核查现状调用链是否已在上游换算,若是历史暗雷则 顺手修复(独立小 commit)。