C++模板编译期循环展开:从递归到折叠表达式的实战指南
写 C 高性能代码最烦的就是同一段循环在不同编译器和优化选项下给你生成完全不同的汇编。你明明想让 8 次、16 次、32 次固定迭代老老实实摊开编译器却可能缩成一个有进位、有计数器、有跳转的运行时循环反过来你想让编译器自己展开它又可能在-Os下连理都不理你。“模板编译期循环展开”就是我自己解决这个纠结最顺手的一种方式把循环次数直接拧成模板参数让编译器在编译期就把循环体铺开。这篇文章就聊聊我实际用过、踩过坑的几个写法包括如何控制模板深度、如何防止代码膨胀、以及展开后反而变慢时怎么排查适合正在做嵌入式、图形、音视频或写加密轮函数的 C 开发者参考。1. 模板编译期循环展开先搞清楚它到底改了什么1.1 一个“把次数变成类型”的类比运行时循环展开通俗一点说就像流水线上工人拿一卷布一卷一卷往前铺地板模板编译期展开则是事先把每一块地板的位置都量好、标号、切好再一次性拼接。前者由CPU在运行期间反复执行“判断是否到终点”的指令后者是在编译阶段就把“次数”这个信息刻进类型里运行时只剩下排好的操作序列。在模板代码里这种展开通常不是靠写很多遍重复代码而是靠“递归模板实例化”或“参数包展开”实现的。比如你要对 8 个元素逐个执行某个操作运行时写法是for (int i 0; i 8; i) f(i)编译期写法则是让编译器看到f(0); f(1); f(2); ... f(7);这样的展开效果但源代码里依然只有一个通用定义次数由std::integral_constantsize_t, I这样的编译期整数决定。这里有个关键点循环展开本身不是模板元编程的专利编译器自动也能做。但模板展开的价值在于“确定性和可控性”。你可以决定展开到第几层、按什么粒度分块、每一轮拿到的是编译期常量还是运行时变量。只要代码写对了无论优化开关怎么变源代码层面的展开已经固定下来编译器后续优化只是锦上添花。1.2 模板展开和编译器自动展开真不是一回事很多人第一次接触模板循环展开时会觉得反正开-O2编译器也会做 unroll我写模板不是多此一举吗实测下来这个想法在简单循环里确实不冤但在几种特定场景下自动展开会失效循环体里有break、continue、提前返回自动 unroller 往往倾向于保守处理。循环次数来自一个运行时变量哪怕调用方实际传入的是常量优化器在跨翻译单元看不到调用点时也拿不到值。循环体内部有依赖循环下标i的分支、数组索引或查表操作自动展开后不一定能把i的每个具体取值都往前传播。模板展开恰好把这些问题从“优化器努力猜”变成“源代码明确告诉你”。比如我在写固定阶数 FIR 滤波时i的每一个取值都是模板参数I那么coef[I]的地址偏移就是编译期常量编译器可以直接分配寄存器甚至可以跨轮常量折叠。自动循环展开也能做到一部分但如果函数体变大、优化启发式认为“展开不划算”它就随时可能放弃。模板展开赢在确定性上而不是赢在“比编译器聪明”。另外还有一个实际工程优势模板展开的代码可以在不同编译器下保持稳定行为。我在 MSVC、GCC、Clang 三个编译器下跑同一套轮函数展开生成的汇编虽然不完全一样但起码“该展开的都展开了”不会出现“GCC 展开 8 次、MSVC 只展开 4 次”这种需要逐平台调优的局面。2. 三种可复现的模板展开写法与使用场景2.1 递归模板展开适合需要逐轮传递状态的场景最早、也最容易理解的模板展开方式是递归模板函数。它的思路是每一轮操作之后用Round 1去实例化下一轮函数直到一个特化或if constexpr条件把它截断。这类写法特别适合“第 N 轮结果依赖第 N-1 轮结果”的场景比如很多哈希轮函数、伪随机数生成器、逐轮状态更新的算法。#include bit #include cstdint #include utility templatestd::size_t Round, std::size_t Total, typename F void compile_time_round(uint32_t state, F f) { if constexpr (Round Total) { // Round 是编译期常量f 的每一轮参数在这里都是可见的常量 state std::rotl(state, Round) ^ f(Round); // 状态传递到下一轮下一轮看到的仍是编译期常量 Round 1 compile_time_roundRound 1, Total(state, std::forwardF(f)); } } // 调用示例展开 16 轮每轮都拿到编译期轮号 void demo(uint32_t state, auto f) { compile_time_round0, 16(state, std::forwarddecltype(f)(f)); }这段代码里每一轮的Round都是编译期常量所以std::rotl(state, Round)的位移量是常数f(Round)如果内部有查表或分支同样可以随常量折叠。代价是编译器会实例化Total个不同签名的函数模板。当Total上千时递归实例化深度会接近调用链长度容易顶到模板深度上限所以我用它时一般只处理 64、128 这类中等次数。需要注意的是这里不能用普通的for循环替代因为Round一旦变成运行时intstd::rotl(state, Round)就变成一个运行时位移量后续很多常量折叠就断了。循环展开要的就是“轮号本身是编译期常量”这个性质。2.2 index_sequence 加折叠表达式最常用的通用写法现代 C 里我更常用的基础工具是std::make_index_sequence配合 C17 的折叠表达式。它的思路是靠编译器生成0, 1, 2, ..., N-1这一串整数然后通过逗号折叠把它们一个接一个地传给同一个f。由于参数包展开天然是“平铺”的不像递归那样产生深度嵌套所以对模板深度友好得多也更容易阅读。#include utility #include type_traits templatestd::size_t N, typename F void unrolled_for(F f) { []std::size_t... I(std::index_sequenceI...) { // 逗号折叠依次执行 f(I0), f(I1), ..., f(I_{N-1}) (f(std::integral_constantstd::size_t, I{}), ...); }(std::make_index_sequenceN{}); } // 用法对 8 个固定下标做乘法累加 float dot8(const float* x, const float* c) { float acc 0.f; unrolled_for8([](auto idx) { constexpr std::size_t i decltype(idx)::value; acc x[i] * c[i]; }); return acc; }如果你用的是 C17 而不是 C20也可以把模板 Lambda 改成一个带模板参数包的辅助函数效果一样。关键是decltype(idx)::value让 lambda 内部重新拿到编译期下标i。这样x[i]、c[i]的地址偏移都是编译期常数编译器可以把它们当作直接寻址来调度而不会为了一个可变下标额外生成 index 计算指令。我第一次用这个写法时最大的感受是代码可读性比递归模板高多了。递归展开要一层层去看终止条件而这个unrolled_forN几乎和for (i 0; i N; i)一样自然。遇到要展开 32 轮、64 轮的任务我一般优先选它。2.3 C20 模板 Lambda 与后续标准写法还能更顺手C20 带来的模板 Lambda 让循环展开的实现又薄了一层。上面unrolled_for里那个[]std::size_t... I(std::index_sequenceI...)就是模板 Lambda 的典型用法它可以出现在普通函数体内不用再额外定义函数模板。这个能力在多处复用、内联、捕获局部变量时尤其方便。templatestd::size_t N, typename F void unrolled_for20(F f) { []std::size_t... I(std::index_sequenceI...) { (f(std::integral_constantstd::size_t, I{}), ...); }(std::make_index_sequenceN{}); }从 C17 到 C20表面上只差一个语法糖但实际影响是我可以把展开逻辑直接写进一个算法类函数的函数体内不需要反复声明外部模板结构代码的局部性大幅提升。再往后C26 的 pack indexing 会对参数包下标访问更方便不过对“循环展开”这个场景核心模型还是index_sequence fold 表达式短时间内不会过时。如果项目允许现代化标准我也是建议直接上 C20 的写法。一方面if constexpr和模板 Lambda 配合起来更自然另一方面概念concept也能帮你在编译期展开出错时给出更清晰的诊断信息这一点在后面的问题排查部分会展开讲。3. 展开规模怎么定模板深度、代码膨胀与分块策略3.1 模板深度预算怎么算默认值与你踩到的报错模板递归展开不是无限制的。GCC 的默认模板实例化深度在 900 左右Clang 通常 1024MSVC 大概是 500具体值会随版本变化。你可以在编译命令里用参数调整但超过默认值前应该先想清楚“是不是真的需要这么深的递归”。一个经典报错长这样error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depthN to increase the maximum) instantiating ...第一次遇到这个报错时我尝试直接把-ftemplate-depth加到 2048结果编译时间直线上升后期链接也慢。后来我意识到需要打开几千次展开的场景说明设计上已经不适合用纯递归实现了。与其硬顶深度上限不如换成分块展开把“深度”换成“平铺”。有个简单的估算公式递归展开N次需要的模板深度大致是O(log N)还是O(N)取决于实现。像我前面那种一阶递归写法是O(N)深度N1024 时大概率撞线而使用 fold 表达式包展开时所有I...被一次性展开没有递归调用链模板深度压力小得多但编译器解析参数包的开销会变大错误信息也会长得多。所以选择哪种方案本质是在“深度压力”“编译时间”“代码体积”三者之间做权衡。3.2 分块展开把 1024 次展开拆成两层完成面对大次数展开我最常用的方案是“块展开”外层用一个模板参数包控制块数内层再用另一个参数包控制每块内的次数。这样总的递归深度只有两层不管总次数是 1024 还是 8192都能稳定编译。templatestd::size_t Start, typename F, std::size_t... I void run_block(F f, std::index_sequenceI...) { // 每个块内部依次执行 Start0, Start1, ... (f(std::integral_constantstd::size_t, Start I{}), ...); } templatestd::size_t Block, typename F, std::size_t... B void run_blocks(F f, std::index_sequenceB...) { // 外层依次切换块起始位置 (run_blockB * Block(f, std::make_index_sequenceBlock{}), ...); } templatestd::size_t Total, std::size_t Block 8, typename F void block_unrolled_for(F f) { constexpr std::size_t full Total / Block; constexpr std::size_t tail Total % Block; if constexpr (full 0) run_blocksBlock(f, std::make_index_sequencefull{}); // 尾部剩余次数单独展开 if constexpr (tail 0) run_blockfull * Block(f, std::make_index_sequencetail{}); }比如block_unrolled_for24, 8(f)外层展开 3 个块每块内部展开 8 次总共 24 次操作但模板实例化深度只有两层。这个方案我在嵌入式那边用得很顺手因为小块数量可以从 8 调到 4 或 16找到一个当前平台指令缓存能接受的平衡点。分块展开还有一个好处代码体积管理变得可预期。如果你担心一个unrolled_for512一次性展开会生成太多指令用block_unrolled_for512, 8或者block_unrolled_for512, 4就能把体积切成你自己想看的大小。这里Block的选择就是“展开粒度”通常 4 到 8 是性能和体积的平衡区后续实测对比里我也给出了一组数据。3.3 一次带参数的量化对比并非展开越多越好我在一台 x86-64 机器上用 GCC 12、-O2做过一次简单对比分别用运行时循环、unrolled_for4/8/16/32处理同一个固定 8 次 FIR 点积。结论先说编译器自动展开的版本已经非常接近模板展开版本差距在 5% 以内有些情况下自动展开还会更好因为它可以根据流水线压力动态选择展开因子。但真正拉开差距的是把“轮号”用到算法内部常量的场景。比如带不同轮常量的数据变换每轮查表下标由I决定运行时循环里的i是变量编译器在自动展开时虽然也会把迭代次数常量传播但如果循环体复杂、寄存器不够用展开就可能只做部分。模板展开后I是编译期常量查表偏移、位移量、掩码可以被常量折叠这时性能差距才真正体现出来。还有一次我踩过反面教材把某一轮数据变换展开到 64 轮函数体变得特别长连指令缓存都干爆了结果 64 轮全展开版本比 32 轮版本慢了 12%。从那以后我养成了一个习惯不是无脑往大展开而是把 16、24、32、48、64 几个档位全部测一遍再看代码体积和实际耗时选一个。下面是我当时记录的大致对比表格注意不同 CPU 型号结果可能完全不同实现方式相对耗时生成体积预估说明普通for循环1.00最小依赖编译器自动展开unrolled_for80.97很小适合简单 hot loopunrolled_for320.93中等加密轮函数甜点区间unrolled_for641.08很大指令缓存/预取压力变大这个表格不是普适公式但它说明了一个核心道理模板循环展开是工具箱里的一把好工具但不是所有场景都该开到最大档位。4. 常见问题与排查技巧实录4.1 报错信息几百行怎么快速定位到出错的第 i 个操作模板展开最大的痛点是报错信息爆炸。你在unrolled_for32里写错一个c[i]的类型编译器可能蹦出几百行展开记录每一行都一样只是I不同。你很难分清是第几个实例化出了问题。我自己常用的几个排查手段先降低N从unrolled_for2开始测试逐步往上加确认问题出现在第几次展开附近。在 lambda 内部加static_assert把类型意图直接写出来例如static_assert(std::is_same_vdecltype(c[i]), float)这样报错信息会直接指向具体类型和具体i。用 C20 的 concept 约束F的概念把错误从几十层实例化链提前到函数调用的边界上报错会简洁很多。编译时加上-fmax-errors5之类的选项避免错误信息刷屏保留最前几条关键信息。有一次问题出在std::integral_constantstd::size_t, I{}和 lambda 参数类型不匹配上我当时用auto idx接收但漏写了decltype(idx)::value导致I根本没进入 lambda 内部。这类错误不会在代码表面露出破绽只有靠逐步缩小展开数量才能看出来。4.2 展开之后反而变慢指令缓存和代码体积的代价前面提到过64 轮全展开可能比 32 轮更慢原因主要是指令缓存压力。现代 CPU 前端有 uop 缓存和指令预取器但它们的容量都有限。当展开后的机器码超过一定规模每一次热循环重新进入时指令预取可能反复 miss反而比带个dec/jne尾巴的运行时循环慢。另一种情况是寄存器压力增加。展开后的代码同时在寄存器中保留多个中间值如果某个值只在某一次迭代里用一次展开反而让寄存器分配变差编译器可能被迫多读多存几次栈。所以遇到“展开后变慢”别急着怀疑编译器先看两件事用objdump或 Godbolt 看看展开后的实际指令条数和寄存器分配情况。对比不同展开粒度把数据记录下来。在 ARM 和 x86 上我得到的经验差不多简单算术循环4 到 8 次展开通常足够带函数调用或复杂状态的轮函数16 到 32 次比较稳少数计算极简、循环体极短的场景才值得开到 64 以上。4.3 性能测试被优化器“作弊”如何得到可信的对比数据很多人对比“模板展开 vs 普通循环”时都会遇到一个坑因为展开后的计算是完全确定的优化器发现函数外部根本没有使用结果直接就把整段代码判断为死代码删了。测出来自然是模板展开版本“快得离谱”因为什么都没执行。我自己最常用的办法是把最终结果写到一个volatile变量里强行阻止死代码消除volatile float sink; void bench_dot(const float* x, const float* c) { float acc dot8(x, c); sink acc; // 防止整个计算被优化掉 }如果不想引入volatile也可以把基准函数的指针用于回调比如benchmark::DoNotOptimize(result)效果类似。关键是让优化器无法认定“结果没被使用”才能测出真实耗时。另外在 x86 上跑微基准时还要注意 CPU 频率波动最好固定性能核预热几轮再计时。我见过有人拿chrono直接掐了一段纳秒级循环结果连噪音都比真实差距大最后结论完全颠倒。要可信就用循环内多次调用取平均值或者直接用现成的 benchmark 库。4.4 我现在的选择标准与心得经过这些实践我现在选型时的判断标准很简单循环次数固定且编译器已知内部无复杂状态依赖优先交给编译器的自动 unroll模板不参与。循环次数固定但每轮需要编译期常量下标、常量位移、常量查表或者需要跨轮传播状态才用模板展开。展开规模超过 256 次优先分块绝不盲目调高-ftemplate-depth。代码体积敏感的场景把展开粒度和效率当成一个权衡问题去定量测量而不是拍脑袋定一个“展开快”的经验。还有一个小技巧如果你要维护一个跨编译器项目可以把unrolled_forN这类辅助函数放在公共头文件里并统一使用std::integral_constant传参。这样上位机、嵌入式、GPU 侧不同编译器编译时至少展开逻辑是一致的排查问题时不用把“平台差异”和“代码问题”两件事混在一起猜。模板编译期循环展开本身不复杂真正难的是在“强行展开”和“依赖编译器自动优化”之间找到合理的边界。它更像是一种给编译器提供额外信息的约束手段而不是性能银弹。实测下来方向用对了收益很明显用过头了反而会被指令缓存和编译时间反噬。如果你正在写固定次数的轮函数、滤波器或需要逐轮状态更新的模块不妨从unrolled_for16开始试先把体积和性能数据测出来再决定要不要继续加档。

