C++怎么使用概念(Concepts)_C++20约束模板【规范】

1次阅读

concepts是c++20引入的编译期模板参数语义约束机制,非运行时检查或接口定义;它通过requires子句或命名concept对类型施加操作可行性约束,提升错误信息可读性,零开销且仅限c++20及更新编译器支持。

C++怎么使用概念(Concepts)_C++20约束模板【规范】

Concepts 是什么,不是什么

Concepts 不是运行时检查,也不是接口定义;它是编译期对模板参数的语义约束。你写 std::sortable,编译器不会去跑排序算法验证,而是检查类型是否提供必需的操作(比如 operator、可迭代、可交换等)。它替代的是过去靠 SFINAE 或 <code>static_assert 出来的模糊报错,让错误信息从“no type named 'iterator' in 'int'”变成“int does not satisfy sortable”。

常见错误现象:把 Concepts 当成类型别名或运行时断言用;或者以为加了 requires 就能自动推导类型——其实它不参与重载解析的优先级排序,只做硬性过滤。

  • 使用场景:泛型容器、算法库、自定义 trait 约束(如 addable<t u></t>
  • 性能影响:零开销——所有检查在编译期完成,生成代码与手动 static_assert 无异
  • 兼容性:仅 C++20 起支持,GCC 10+、Clang 12+、MSVC 19.29+;C++17 项目不能混用

怎么写一个最小可用的 Concept

最简形式就是用 concept 关键字 + 布尔常量表达式。别急着套 std::ranges 那套,先从判断有没有某个成员函数开始:

template<typename T> concept has_size = requires(T t) {     t.size(); };

注意这里 requires 块里写的是“能调用”,不是“返回 int”或“是 const 成员”——那是更细粒度的约束,要额外加条件。

立即学习C++免费学习笔记(深入)”;

  • 容易踩的坑:requires(T t) 中的 t 是纯占位符,不构造对象;但若类型没有默认构造函数,就得改用 requires(std::declval<t>().size())</t>
  • 参数差异:单参数 concept 直接跟在模板参数后;多参数(如 addable<t u></t>)需显式列出并检查 T + U 是否合法
  • 别写 requires std::is_integral_v<t></t> 这种——这是 type trait,不是 concept;要用就封装integral concept

函数模板中用 requires 还是 concept 名字

两者都能用,但语义不同:requires 是内联约束,适合一次性的简单条件;命名 concept 更利于复用和错误提示。

// 推荐:清晰、可读、报错直接显示概念名 template<has_size T> void print_size(T&& c) { std::cout << c.size() << 'n'; }  // 可用但不推荐:约束逻辑散落在函数声明里,难复用 template<typename T> requires requires(T t) { t.size(); } void print_size(T&& c) { std::cout << c.size() << 'n'; }
  • 使用场景:库作者应优先定义命名 concept;临时脚本或调试时可用 requires 快速验证
  • 错误信息差异:前者报错含 has_size,后者只说“requires clause not satisfied”,调试成本高
  • 注意:不能在非模板上下文中用 concept(比如普通函数参数),会编译失败

和 SFINAE、enable_if 比较时的真实取舍

Concepts 不是 SFINAE 的升级版,而是替代方案——它解决的是同一问题的不同侧面。SFINAE 本质是“让错误不发生”,Concepts 是“让错误说得清”。

  • 如果你的模板需要精细控制重载顺序(比如优先匹配 std::vector 而非任意 Container),SFINAE 仍不可替代;Concepts 只做准入,不做排序
  • 如果你的约束逻辑跨多个模板参数(如 T::value_type == U::key_type),用 Concepts 写起来更直白,不用嵌套 decltypestd::declval
  • 兼容旧代码时,别强行把 std::enable_if_t 全换成 concept——尤其当模板已被广泛特化时,约束变更可能破坏 ABI 或导致重载歧义

最常被忽略的一点:Concepts 对模板参数推导本身没帮助。写 template<has_size t> void f(T)</has_size>,传入 std::String{} 没问题;但传入 42,编译器不会因为 has_size 失败就尝试其他重载——它直接报错,不回退。

text=ZqhQzanResources