Skip to content

Commit 247f7d6

Browse files
authored
feat: 练习即测试——d2mcpp 全面重构(mcpp Provider/d2x 库/双入口) (#84)
* feat(buildtools): 新增 mcpp Provider(C++26),替代 xmake 插件 d2x 改为通用练习框架后,构建工具由具体课程实现。本 Provider 用 C++26 + mcpp 编写——教 C++ 的仓库用 C++ 写自己的工具,且 mcpp 本就是 必需品,零新增运行时依赖。 发现方式刻意不引入声明文件:真相只有目录结构和练习文件自己的头部注释 (// d2x:cxxflags:)。rustlings PR #1355 的教训是 edition 同时写在 rustc 参数和 rust-project.json 里、两边漂移酿成 bug——任何独立声明文件都是 第二套真相源。 双 member 布局解决一个硬约束:dslings 的练习默认就编译不过,而 mcpp 的 build/run 都会先全量构建整个包,一个坏兄弟拖垮全部且零产物。于是 .d2x/build/<std>/ 持全量 target 供 clangd 拿到完整 compile_commands.json, .d2x/build/_current/ 每次只写当前一题供 checker。清单写入前做内容比对, 不推进 mtime,mcpp 的快速路径得以保留。 练习源文件原地不动:main 用 ../ 逃逸出包根。 引导零脚本、Windows 安全:.d2x.json 指向 `mcpp run -q -p d2x/buildtools/mcpp --`,从仓库根执行无需 cd,首次自动 构建 Provider。 runner 在每次 spawn 前清 LD_LIBRARY_PATH:mcpp run 会把它指向自己私有的 glibc 并注入子进程,嵌套的 mcpp 被迫加载错配运行时后会在动态链接器里 段错误(冷启动稳定复现)。d2x 侧对同一问题早有相同处理。 tests/e2e.sh 断言每个参考答案通过、每个练习不通过——rustlings `dev check --require-solutions` 的等价物。实测 51/51 通过。 * chore: 彻底移除 xmake,构建链路全部走 mcpp d2x 改为通用练习框架、构建由 mcpp Provider 承担后,xmake 已无调用方。 删除 11 个 xmake.lua(根 / dslings / dslings/en / solutions 各级)、 d2x/buildtools/xmake/ 插件、d2x/cpp/common.lua(xmake 侧的语言探测辅助, 早已被 main.lua 读 .d2x.json 取代)、以及只写着 TODO 的 cmake 适配器占位。 d2x/buildtools/README.md 一并删除——它描述的 list/build/run 契约已被 Provider 协议(describe/exercises/check)取代,留着会误导。 .xlings.json 去掉 xmake 3.0.7 的 pin(该版本已从 registry 移除,是 d2x CI 里那条 "用真 xmake 而非 xlings shim" workaround 的根因),同时去掉 gcc 与 mingw-w64 —— mcpp 自带工具链沙箱,Windows 侧由它的 MinGW/MSVC 覆盖。 CI 重写:旧流水线只挑 `-ref` 结尾的 xmake target,而 solutions/ 在 xmake.lua 里被注释掉,grep 返回空、循环全跳过、job 退出 0 —— 实际校验零个 目标,且第 60 行的 wc -l 对空串仍打印 "Found 1 reference targets" 把空转 掩盖了。新流水线跑 e2e.sh,断言每个参考答案通过、每个练习不通过,走的是 `d2x checker` 内部同一条 Provider 路径。 撰稿 skill 同步更新:练习不再需要注册,放进 dslings/<std>/ 即可被目录约定 发现;per-exercise 编译选项改为练习文件头部的 // d2x:cxxflags: 指令。 复验:Provider 枚举 52 题、端到端 51/51 参考答案通过 0 失败、d2x checker 全链路正常。 * feat(harness): 判定信号改走侧信道,练习脚手架独立成库 判定原先 100% 是带内信号:断言失败并不改变退出码(实测退出码仍为 0), 所以只能扫 stdout 找 ❌。这会误判——一个断言全过的正确解答,只要在说明 文字里打了个 ❌ 就被判失败(已复现)。反过来,输出截断或 Windows 控制台 代码页不对,判定同样失效。 改为:stdout 回归「给人看」,判定走侧信道 NDJSON。 harness 独立成 mcpp 库包 dslings/harness/,同时提供两条路径: - #include <d2x/cpp/common.hpp> 宏路径。可见输出与旧版逐字节一致, ✅/❌ 的逐条对照是教学的一部分,不动。 - import d2x.harness; 模块路径。用 std::source_location 自动带 file/line,比 __LINE__ 更准,正好是 Verdict.diagnostics 需要的东西。 两条路径不等价,这是 C++ 的硬约束而非疏漏:宏无法跨模块导出,所以 D2X_YOUR_ANSWER 没有模块等价物——它必须展开为空才能制造编译错误。 模块化章节需要另一套填空约定。 include/ 下保持 d2x/cpp/common.hpp 这个既有路径,104 个练习和书本一行 不用改;路径改名可以以后单独做,不必和机制改造捆在一起。 生成的 member 清单改用 [dependencies] path 依赖,干掉了 include_dirs = ["../../.."] 那个把整个仓库根塞进搜索路径的 hack—— 原先练习能 #include 仓库里任何文件。 侧信道逐条追加而非退出时统一写:练习段错误时崩溃前的断言结果照样保留。 D2X_RESULT_FILE 未设置时 harness 只打印不写文件,学员直接跑二进制零摩擦。 Provider 判定顺序:有 ok:false → Fail 且每条失败转成一个 Diagnostic; 无失败但有 wait → Blocked;侧信道文件不存在 → 退回「编译通过 + 退出 0」。 最后一条让 harness 自动变成可选的——纯观察型练习(现有 18 个只用 D2X_WAIT)可以写成零依赖的纯 C++ 文件,学员能原样拷进 Compiler Explorer。 顺带删掉死代码 d2x_is_invocable(104 个练习中 0 处使用)和坏掉的模块桩 d2x/mcpp/common.cppm(宏不能跨模块导出,import 进去什么也拿不到)。 e2e.sh 的清理钩子收窄为逐个练习目录:harness 现在也住在 dslings/ 下, 原先的 `git checkout -- dslings/` 会把开发中的改动一起抹掉(已踩过)。 实测:51/51 参考答案通过;四态判定正确(未完成→fail、参考答案→pass、 答案对且输出含❌→pass、答案对留路障→blocked);真实断言失败产出带 行号与期望/实际值的 diagnostics;模块路径与「无 harness 退回退出码」均通过。 * fix: 堵住 id 注入、修复英文答案空转、让教学漂移可被 CI 检测 三个真缺陷,都不是理论风险: 1. 练习 id 注入。id 直接取自文件名,有两个危险去向:d2x 把它拼进 shell 命令,我们把它写进生成的 TOML([targets.<id>])。带反引号、`]` 或引号 的文件名在任一处都能越界 —— 对社区课程仓库来说,一个恶意 PR 文件名就 足以在任何跑 checker 的人机器上执行命令。 在 discovery 源头做白名单校验并拒绝,而不是在两个下游各自转义:这类 文件名本就是笔误或恶意,与其想办法安全地传递,不如让作者改名。 实测:`99-evil`touch pwned_marker`.cpp` 被拒绝,命令未执行。 2. e2e.sh 把所有英文参考答案静默 SKIP。前缀剥离顺序错了 —— `${sol#en/}` 执行时 sol 已经以 "solutions/" 开头,匹配不到任何东西,是个静默 no-op, 于是每道英文练习都因找不到 solutions/en/... 而跳过。 这正是本脚本头部注释里说要防的那种空转,和旧 CI 一模一样的毛病。 除了修顺序,另加一道防线:pass==0 时直接判失败,杜绝「0 失败」蒙混。 实测修复后 en 也是 51/51 真验证(此前是 0 通过 / 52 跳过)。 3. d2x_assert_eq 的日志分支仍用裸 std::to_string,而上报分支已改用 SFINAE 安全的 show()。std::to_string 没有 std::string / const char* / scoped enum 的重载,下一个比较字符串或强类型枚举的练习会直接编译失败 —— show() 存在的意义就是避免这个,却只用了一半。 教学漂移(04-rvalue-references): C++17 起 prvalue 直接初始化目标(保证复制省略),-fno-elide-constructors 再也无法让 `Object obj = Object();` 产生移动构造。全仓库改按 c++23 编译后, 这节课的核心观测点被静默抹掉。 改成从具名对象 std::move —— 这在任何标准下都必然调用移动构造。更重要的是 补了一条 d2x_assert(move_ctor_calls >= 1):漂移之所以能静默发生,正是因为 从前没有任何断言检查它,输出少一行没人发现。现在它是 CI 可检测的事实。 * fix(dslings): 04-rvalue-references 练习侧同步修复,并给 e2e 加脏树防护 上一次提交只带上了 solutions/ 侧 —— 练习侧的补丁被 e2e.sh 的清理钩子 还原掉了,我没复查就提交了。 清理钩子会把参考答案覆盖到练习上再还原,所以它天然会吃掉练习目录里未提交 的改动。这个陷阱已经咬过两次(一次丢了脚手架,一次丢了刚修好的练习), 所以加一道前置检查:练习目录不干净就拒绝运行,并列出是哪些文件。 宁可拒绝,也不能悄悄丢掉别人的工作。 * fix(provider): 报错路径改为相对仓库根,协议里仍给绝对路径 学员看到的断言报错原先顶着一长串 /home/... 前缀 —— mcpp 传给编译器的是 绝对路径,__FILE__ 便是绝对的。生成的清单加上 -fmacro-prefix-map 让它 相对仓库根,噪声消失。 但协议要求绝对路径:d2x 靠 diagnostics.file 打开编辑器、监听文件变更。 所以在协议边界上还原成绝对。展示归展示,定位归定位。 * docs: 添加 Provider 与练习脚手架参考(目录约定、判定机制、已知缺口) * docs: 练习即测试重设计稿(双入口、无宏 harness、mcpp 上游三改动) * docs: 练习即测试迁移计划(试点先行) * feat(harness+pilot): 无宏 harness 包 + cpp14 试点迁移(练习即测试链路验证通过) * feat(provider): 瘦身为 mcpp test JSON 薄壳——清单生成/自建判定/unsetenv workaround 全部删除 * feat: dslings 全量迁移到「练习即测试」布局(104 练习 + 51 答案 + 7 成员工程) * fix: 迁移清尾——hello-mcpp 教学文本重写+答案、NULL/int8_t/dont_delete_this、harness formattable 探测 * test(e2e): 重写为 mcpp test 驱动——pristine 0-pass + 52/52 答案全绿(zh/en),防线保留 * docs: book/撰稿技能/CI/gitignore 全面同步「练习即测试」布局 * docs(skill): chapter 模板路径同步 * docs: 迁移后参考文档 + 旧文档标记取代 + 计划勾选 * refactor: harness→d2x 库(import d2x)、课程工程收进 src/、日志标识去 HONLY 重设计 * docs: 术语统一(学员→学习者) + 判定顺序注释与实现对齐 * fix: hello-mcpp 恢复未完成态(体验时的通关改动被误提交,pristine 断言抓出) + 参考文档同步新布局 * refactor: Provider 拍平到 d2x/buildtools(去掉冗余的 mcpp 目录层) * docs: 注释与教学文案专业化(消除口语化表达) * chore: mcpp pin 0.0.99 -> 0.0.104(测试能力批次已发布,CI 依赖满足) * chore: d2x pin 0.1.5 -> 2026.07.24.1(协议层分库/稳定性批次已发布) * docs: 获取与使用对齐 d2x 2026.07.24.1(status/原生模式双入口;d2x update 幽灵命令改为 git 说明;三语 README 快速开始)
1 parent 1c37ddd commit 247f7d6

258 files changed

Lines changed: 3883 additions & 2669 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/docs/2026-07-19-mcpp-replace-xmake-research.md

Lines changed: 416 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# d2mcpp Provider 与练习脚手架 参考
2+
3+
- 日期:2026-07-20
4+
- 分支:`feat/mcpp-provider`
5+
- 配套调研:[`2026-07-19-mcpp-replace-xmake-research.md`](2026-07-19-mcpp-replace-xmake-research.md)(xmake → mcpp 的可行性论证与 rustlings/cargo 横向对照)
6+
- d2x 侧协议规范:d2x 仓库 `.agents/docs/2026-07-20-d2x-architecture-reference.md`
7+
- 本文定位:**当前实现的参考手册**——目录约定、生成物、判定机制、维护须知
8+
9+
10+
> **⚠️ 2026-07-23 起本文的 §4(清单生成)与 §5(判定机制)已被「练习即测试」迁移取代,现状见 [`2026-07-23-exercises-as-tests-reference.md`](2026-07-23-exercises-as-tests-reference.md)**
11+
---
12+
13+
## 1. 全景
14+
15+
xmake 已完全退役。构建链路是:
16+
17+
```
18+
d2x(通用框架,零内置后端)
19+
└─ Provider Protocol (NDJSON) ─→ d2x/buildtools/mcpp/ ← 本仓库自己的 Provider(C++26)
20+
├─ 目录约定发现练习
21+
├─ 生成 mcpp workspace 清单
22+
├─ 调 mcpp build/run
23+
└─ 读侧信道判定通过
24+
25+
mcpp(未做任何改动)
26+
```
27+
28+
### 提交进仓库的包
29+
30+
```
31+
mcpp.toml [workspace] members = ["d2x/buildtools/mcpp", "dslings/harness"]
32+
d2x/buildtools/mcpp/ Provider,standard = "c++26"
33+
dslings/harness/ 练习脚手架库,standard = "c++23"
34+
```
35+
36+
### 生成物(gitignore)
37+
38+
```
39+
.d2x/build/mcpp.toml 独立 workspace 根
40+
.d2x/build/<std>/mcpp.toml 该标准下全部 target —— 供 clangd
41+
.d2x/build/_current/mcpp.toml 只含当前一题 —— 供 checker
42+
.d2x/build/_current/result.ndjson 判定用侧信道
43+
.d2x/state.json 学习者进度(d2x 写)
44+
```
45+
46+
---
47+
48+
## 2. 引导:零脚本、Windows 安全
49+
50+
`.d2x.json`
51+
52+
```json
53+
{ "buildtools": "mcpp run -q -p d2x/buildtools/mcpp --" }
54+
```
55+
56+
d2x 拼接后得到 `mcpp run -q -p d2x/buildtools/mcpp -- check <id>`**从仓库根执行、无需 cd、首次自动构建 Provider**,暖开销约 26ms。
57+
58+
d2x 从不 `chdir`(它在静态初始化时捕获 CWD),所以免 `cd` 是硬要求。注意 `mcpp``-p` 匹配的是**目录 basename 或完整相对路径**,不是包名。
59+
60+
### ⚠️ 嵌套 mcpp 与 `LD_LIBRARY_PATH`
61+
62+
`mcpp run` 会把 `LD_LIBRARY_PATH` 指向它私有的 glibc 并注入子进程。Provider 再去 spawn 嵌套的 `mcpp` 时被迫加载错配的 glibc,**在动态链接器里段错误**(冷启动稳定复现,输出只剩 `<pid>:\t__vdso_time` 这样的 trace 残片)。
63+
64+
`runner.cppm` 在每次 spawn 前 `unsetenv("LD_LIBRARY_PATH")` 绕过;mcpp 会为它自己的子进程重新设置正确的值。
65+
66+
d2x 侧对同一问题早有相同处理(`platform.cppm`),仓库里还留着 `workaround_ld_library_path_issue` 分支——**这是 mcpp 的既有问题,值得单独向上游报**
67+
68+
---
69+
70+
## 3. 练习发现:目录约定 + 就近指令
71+
72+
**没有声明文件。** 放一个 `.cpp` 进去就是全部工作。
73+
74+
rustlings 最贵的一课是 PR #1355:Rust edition 同时写在 `rustc` 参数和 `rust-project.json` 里,两边漂移酿成 bug。**任何独立声明文件都是第二套真相源。** 这里的真相只有两处,且都无法漂移:目录结构,和练习文件自己的头部注释。
75+
76+
### 目录布局
77+
78+
```
79+
dslings/hello-mcpp.cpp 入门练习(chapter = intro)
80+
dslings/<std>/NN-topic-K.cpp zh 练习
81+
dslings/en/<std>/NN-topic-K.cpp en 练习(lang=en 时启用,与 zh 互斥)
82+
solutions/<std>/NN-topic-K.cpp 参考答案(zh/en 共用同一份)
83+
```
84+
85+
### id / order / chapter 的推导
86+
87+
| 文件 | id | chapter | order |
88+
|---|---|---|---|
89+
| `dslings/cpp11/00-auto-and-decltype-0.cpp` | `cpp11-00-auto-and-decltype-0` | `cpp11/00-auto-and-decltype` | 1100000 |
90+
| `dslings/cpp11/03-trailing-return-type.cpp` | `cpp11-03-trailing-return-type` | `cpp11/03-trailing-return-type` | 1100300 |
91+
| `dslings/hello-mcpp.cpp` | `hello-mcpp` | `intro` | -100000 |
92+
93+
`order = std_rank × 100000 + 章节号 × 100 + 练习序号`。尾部 `-<数字>` 是序号,没有就当 0(仓库里 03/04 两章确实缺 `-0` 后缀,规则把它们归一化)。
94+
95+
### 每练习编译选项
96+
97+
写在练习文件自己的头部注释里(只扫前 40 行):
98+
99+
```cpp
100+
// d2x:cxxflags: -O0 -fno-elide-constructors
101+
```
102+
103+
仓库现存用例:`04-rvalue-references`(观察移动)、`07-constexpr-0``-Wpedantic -Werror` 让 VLA 成为错误)。
104+
105+
### id 白名单
106+
107+
id 只允许 `[A-Za-z0-9._-]`,违反者**被拒绝并打印到 stderr**,不进入枚举。
108+
109+
因为 id 有两个危险去向:d2x 拼进 shell 命令、我们写进生成的 TOML(`[targets.<id>]`)。在源头挡住比在两个下游各自转义更可靠——这类文件名本就是笔误或恶意 PR,与其想办法安全传递,不如让作者改名。
110+
111+
---
112+
113+
## 4. 双 member 布局
114+
115+
**硬约束:dslings 的练习默认就编译不过**(49 个带 `D2X_YOUR_ANSWER`),而 mcpp 的 `build``run` 都会先全量构建整个包——一个坏兄弟拖垮全部且零产物。实测:
116+
117+
```
118+
member 含全部 3 题(后两题未做完)→ mcpp build -p → exit=1 ← 当前这题被拖挂
119+
只生成当前这一题 → 0.071s,exit=0
120+
```
121+
122+
**member 粒度(标准/特性/练习)本身不解决逐题隔离。** 所以分两个:
123+
124+
| 目录 | 内容 | 用途 |
125+
|---|---|---|
126+
| `.d2x/build/<std>/` | 该标准下全部 target | clangd —— 即使多数练习编译不过,mcpp 仍生成完整 `compile_commands.json`,IDE 对每题都有正确编译参数 |
127+
| `.d2x/build/_current/` | 只含要验证的那一题 | checker |
128+
129+
实测动态改写清单代价极低:切题 0.118s、切回 0.018s,**fingerprint 目录始终只有 1 个**(改写 target 集合不会让缓存爆炸)。
130+
131+
### 其他生成细节
132+
133+
- `main``../../../` 逃逸出包根,**练习源文件原地不动**
134+
- 脚手架走 `[dependencies] path` 依赖,而非把仓库根塞进 include 搜索路径(后者会让练习能 `#include` 仓库里任何文件)
135+
- `-fmacro-prefix-map=<repo_root>/=``__FILE__` 相对仓库根,学习者看到的报错不再顶着一长串 `/home/...`
136+
- 所有清单**内容比对后才落盘**,不推进 mtime,保住 mcpp 的快速路径
137+
- `check` 会先 `write_full``write_current`——全新仓库上学习者直接跑 `d2x checker` 时根清单还不存在,否则 mcpp 报退出码 2
138+
139+
---
140+
141+
## 5. 判定机制
142+
143+
### 侧信道,不是扫 stdout
144+
145+
判定原先 100% 是带内信号:断言失败**并不改变退出码**(实测 exit=0),所以只能扫 stdout 找 ``。这会误判——一个断言全过的正确解答,只要在说明文字里打了个 `` 就被判失败(已复现)。反过来,输出截断或 Windows 控制台代码页不对,判定同样失效。
146+
147+
现在 stdout 回归「给人看」,判定走侧信道:
148+
149+
```jsonc
150+
{"kind":"assert","ok":false,"expr":"a == b","expected":"1","actual":"2",
151+
"file":"dslings/cpp11/xx.cpp","line":33}
152+
{"kind":"wait","file":"dslings/cpp11/xx.cpp","line":40}
153+
```
154+
155+
**逐条追加而非退出时统一写** —— 练习段错误时,崩溃之前的断言结果照样保留。
156+
157+
`D2X_RESULT_FILE` 环境变量未设置时 harness 只打印不写文件:**学习者直接跑二进制零摩擦**
158+
159+
### 判定顺序
160+
161+
| 侧信道内容 | Verdict |
162+
|---|---|
163+
|`ok:false` | `fail` + 每条失败转一个 `Diagnostic` |
164+
| 无失败但退出码非 0 | `fail` |
165+
| 无失败、有 `wait` | `blocked` |
166+
| **文件不存在**(练习没用 harness) | 退回「编译通过 + 退出 0 = `pass`|
167+
168+
最后一条让 **harness 自动变成可选的**,不需要额外机制:纯观察型练习(现有 18 个只用 `D2X_WAIT`)可以写成零依赖的纯 C++ 文件,学习者能原样拷进 Compiler Explorer。
169+
170+
### 路径:展示 vs 定位
171+
172+
侧信道里的 `file``-fmacro-prefix-map` 是相对路径(学习者看着舒服),但协议要求绝对路径(d2x 靠它开编辑器、监听变更)。**在协议边界上还原成绝对。**
173+
174+
---
175+
176+
## 6. 练习脚手架 `dslings/harness/`
177+
178+
按库设计,目前住在本仓库,将来可整体抽成独立 mcpp 包给其他 C++ 课程复用。
179+
180+
### 两条路径不等价
181+
182+
| 设施 | `#include <d2x/cpp/common.hpp>` | `import d2x.harness;` |
183+
|---|---|---|
184+
| 断言 | `d2x_assert` / `d2x_assert_eq`| `d2x::check` / `check_eq``source_location` 自动带 file/line |
185+
| 路障 | `D2X_WAIT` | `d2x::wait()` |
186+
| 填空占位 | `D2X_YOUR_ANSWER` | **无等价物** |
187+
188+
**宏无法跨模块导出**,这是 C++ 的硬约束而非疏漏。`D2X_YOUR_ANSWER` 必须展开为空才能制造编译错误,本质就是宏。**cpp20/cpp23 等模块化章节需要另设填空约定——这是个尚未决策的缺口。**
189+
190+
`include/` 下保持 `d2x/cpp/common.hpp` 这个既有路径,所以 104 个练习和书本一行都不用改。路径改名可以以后单独做。
191+
192+
### 维护须知
193+
194+
`d2x_assert_eq`**所有分支**都必须走 `d2x::detail::show()`,不能用裸 `std::to_string`——后者没有 `std::string` / `const char*` / scoped enum 的重载,一旦有练习比较这些类型就会硬编译失败。(曾经上报分支用了 `show()` 而日志分支没用,是个潜伏的地雷。)
195+
196+
---
197+
198+
## 7. 测试
199+
200+
### `d2x/buildtools/mcpp/tests/e2e.sh`
201+
202+
断言两件事,缺一不可:**每个练习未完成时不通过****每个参考答案放进去后通过**。等价于 rustlings 的 `cargo dev check --require-solutions`
203+
204+
走的是 `d2x checker` 内部同一条 Provider 路径,所以绿灯等价于学习者本地能跑通,而非另一条平行路径。
205+
206+
**两道防线:**
207+
208+
1. **防空转**——`pass == 0` 时直接判失败。旧 CI 就是这么绿了很久的:它只挑 `-ref` 目标,而 `solutions/` 早被注释掉,循环一次都没进,job 照样退出 0;第 60 行的 `wc -l` 对空串还打印 "Found 1 reference targets" 把空转掩盖了。同类问题在本脚本自己身上也犯过一次(`${sol#en/}` 剥离顺序错,导致所有英文答案静默 SKIP)。
209+
210+
2. **脏树检查**——练习目录有未提交改动时**拒绝运行**并列出文件。脚本会把参考答案覆盖到练习上再还原,天然会吃掉未提交的改动,这个陷阱咬过两次。
211+
212+
### 当前状态
213+
214+
|| 结果 |
215+
|---|---|
216+
| Provider 枚举 | 52 题(zh:1 + 49 + 2;en 同样 52) |
217+
| 端到端 zh | 51/51 参考答案通过 |
218+
| 端到端 en | 51/51 参考答案通过 |
219+
| d2x session 单测 | 28/28 |
220+
| 事件流 | 纯 JSON,0 污染 |
221+
| 注入防护 | 恶意文件名被拒绝,命令未执行 |
222+
223+
---
224+
225+
## 8. C++ 标准与教学漂移
226+
227+
**所有练习按 `c++23` 编译。** mcpp 目前硬拒 `c++11/14/17/20``src/manifest/types.cppm` 的白名单),本方案选择不改 mcpp 上游。因此 `cppNN/` 目录现在表示**特性引入于哪个标准**,不再改变编译参数。
228+
229+
### 已知代价
230+
231+
`04-rvalue-references` 的移动构造教学点。C++17 起 prvalue 直接初始化目标(保证复制省略),`-fno-elide-constructors` 再也无法让 `Object obj = Object();` 产生移动构造。
232+
233+
**修法:** 改成从具名对象 `std::move`(任何标准下都必然调用移动构造),并补一条 `d2x_assert(move_ctor_calls >= 1)`
234+
235+
**更重要的是那条断言。** 漂移之所以能静默发生,正是因为从前没有任何断言检查它——输出少一行没人发现。现在它是 CI 可检测的事实。
236+
237+
> **教训:教学点如果没有断言覆盖,它的消失就是不可见的。** 新增练习时应问:这一课的核心观测点,有断言在守着吗?
238+
239+
量化过的风险面:51 个参考答案在「原标准 vs c++23」下逐一编译并 diff 运行输出,**50 个完全一致,仅此 1 个漂移**。但随着课程往 cpp17/20 扩写,这类静默漂移只会变多。
240+
241+
---
242+
243+
## 9. 撰稿流程的变化
244+
245+
`.agents/skills/d2mcpp-authoring/` 已同步更新:
246+
247+
- **练习不再需要注册**——放进 `dslings/<std>/` 即被目录约定发现
248+
- 新增 `<std>` 章节只需建目录 + 改两个 `SUMMARY.md`,无构建文件接线
249+
- 每练习编译选项改为文件头部 `// d2x:cxxflags:` 指令
250+
- 验证命令改为 `mcpp run -q -p d2x/buildtools/mcpp -- check <id>` 或整体 `bash d2x/buildtools/mcpp/tests/e2e.sh`
251+
252+
---
253+
254+
## 10. 已知缺口
255+
256+
| 缺口 | 影响 |
257+
|---|---|
258+
| **模块化练习的填空占位符无约定** | `D2X_YOUR_ANSWER` 是宏,跨不了模块边界;cpp20/cpp23 章节需先决策 |
259+
| **macOS / Windows 从未验证** | `_popen``_putenv_s``#ifdef` 分支全是纸面推断 |
260+
| **新 CI 从未真跑过** | workflow 是手写的,`xlings install -y` 在 CI 环境能否装上 mcpp 未验证 |
261+
| **`diagnostics` 只覆盖运行期断言** | 编译错误尚未解析成结构化诊断 |
262+
| **Provider 侧无单测** | discovery 的 id/order/chapter 推导、manifest 生成都只被端到端间接覆盖 |
263+
| **`hello-mcpp` 无参考答案** | e2e 只能 SKIP 它,入门练习实际未被验证 |
264+
| **`solutions/` 无 en 镜像** | 设计如此(共用一份),但意味着 en 专有的措辞错误无法被答案校验发现 |

0 commit comments

Comments
 (0)