相关新闻

cls_threshold 一调就灵?GLiNER2.5-Decide 多标签分类的精度/召回跷跷板

cls_threshold 一调就灵?GLiNER2.5-Decide 多标签分类的精度/召回跷跷板

cls_threshold 一调就灵?GLiNER2.5-Decide 多标签分类的精度/召回跷跷板 【免费下载链接】GLiNER2.5-Decide 项目地址: https://ai.gitcode.com/hf_mirrors/fastino/GLiNER2.5-Decide 在多标签分类任务里,GLiNER2.5-Decide(340M 参数…

2026/10/10 15:20:39 阅读更多 →
网页消息提醒音JS落地指南:自动播放解锁与实战避坑

网页消息提醒音JS落地指南:自动播放解锁与实战避坑

简介:网页消息提醒音js是一份面向网页前端开发者的实用示例资源,主要解决页面在收到新消息或事件时准确播放提示音的问题,适合在实时通讯、社交网络、在线协作等需要即时感知的场景中使用。资源压缩包共包含3个文件,有可直接运行的…

2026/10/10 15:19:38 阅读更多 →
Postman × Codex:如何将API集合封装成智能体Skill

Postman × Codex:如何将API集合封装成智能体Skill

直接上一个我最近在空隙时间里折腾完的东西:把 Postman 里的 API 集合,做成 Codex 可以直接调用的智能体 Skill。简单说,就是让 AI 代理能像人一样用 Postman 里的接口去查数据、发请求、跑流程,而不是只能对着文档“空谈”。这个…

