在 C++20 中,concepts 与 requires 关键字是用于模板约束的两种主要工具,它们虽然目的相同——在编译时对模板参数进行检查,但在语法结构、可读性以及使用场景上有显著差异。本文将对二者进行对比,并给出实际编码示例,帮助你更好地把握何时使用哪一种。
1. 语法概览
| 关键字 | 语法位置 | 作用域 | 典型用法 |
|---|---|---|---|
concept |
先声明再使用 | 在整个模板中 | 定义可复用的约束 |
requires |
直接放在函数/类/模板参数列表中 | 在定义局部 | 直接嵌入约束表达式 |
示例 1:使用 concept
template<typename T>
concept Incrementable = requires(T a) {
{ ++a } -> std::same_as<T&>;
};
template<Incrementable T>
T add_one(T x) { return ++x; }
示例 2:使用 requires
template<typename T>
requires requires(T a) {
{ ++a } -> std::same_as<T&>;
}
T add_one(T x) { return ++x; }
两者功能相同,但约束表达式被直接写在 requires 关键字后面,省去了单独定义 concept 的步骤。
2. 何时使用 concept
- 可重用约束:当同一组约束需要在多处使用时,定义
concept可以避免重复书写,提高可读性和维护性。 - 文档化:
concept名称能直观地表达约束意图(如Incrementable、Sortable等),有助于代码阅读。 - 复杂约束:当约束组合复杂时,将其封装为
concept能降低模板定义的视觉噪音。
template<Incrementable T, Incrementable U>
auto sum(T a, U b) { return a + b; }
3. 何时使用 requires
- 一次性约束:仅在当前模板中使用,且约束不需要复用时,直接使用
requires更简洁。 - 局部特殊化:在特化模板时,
requires能直接表达特化条件。 - 避免命名冲突:若你不想在全局范围定义新名称,或者约束非常短小,使用
requires更为直观。
template<typename T>
requires std::integral <T>
void print(T x) { std::cout << x << '\n'; }
4. 性能与编译器实现
从编译器实现角度,concept 和 requires 本质上都依赖模板元编译。编译器会在模板实例化时检查约束是否满足。大多数现代编译器(GCC 11+、Clang 13+、MSVC 19.33+)对两者实现均已成熟,性能差异可以忽略不计。
5. 兼容性与最佳实践
- 使用
concept定义基础约束,然后在需要的地方通过requires引用它们,既能复用又能保持代码简洁。 - 对于复杂的逻辑,分解成小
concept再组合使用,可显著提升代码可读性。 - 牢记 SFINAE 与 Concepts 的区别:Concepts 的错误信息更友好、编译速度更快,但并非所有旧编译器均支持。
6. 结语
C++20 的 Concepts 与 Requires 为模板编程带来了更强的类型安全与表达力。正确理解两者的语法和适用场景,能够让你的模板代码更加健壮、易读。建议在项目初期就引入 Concepts,逐步用它们替换传统的 SFINAE 方案,从而获得更好的开发体验。