Concepts(概念)是 C++20 核心特性之一,用于给模板参数添加显式、可读、编译期类型约束。
C++20 之前,模板约束主要依赖:
SFINAE(Substitution Failure Is Not An Error)std::enable_if- traits + 偏特化
这些方案的核心问题:
| 问题 | 描述 |
|---|---|
| 可读性差 | 模板签名被污染 |
| 错误信息差 | 编译报错极其难读 |
| 语义不清 | 无法表达“意图”(只是技巧) |
基础
Concept解决:约束前置、错误清晰、语义显式、语法简洁。
基本定义
template<typename T>
concept 概念名 = 编译期布尔表达式;
concept:关键字,定义命名的编译期谓词- 表达式:
true/false,决定类型是否满足约束
整数概念示例
#include <type_traits>
template<typename T>
concept Integral = std::is_integral_v<T>; // T 是整型
概念组合
概念本质上是一个编译期布尔谓词,可直接用逻辑运算符组合,形成更复杂的概念。
template<typename T>
concept Integral = std::is_integral_v<T>;
template<typename T>
concept FloatingPoint = std::is_floating_point_v<T>;
// 析取(或):既是整数 或 浮点数
template<typename T>
concept Numeric = Integral<T> || FloatingPoint<T>;
// 合取(与):整数且是有符号的(但标准库已经提供更好的写法)
template<typename T>
concept SignedInt = Integral<T> && std::is_signed_v<T>;
// 否定:不是整数
template<typename T>
concept NonIntegral = !Integral<T>;
概念细化
细化类似“继承”:所谓的“继承”是指一个概念在另一个概念的基础上添加额外的约束,形成一条概念层次链
template<typename T>
concept Animal = requires(T a) {
{ a.speak() } -> std::convertible_to<std::string>;
};
template<typename T>
concept Dog = Animal<T> && requires(T d) {
d.wag_tail();
requires std::same_as<decltype(d.speak()), std::string>; // 细化了返回值
};
Dog 概念细化了 Animal:凡是满足 Dog 的类型,必然满足 Animal。
requires 表达式
requires 用于描述类型必须支持的语法 / 接口:
- 编译期约束模板参数
只有满足requires条件的类型或表达式,模板才能被实例化,否则直接编译报错,且错误信息远比传统 SFINAE 清晰。 - 改善重载解析
多个模板重载时,满足不同requires的版本会被精确选择,可实现类似“类型模式匹配”的效果。 - 表达对类型能力的精确要求
可以检查:某个表达式是否合法、某个类型是否存在、某个成员函数返回值是否满足某概念、某个操作是否不抛异常等。
requires 表达式内的需求序列默认是逻辑与:列出的每一项都必须满足,整个表达式才返回 true。需要有多个 requires 表达式(或概念)在外层用 && 或|| 拼接;或者嵌套:
template<typename T>
concept MyConcept = requires(T x) {
x.foo();
requires std::copyable<T>;
requires sizeof(T) == 4;
};
两种形态
requires clause(约束子句)
写在模板参数列表后、函数/类后面,用于“开关”模板。当 布尔常量表达式(通常是概念检查) 为 true 时模板启用。
template<typename T>
requires 布尔常量表达式
void func(T t);
也可以将 requires 放在函数尾部(更常见于类模板成员):
template<typename T>
T add(T a, T b) requires std::integral<T>
{ return a + b; }
concept 本质上就是产生布尔值的 constexpr 表达式,所以可直接使用:
template<std::integral T> // 简写,等价于 requires std::integral<T>
T add(T a, T b)
{ return a + b; }
requires 子句的典型用途:约束函数模板、类模板、成员函数。
requires expression(约束表达式)
用于定义 concept 的“检测器”
requires (参数列表) { 需求序列; }
参数列表可省略,需求序列是一组对类型、表达式、嵌套条件的要求。requires 表达式本身返回 bool(纯右值),因此可直接作为 concept 的定义体。
定义一个 concept,检查类型是否可相加:
template<typename T>
concept Addable = requires(T a, T b) {
a + b; // 简单需求:a+b 必须是合法表达式
};
四类 requirement
1)简单需求
只检查表达式是否合法
template<typename T>
concept Addable = requires(T a, T b) {
a + b; // 支持 +
a += b; // 支持 +=
};
2)类型需求
检查某个类型是否存在
template<typename T>
concept HasValueType = requires {
typename T::value_type; // 有内嵌类型 value_type
};
3)成员需求
检查是否有指定成员
template<typename T>
concept Drawable = requires(T t) {
t.draw(); // 有 draw() 成员函数
{ t.size() } -> std::same_as<size_t>; // size() 返回 size_t
};
4)复合需求
多种需求组合在一起
template<typename T>
concept Container = requires(T c) {
typename T::iterator;
c.begin();
c.end();
c.size();
} && std::copyable<T>; // 且满足可拷贝
编译器分支
if constexpr:编译期分支判断(不同类型走不同逻辑)
- 只在编译时判断: 条件必须是编译期常量
- 不满足的分支直接被丢弃,不参与编译、不生成代码;
- 无任何运行时开销
- 可嵌套、可和模板配合
if constexpr (编译期布尔表达式) {
// 满足时编译
} else {
// 不满足时编译
}
与概念结合,增强模板功能
#include <iostream>
#include <type_traits>
using namespace std;
// 1. 定义概念:只允许数字类型
template<typename T>
concept Number = is_arithmetic_v<T>;
// 2. 约束模板
template<Number T>
auto smart_add(T a, T b) {
// 3. 编译期分支
if constexpr (is_integral_v<T>) {
cout << "[整数运算] ";
return a + b;
} else {
cout << "[浮点数运算] ";
return a + b;
}
}
int main() {
cout << smart_add(2, 3) << endl; // 整数
cout << smart_add(2.1, 3.2) << endl;// 浮点数
// smart_add("abc", "def"); // ❌ 直接被 Concept 拦截,报错清晰
}
标准库
<concepts> 头文件中提供了大量常用概念
语言核心概念
std::same_as<T, U>—— 严格相同类型std::derived_from<Derived, Base>—— 派生关系std::convertible_to<From, To>—— 隐式转换或与 To 相同std::common_with<T, U>—— 有公共类型std::integral<T>—— 整数类型std::signed_integral<T>—— 有符号整数std::unsigned_integral<T>—— 无符号整数std::floating_point<T>—— 浮点类型
对象构造 / 赋值的概念
std::destructible<T>std::constructible_from<T, Args...>std::default_initializable<T>std::move_constructible<T>std::copy_constructible<T>std::movable<T>—— 可移动 + 可交换std::copyable<T>—— 可拷贝 + 可移动
比较相关
std::equality_comparable<T>—— 支持==,且结果为boolstd::totally_ordered<T>—— 支持< <= > >=
调用相关
std::invocable<F, Args...>—— 可调用std::regular_invocable<F, Args...>—— 等价调用(无副作用)
对象特性
std::semiregular<T>—— 可默认构造、拷贝、赋值std::regular<T>—— semiregular + equality_comparable