直接用concept约束模板参数最清晰安全;自定义concept用concept关键字加requires表达式;复用标准库<concepts>中std::integral等内置concept;函数模板可直接用requires短语约束。

直接用 concept 约束模板参数,是最清晰、最安全、错误提示最友好的方式。别再写 std::enable_if 或手动 SFINAE —— 那些是 C++17 时代的绕路方案,现在编译器能直接告诉你“double 不满足 Integral”,而不是抛出半屏无关的模板展开错误。
怎么定义一个自定义 concept
用 concept 关键字 + requires 表达式,描述类型必须支持的操作或属性:
-
requires块里写的是“能合法执行的表达式”,不是返回值检查(除非显式加{ ... } -> std::same_as<t></t>) - 变量名只是占位符,不参与运行时逻辑;
requires(T a, T b) { a + b; }只检查T是否有operator+,不关心a和b的值 - 不要在
requires里调用未声明的函数或访问私有成员——编译器只做语法和可见性检查,不做语义分析
示例:
template<typename T>
concept Addable = requires(T a, T b) {
a + b;
};
<p>template<Addable T>
T add(T a, T b) { return a + b; }
怎么复用标准库内置 concept
头文件 <concepts> 提供了开箱即用的常用约束,比手写更准、更兼容:
立即学习“C++免费学习笔记(深入)”;
-
std::integral:匹配所有整型(int、long long、unsigned char等),但不包括bool或枚举(除非显式特化) -
std::floating_point:仅限float、double、long double,不含std::complex -
std::equality_comparable:要求支持==且结果可转为bool,比手写{ a == b } -> std::convertible_to<bool>更简洁 - 组合多个 concept 用
&&或||,比如std::integral && std::signed_integral表示“有符号整型”
注意:std::is_integral_v<T> 是类型特征(type trait),返回 bool;而 std::integral 是 concept,用于模板约束——二者不能混用。
怎么在函数模板中直接用 requires 约束(不定义 concept)
适合一次性、简单约束,省去命名和复用成本:
- 写法是
template<typename T> requires <条件>,条件可以是内置 concept、自定义 concept,或内联requires表达式 - 内联写法:
requires std::is_integral_v<T>合法,但推荐用std::integral—— 前者是运行期常量表达式,后者是编译期概念检查,语义更正 - 错误定位更细:如果用
requires { a ,报错会指出“<code>a < b不合法”;而用std::totally_ordered则报“不满足全序”
示例:
template<typename T>
requires std::integral<T>
T square(T x) { return x * x; }
容易被忽略的坑:约束位置和重载决议
concept 约束不是注释,它直接影响函数是否参与重载候选集:
- 约束写在模板参数列表后(
template<Integral T>)和写在函数声明后(requires Integral<T>)语义等价,但前者更常见 - 如果两个函数模板都接受
int,一个约束为std::integral,另一个为std::signed_integral,则后者优先匹配——concept 越具体,重载优先级越高 - 别在同一个作用域里定义两个仅 constraint 不同的函数模板并期望它们共存:若约束不互斥(比如
std::integral || std::floating_point和std::integral),可能触发“ambiguous call” - 自定义类想满足某个 concept,必须显式提供对应操作符(如
operator+)或成员函数(如begin()),光有 public 接口不够,还要符合签名和返回类型要求


















