使用 std::concepts 可实现早报错、精确定位,避免冗长 SFINAE;通过 concept + requires 定义约束(如 Addable),支持前置约束(void func(Addable auto x))或模板参数约束(template),组合用 &&/|| 但不可否定,需按语义自定义而非滥用标准 concept。

直接用 std::concepts 写模板约束,不是为了“炫技”,而是为了早报错、好定位、少写 std::enable_if_t 和冗长的 SFINAE 表达式。它在编译期就卡住不合法的类型,而不是等到实例化失败才抛出几十行看不懂的错误。
如何定义一个基础 concept
用 concept 关键字 + requires 表达式声明约束条件,核心是“这个类型必须能干成什么事”。
-
requires块里写的是表达式(比如os ),不是布尔判断;编译器会尝试解析它是否合法 - 返回类型约束要用
-> std::same_as<t></t>这类标准 trait,不能写-> bool或省略 - 别在
requires里调用未声明的函数或访问私有成员——它只做语法/可见性检查,不做语义分析 - 示例:要求类型支持加法且结果可赋值给自身
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
怎么把 concept 用在函数模板上
有两种主流写法,行为一致但可读性和错误提示略有差异:
- 前置约束(推荐):
void func(Addable auto x)—— 错误信息最清晰,直接指出 “std::vector<int></int>does not satisfy ‘Addable’” - 模板参数列表约束:
template<addable t> void func(T x)</addable>—— 更接近传统模板写法,但嵌套多层时括号易乱 - 不能混用:比如
template<typename t> void func(Addable auto x)</typename>是非法的,T没被约束 - 多个参数可用不同 concept:
void process(InputIterator auto first, Sentinel auto last)
组合多个 concept 的常见陷阱
用 &&、||、! 组合没问题,但要注意求值顺序和短路逻辑不生效(因为是编译期常量表达式):
立即学习“C++免费学习笔记(深入)”;
-
Arithmetic && DefaultConstructible等价于两个都必须满足,任一不满足就失败 -
Arithmetic || std::derived_from<t numberbase></t>可以放宽约束,但注意||左右两边都要能独立解析,否则编译不过 - 避免写
!Copyable来排除可拷贝类型——C++20 不支持否定 concept,得另写一个MoveOnly显式定义 - 别依赖概念名暗示语义:比如自定义
Sortable,不代表std::sort就能用它;标准算法不认你写的 concept
和标准库 concept 混用时要注意什么
<concepts></concepts> 头文件里预定义了大量实用 concept,但它们不是“万能胶”,要按需选用:
-
std::integral和std::floating_point是类型分类,不是“能算术运算”的保证(比如std::complex<int></int>不满足前者但能加) -
std::equality_comparable要求a == b和b == a都合法且返回bool,但不要求==真的实现相等逻辑 -
std::ranges::input_range依赖迭代器类别,传入std::vector<int>::iterator</int>没问题,但传int*需确认是否满足std::input_iterator - 别把
std::regular当“万能安全类型”用——它要求可复制、可比较、可默认构造,很多自定义类型天然不满足
真正难的不是写对 syntax,而是想清楚“我到底要约束什么行为”,而不是照抄某个 concept 名字。比如你要传一个能被 std::cout 打印的类型,就该自己写 Printable,而不是硬套 std::streamable(它并不存在)。


















