
1. 五十行 substitution failure
新人给 filter() 传 int,编译器吐出五十行 vector<_Tp, _Alloc>——维护者是你六个月后的自己,oncall 没人有心情读 SFINAE 残骸。C++20 concepts 把约束写在签名上,失败早、信息短,overload 可按 concept 分派。机器人 math/点云工具库对外 API 尤其该 concept——调用方杂,模板错误成本全在作者身上。Concepts 是把「这类 Cloud 必须有什么接口」写进签名,失败信息从模板元编程转成可读句子。公开头里的每个模板参数都应能回答:「不满足时用户看到什么?」SFINAE 时代靠文档写 requires 列表,concept 时代签名即文档,但 README 仍要与 concept 同步,否则用户读文档传错类型还是踩坑。
2. 标准 concept 与领域命名
floating_point 约束 clamp;ContiguousPoints 要求 data() 与 size()。concept 名跟 domain:PointCloudLike、BufferLike,grep review 能对上模块。floating_point / integral 分开 overload,避免 int 静默 promote 导致 so 里模板实例化爆炸。requires 失败时 clang -fconcepts-diagnostics-depth=3 帮助新人——仍应用 static_assert 友好别名二次提示。concept 文档与 README 一致:签名是机器可检查的文档,README 滞后时应以 concept 为准修文档。
template <class T>
concept ContiguousPoints = requires(T& t) {
{ t.data() } -> std::convertible_to<const float*>;
{ t.size() } -> std::convertible_to<std::size_t>;
};
template <class F, class Cloud>
concept CloudFilter = requires(F f, Cloud& c) {
{ f.apply(c) } -> std::same_as<void>;
};3. CloudFilter 与 pipeline
CloudFilter 要求 f.apply(c) 返回 void;pipeline 组合 filter 时错误类型 compile 在入口。约束 swap noexcept 时可组合 movable<T>。concept 过宽等于没约束;过窄则合法算法实例化失败——negative test static_assert(!CloudFilter<int>) 进 CI。公开 API concept,内部单 TU helper 不必——减少膨胀与编译时间。pipeline 组合函数 template<CloudFilter F> 接受 filter,call site 传错类型时错误指向 CloudFilter 而非内部 Eigen 表达式。
4. 与 SFINAE 迁移
遗留 enable_if 逐步迁 concepts,别双轨长期并存。requires same_as 替换 is_same_v 模板参数列表。so 里 float/double 显式实例化,concept 约束签名,实例化列表控制二进制体积——无限泛型进共享库是 link 时间与体积的双杀。迁移步骤:新 overload 用 concept,旧 enable_if 标记 deprecated,negative compile test 锁住行为,再删 SFINAE 分支。
5. 可读错误是验收标准
故意传错类型:错误信息含 concept 名(ContiguousPoints),而非纯 _Tp。static_assert 作第二道友好提示。团队 onboarding 时「读 concept 名即读文档」比「读 fifty lines instantiate」省半天。concept 与运行时前置条件不一致:签名能过、运行仍失败——requires 写全,别只写 size() 不写 alignment 或 stride。requires 可表达 noexcept、return type、关联类型,把运行时 CHECK 能前移的尽量前移。
6. 失败症状
concept 过宽:错误类型仍 deep template 错误。一切函数都 concept:编译变慢,收益不成比例。concept 与运行时前置条件不一致:签名能过、运行仍失败——requires 写全,别只写 size() 不写 alignment。客户定制 Cloud 类型只在 field 暴露问题,说明公开 concept 未覆盖真实调用方——应把该类型纳入 CI negative/positive compile test。
7. 案例:filter 误传 int
voxelDownsample 曾用 SFINAE,新人传 int 报五十行。改成 ContiguousPoints 后,错误一行「int 不满足 ContiguousPoints」——当天 self-service 修完,oncall 少两单。negative static_assert 防止有人把 concept 改宽到「啥都能编译」再复发。PR review 要求:改 concept 必须同时改 negative test,防止「修编译」把约束放松到无效。
8. 验收
传错类型:错误含 concept 名。static_assert 正负例 CI 跑过。公开头 concept 与 README 一致。大项目抽样:相对 SFINAE 编译时间无显著恶化。新公开模板 API PR 必须带 negative compile test(应失败)截图或 CI job。故意传 const Cloud& 给需要 mutable apply 的 concept,应失败并提示 apply 非 const 可行。positive compile test 同样重要:客户常用 Cloud 类型必须能实例化 pipeline entry,否则 concept 过窄会在 field 才暴露。
9. 与维护、录包对齐
模板错误若只出现在客户定制 Cloud 类型上,说明 concept 未覆盖真实调用方——录包复盘时把失败类型名写进 issue。Concepts 让人能读模板 API——约束即文档,失败即评审,六个月后的你会感谢现在的签名。库版本升级时 concept 收紧是 breaking change,应 major bump 并在 changelog 列出「哪些类型不再满足约束」。对外 SDK 可把 concept 名写进错误码或 static_assert 文案,客户 self-service 修集成问题时不必把 fifty-line instantiate 贴给 support——支持成本也是 concept 的隐性收益。
相关
也可以看看
johan's blog