2026/10/10 15:19:38 阅读更多 →

最新新闻

GPU算力怎么选?从并行计算到AI大模型实战指南

GPU算力怎么选?从并行计算到AI大模型实战指南

今年明显感觉身边聊GPU算力的人变多了。以前大家问显卡,翻来覆去就是“能不能流畅玩XX游戏”“帧率多少”,现在画风完全不一样了,开口就是“这卡能跑多少B参数的模型”“显存够不够微调”“深度学习吃不吃得消”。说白了,不管游戏…

2026/10/10 21:45:34 阅读更多 →
人脸识别门禁考勤系统毕设实战:OpenCV+MTCNN+FaceNet全解析

人脸识别门禁考勤系统毕设实战:OpenCV+MTCNN+FaceNet全解析

简介:一套完整的人脸识别系统毕业设计资料包,面向计算机视觉、图像处理与模式识别方向的本专科学生,尤其适合需要完成从算法原理到工程实现全流程毕业设计的读者。压缩包共80个文件,约2.18MB,以C源码为主,包…

2026/10/10 21:45:34 阅读更多 →
Agent Router 免费接入 codex、claude 后,如何把 API Key 配置到 CC Switch

Agent Router 免费接入 codex、claude 后,如何把 API Key 配置到 CC Switch

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 21:45:34 阅读更多 →
Operator 之后,开源社区半年追平 OpenAI:『看屏操作』CUA 开源化全面复盘

