在 C++20 标准中,概念(Concepts)被引入为模板约束(constraint)的语言级别支持,解决了长久以来模板编程中“SFINAE”难以阅读、错误信息模糊的问题。下面从概念的定义、使用方式以及实际场景进行阐述,并给出代码示例。
1. 概念的基本语法
// 定义一个概念
template<typename T>
concept Incrementable = requires(T x) {
{ ++x } -> std::same_as<T&>; // ++x 必须返回 T&
{ x++ } -> std::same_as <T>; // x++ 必须返回 T
};
// 使用概念约束模板参数
template<Incrementable T>
T add_one(T value) {
return ++value;
}
requires子句描述了类型T必须满足的表达式。-> std::same_as<...>用于指定表达式的返回类型(可以用其他标准库的概念,如std::convertible_to等)。Incrementable可以直接用于requires语句或template参数。
2. 组合与继承概念
// 组合概念
template<typename T>
concept Integral = std::is_integral_v <T>;
template<typename T>
concept SignedIntegral = Integral <T> && std::is_signed_v<T>;
// 继承概念
template<typename T>
concept Number = std::is_arithmetic_v <T>;
// 使用
template<Number T>
T square(T value) {
return value * value;
}
3. 通过 requires 关键字做局部约束
template<typename T>
void process(T&& t) requires Incrementable <T> {
// 仅当 T 满足 Incrementable 时才编译
++t;
}
4. 与 SFINAE 的对比
// 传统 SFINAE
template<typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
T multiply(T a, T b) { return a * b; }
// 现代概念
template<std::integral T>
T multiply(T a, T b) { return a * b; }
概念提供了更直观、错误信息更友好的约束方式。
5. 实际应用场景
| 场景 | 传统实现 | 用概念实现 | 优点 |
|---|---|---|---|
| 容器适配 | SFINAE 检测 size()、begin() 等 |
直接约束 std::ranges::range |
更清晰的错误信息 |
| 数值运算 | 手写 requires |
std::floating_point、std::integral |
标准化约束 |
| 多态接口 | 复杂的 enable_if |
requires 语句 |
更易维护 |
6. 常用标准库概念
| 标准概念 | 用途 |
|---|---|
std::integral |
整数类型 |
std::floating_point |
浮点数 |
| `std::same_as | |
| ` | 两个表达式的类型相同 |
| `std::derived_from | |
| ` | 子类约束 |
| `std::convertible_to | |
| ` | 可转换为某类型 |
7. 实战示例:范围适配器
#include <concepts>
#include <ranges>
#include <vector>
#include <iostream>
template<std::ranges::input_range R>
auto sum(R&& r) {
using T = std::ranges::range_value_t <R>;
T total{};
for (auto&& v : r) total += v;
return total;
}
int main() {
std::vector <int> v = {1,2,3,4};
std::cout << sum(v) << '\n'; // 10
std::cout << sum(v | std::views::reverse) << '\n'; // 10
}
此函数仅适用于输入范围(input_range),并在编译时得到准确的错误提示。
8. 潜在陷阱与注意事项
- 递归概念:如果概念彼此递归引用,编译器会报错。避免深层递归。
- 默认参数:
requires子句中的表达式会被实例化;若涉及到大量类型检查,可能导致编译时间上升。 - 命名冲突:与标准库同名概念需谨慎;可使用命名空间隔离。
9. 结语
概念使模板编程更像普通函数编程,约束显式、错误信息友好。随着 C++20 的广泛应用,掌握概念将成为提升代码质量与开发效率的关键。希望本文能帮助你快速上手,写出更安全、可读的模板代码。