C++ 概念编程(Concepts)详解
C++20 引入的概念(Concepts) 是对模板类型约束的标准化、可读性更强的实现,核心目的是:
- 替代传统的
static_assert/SFINAE 等晦涩的模板约束方式; - 让模板参数的要求显式化、可复用;
- 编译期精准匹配模板重载,提供更友好的错误提示。
一、核心概念
1. 概念的本质
概念是编译期布尔谓词,用于约束模板参数(类型/非类型/模板模板参数),只有满足谓词的参数才能实例化模板。
2. 关键语法
| 语法元素 | 说明 |
|---|---|
concept | 定义概念的关键字(C++20 新增) |
requires | 约束子句,用于定义概念的具体要求(可独立使用或嵌入概念) |
std::same_as | 标准库基础概念(判断类型相同),定义于 <concepts> |
std::integral | 标准库概念(判断整数类型),定义于 <concepts> |
二、基础用法
1. 定义概念
语法:
template <typename T>
concept 概念名 = 约束条件;
示例1:基础类型约束
#include <concepts> // 必须包含标准概念头文件
#include <iostream>
// 定义概念:T 是可打印的(支持 << 输出)
template <typename T>
concept Printable = requires(std::ostream& os, const T& t) {
// requires 表达式:检查表达式是否合法(编译期)
os << t; // 要求 T 支持流输出
};
// 定义概念:T 是整数且可打印
template <typename T>
concept IntPrintable = std::integral<T> && Printable<T>;
2. 使用概念约束模板
方式1:模板参数列表中约束
// 仅接受满足 IntPrintable 的类型
template <IntPrintable T>
void print_int(T val) {
std::cout << "Integer value: " << val << std::endl;
}
方式2:requires 子句约束
template <typename T>
requires Printable<T> // 独立 requires 子句
void print(T val) {
std::cout << "Value: " << val << std::endl;
}
方式3:函数返回值后约束(简写)
template <typename T>
auto add(T a, T b) -> std::enable_if_t<std::integral<T>, T> // 旧方式(对比)
{ return a + b; }
// 新方式(Concepts)
template <typename T>
T add(T a, T b) requires std::integral<T>
{ return a + b; }
3. 完整示例
#include <concepts>
#include <iostream>
#include <string>
// 概念1:可打印类型
template <typename T>
concept Printable = requires(std::ostream& os, const T& t) {
os << t;
};
// 概念2:可比较大小的类型
template <typename T>
concept Comparable = requires(const T& a, const T& b) {
{ a < b } -> std::convertible_to<bool>; // 要求 a<b 返回可转换为 bool 的类型
};
// 概念3:同时满足可打印和可比较
template <typename T>
concept PrintableComparable = Printable<T> && Comparable<T>;
// 约束模板函数
template <PrintableComparable T>
void print_max(const T& a, const T& b) {
std::cout << "Max value: " << (a > b ? a : b) << std::endl;
}
int main() {
print_max(10, 20); // 合法:int 满足约束
print_max(3.14, 2.71); // 合法:double 满足约束
print_max(std::string("a"), "b"); // 合法:string 满足约束
// 错误示例(编译报错):
// struct S {};
// print_max(S{}, S{}); // S 不支持 << 和 <
return 0;
}
三、高级特性
1. 关联约束(Nested Requirements)
检查表达式的返回值类型或属性:
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>; // 要求 a+b 返回值类型等于 T
};
template <Addable T>
T sum(T a, T b) { return a + b; }
2. 模板模板概念
约束模板模板参数:
// 约束:模板参数是可实例化为 Printable 类型的容器
template <template <typename> typename Container, typename T>
concept PrintableContainer = Printable<T> && requires {
{ Container<T>{} }; // 要求 Container<T> 可默认构造
};
template <PrintableContainer Container, typename T>
void print_container(const Container<T>& c) {
// 实现逻辑
}
3. 概念的继承与组合
通过逻辑运算符组合多个概念:
template <typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;
template <typename T>
concept Numeric = Arithmetic<T> && Printable<T>;
四、标准库概念(<concepts>)
C++20 提供了一系列预定义概念,常用的有:
| 概念 | 说明 |
|---|---|
std::integral | 整数类型(char/int/long 等) |
std::floating_point | 浮点类型(float/double 等) |
std::same_as<T, U> | T 和 U 是同一类型 |
std::derived_from<T, U> | T 是 U 的派生类 |
std::convertible_to<T, U> | T 可转换为 U |
std::ranges::range | 可迭代的范围(容器/视图) |
std::invocable<F, Args...> | F 可被调用(函数/函数对象) |
示例:
#include <concepts>
#include <vector>
#include <ranges>
template <std::ranges::range R>
void iterate(R&& r) {
for (auto&& elem : r) { /* 遍历逻辑 */ }
}
int main() {
std::vector<int> v = {1,2,3};
iterate(v); // 合法:vector 是 range
iterate(std::views::iota(1, 10)); // 合法:视图是 range
return 0;
}
五、Concepts 对比传统方式
| 方式 | 优点 | 缺点 |
|---|---|---|
| Concepts(C++20) | 可读性强、编译错误提示友好、可复用 | 需要 C++20 及以上编译器 |
| SFINAE | 兼容旧标准 | 代码晦涩、错误提示不友好 |
static_assert | 简单直观 | 仅能报错,无法重载模板 |
六、编译器支持
- GCC 10+、Clang 10+、MSVC 19.28+ 均支持 C++20 Concepts;
- 编译时需指定标准:
-std=c++20(GCC/Clang)或/std:c++20(MSVC)。
总结
- Concepts 是 C++20 模板编程的重大改进,核心价值是显式化模板约束;
- 优先使用标准库概念,自定义概念时保持单一职责;
- 通过
requires子句可灵活定义类型的行为约束; - 相比传统方式,Concepts 能大幅提升模板代码的可读性和可维护性。
如果需要针对特定场景(如泛型算法、容器约束、自定义类型检查)的 Concepts 实现,可以补充说明,我会提供更针对性的示例。

被折叠的 条评论
为什么被折叠?