Operator 之后,开源社区半年追平 OpenAI:『看屏操作』CUA 开源化全面复盘

Operator 之后,开源社区半年追平 OpenAI:『看屏操作』CUA 开源化全面复盘 【免费下载链接】cua Scale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation. 项目地址: https…

2026/10/10 21:45:34 阅读更多 →
红黑树原理与实现:从2-3-4树到插入删除,对比B+树

红黑树原理与实现:从2-3-4树到插入删除,对比B+树

红黑树这名字起得挺贴切,它确实是一门“平衡的艺术”。但这门艺术折磨过的人也不少——网上关于红黑树的博客一搜一大把,有人上来就甩五个性质,有人画各种旋转图,你从头看到尾,脑子说懂了,手一写代码就懵。…

2026/10/10 21:45:34 阅读更多 →
ComfyUI 零插件跑通 H3:文生视频、图生视频、首尾帧一条龙实战

ComfyUI 零插件跑通 H3:文生视频、图生视频、首尾帧一条龙实战

ComfyUI 零插件跑通 H3:文生视频、图生视频、首尾帧一条龙实战 【免费下载链接】Minimax-h3_Singularity 项目地址: https://ai.gitcode.com/hf_mirrors/WarmBloodAban/Minimax-h3_Singularity MiniMax H3 开源后迅速成为开源社区关注度最高的视频生成模型&…

2026/10/10 21:44:33 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →