在 C++20 之前,模板参数约束通常通过 SFINAE(Substitution Failure Is Not An Error)实现,代码往往繁琐、可读性差且容易出错。C++20 引入了 Concepts,提供了一种更直观、强类型的方式来限制模板参数,从而提高代码的可维护性和可读性。本文将从概念的定义、实现方式、常用标准概念以及实战案例四个方面,系统阐述如何在 C++20 中使用 Concepts 优化模板参数约束。
1. 何为 Concept?
Concept 是一种约束类型(constraint),用于描述模板参数必须满足的一组逻辑条件。它可以在编译期间对模板实参进行检查,一旦不满足约束,编译器会给出更友好的错误信息,而不是隐式地导致 SFINAE 失败。
基本语法:
template<typename T>
concept MyConcept = requires(T a) {
// 约束表达式
{ a.foo() } -> std::same_as <void>;
{ a.bar() } -> std::convertible_to <int>;
};
requires 子句用于描述在类型 T 上可以执行的操作以及返回值的约束。若这些表达式不成立,则 `MyConcept
` 的值为 `false`。
## 2. 如何使用 Concepts 优化模板?
### 2.1 替换 SFINAE
传统 SFINAE 写法:
“`cpp
template<typename t typename="std::enable_if_t<std::is_integral_v>>
T add(T a, T b) {
return a + b;
}
“`
使用 Concept 简化:
“`cpp
template
T add(T a, T b) {
return a + b;
}
“`
这里 `std::integral` 是 C++20 标准库提供的 Concept,直接指定了 `T` 必须是整数类型。
### 2.2 组合多个概念
Concept 可以像布尔表达式一样组合:
“`cpp
template
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as
;
};
template
concept Multipliable = requires(T a, T b) {
{ a * b } -> std::same_as
;
};
template
T sum(T a, T b) {
return a + b;
}
template
T product(T a, T b) {
return a * b;
}
“`
### 2.3 自定义概念与标准概念相结合
自定义概念可以引用标准概念:
“`cpp
template
concept Container = requires(T c) {
{ c.begin() } -> std::input_or_output_iterator;
{ c.end() } -> std::input_or_output_iterator;
};
“`
## 3. 常用标准 Concepts
| Concept | 描述 |
|——–|——|
| `std::integral` | 整数类型 |
| `std::floating_point` | 浮点数类型 |
| `std::default_initializable` | 可默认构造 |
| `std::movable` | 可移动 |
| `std::copyable` | 可复制 |
| `std::destructible` | 可析构 |
| `std::swappable` | 可交换 |
| `std::same_as
` | 与 `T` 相同 |
| `std::convertible_to
` | 可转换为 `T` |
| `std::input_or_output_iterator` | 输入或输出迭代器 |
| `std::derived_from
` | 从 `T` 派生 |
| `std::derived_from` | 从 `Base` 派生 |
| `std::default_initializable
` | 可默认初始化 |
## 4. 实战案例:实现一个通用的容器遍历函数
假设我们需要实现一个遍历容器中元素并执行回调的函数。使用传统方式会显得冗长且易错。使用 Concepts 可以让代码简洁且类型安全。
“`cpp
#include
#include
#include
#include
template
concept IterableContainer =
requires(Container c, Func f, typename Container::value_type val) {
{ f(val) } -> std::same_as
;
{ c.begin() } -> std::input_or_output_iterator;
{ c.end() } -> std::input_or_output_iterator;
};
template
void for_each(const Container& c, Func f) {
for (const auto& elem : c) {
f(elem);
}
}
int main() {
std::vector
v{1, 2, 3};
for_each(v, [](int x){ std::cout lst{“a”, “b”, “c”};
for_each(lst, [](const std::string& s){ std::cout