Concept 是参与重载决议和编译期筛选的类型契约,需描述“能力”而非仅包装 type trait;应复用标准库 concept(如 std::copyable、std::ranges::range),并理解其检查发生在模板参数推导之后。

直接用 concept 定义约束,再在模板参数位置写上概念名——不是“加个装饰”,而是真正参与重载决议和编译期筛选的类型契约。
定义 concept 时别只写 std::is_integral_v<t></t> 这种单层 trait
单纯包装 type trait(比如 std::is_integral_v<t></t>)虽然合法,但掩盖了语义。真正有用的概念应描述“能力”而非“标签”。
- 错误示范:
template<typename t> concept BadIntegral = std::is_integral_v<t>;</t></typename>—— 它不保证你能对T做任何事,比如+或可能仍失败 - 推荐写法:用
requires表达式检查实际操作,例如template<typename t> concept Addable = requires(T a, T b) { a + b; };</typename> - 复合约束更实用:比如
template<typename t> concept SignedIntegral = std::is_integral_v<t> && std::is_signed_v<t> && requires(T t) { ++t; };</t></t></typename>
函数模板中应用 concept 的三种写法效果不同
写法影响重载优先级和错误触发时机,不能混用还觉得“都一样”。
- 前置约束(最常用):
template<integral t> T add(T a, T b);</integral>—— 编译器在形参解析阶段就检查,失败报错最干净 -
requires子句(适合复用已有 concept):template<typename t> requires Integral<t> T mul(T a, T b);</t></typename>—— 和前置写法行为一致,但语法上更显式 - 尾置
requires(慎用):template<typename t> T sub(T a, T b) requires Integral<t>;</t></typename>—— 仅当其他重载都不匹配时才考虑它,容易被忽略或绕过
requires 表达式里写 { a std::convertible_to<bool></bool> 而不只是 a
只写 a 只检查表达式语法合法;加上返回类型约束才能防止“返回 <code>int 却被当 bool 用”的隐式转换漏洞。
立即学习“C++免费学习笔记(深入)”;
- 松散约束:
requires(const T& a, const T& b) { a —— 允许返回任意可隐式转 <code>bool的类型(比如int),但可能掩盖逻辑错误 - 严格约束:
requires(const T& a, const T& b) { { a std::convertible_to<bool>; }</bool>—— 明确要求返回值语义是布尔判断 - 注意:必须用双大括号包裹表达式,否则编译器会误判为简单要求而非复合要求
标准库 concept 不要自己重造,但得知道它们在哪、怎么查
<concepts></concepts> 头文件里有 std::regular、std::equality_comparable 等,<ranges></ranges> 里还有 std::range、std::input_iterator。它们不是玩具,是生产级接口契约。
- 别重复定义
template<typename t> concept Copyable = std::copy_constructible<t> && std::assignable_from<t const t>;</t></t></typename>—— 直接用std::copyable - 查文档优先看 cppreference 的 “Concepts” 章节,而不是翻源码;每个标准 concept 都有明确的语义定义和满足条件
- 组合用法很常见:
template<:input_iterator it std::sentinel_for> Sent></:input_iterator>是std::ranges::find的真实签名,不是示例
最容易被忽略的是:concept 检查发生在模板参数推导之后、函数体实例化之前。这意味着你无法靠 concept 拦住一个连 auto 都推导不出来的类型——它先得是个合法类型,才有资格被约束。这点和 static_assert 的位置与作用完全不同。


















