Highlight
C++20 Concepts 让泛型代码的错误信息从模板内部转移到调用处,编译器在实例化之前就拒绝不匹配的类型;配合 constexpr 编译期求值,还能把运行时初始化开销提前到编译阶段。
核心内容
模板报错让人头疼
写 C++ 模板时,一个常见场景:函数 isOdd 本意只接受整数,但测试代码里手滑写了 1.1 而不是 11。编译器报错指向模板内部,说 % 运算符不能用于 double。开发者要在层层模板展开信息里翻找,才能发现真正的错误在调用处。
C++20 之前,约束模板参数只能靠注释、参数命名约定,或者复杂的 enable_if。这些方式要么没有编译器检查,要么报错信息依然晦涩。
Concepts:在实例化前拦截错误
C++20 引入 Concepts,允许在模板声明时就明确参数类型必须满足的条件。编译器在尝试实例化模板之前先检查 concept,不匹配直接报调用处的错误。
详细内容
用标准库 Concept 约束模板
(03:56)
#include <concepts>
// 以前:任何类型都能传进来
template <class T>
bool isOdd(T value) {
return value % 2 != 0;
}
// 现在:只接受整数类型
template <std::integral T>
bool isOdd(T value) {
return value % 2 != 0;
}
// 测试代码
isOdd(11); // 通过
isOdd(1.1); // 编译错误:double 不满足 integral concept
关键点:
std::integral是 C++ 标准库提供的 concept,包含在<concepts>头文件中- 用
std::integral替代class关键字,限制模板只接受内置整数类型 - 传入
double时,编译器直接报错在isOdd(1.1)这一行,而不是模板内部
标准库 Concepts 概览
(06:04)
<concepts> 头文件提供了一组核心概念:
#include <concepts>
// 类型概念
static_assert(std::floating_point<float>); // float 满足
static_assert(std::floating_point<double>); // double 满足
// 可构造/可销毁
static_assert(std::destructible<int>);
static_assert(std::move_constructible<int>);
// 类型转换
static_assert(std::convertible_to<int, float>); // int 可转 float
// 可比较
static_assert(std::equality_comparable<int>); // int 有 == 运算符
关键点:
floating_point:测试是否为浮点类型convertible_to:测试类型 A 能否隐式转换为类型 Bmove_constructible:测试能否从同类型值移动构造equality_comparable:测试是否有有效的==运算符
requires 子句:多条件约束
(08:22)
单个 concept 不够时,用 requires 子句组合多个条件:
#include <concepts>
template <typename T>
requires std::equality_comparable<T> && std::default_constructible<T>
bool isDefaultValue(T value) {
return value == T{}; // 需要 == 和默认构造
}
关键点:
requires子句放在模板参数列表之后- 用
&&组合多个 concept,所有条件必须同时满足 - 这确保模板只在类型支持所需操作时才实例化
自定义 Concept:用 requires 表达式
(13:46)
假设有一个图形渲染库,各种形状都有 getDistanceFrom 方法。可以定义一个 Shape concept:
// 自定义 Shape concept
template <typename T>
concept Shape = requires(T shape) {
{ shape.getDistanceFrom(0.0f, 0.0f) } -> std::same_as<float>;
};
// 使用 concept 约束模板
template <Shape T>
Color computePixelColor(const T& shape, float x, float y) {
float distance = shape.getDistanceFrom(x, y);
return distance < 0 ? Color::white : Color::black;
}
// Circle 类满足 Shape concept
class Circle {
public:
float getDistanceFrom(float x, float y) const {
float dx = x - centerX;
float dy = y - centerY;
return std::sqrt(dx * dx + dy * dy) - radius;
}
private:
float centerX, centerY, radius;
};
关键点:
concept关键字定义新概念requires表达式中的参数列表声明测试用的值- 表达式要求
{ ... }测试某段代码能否编译 -> std::same_as<float>用复合要求检查返回值类型- 表达式只在编译时验证,不会实际执行
Concept 重载:自动选择最匹配的函数
(18:20)
不同 concept 约束的函数可以构成重载,编译器自动选择最具体的版本:
// 基础形状 concept
template <typename T>
concept Shape = requires(T shape) {
{ shape.getDistanceFrom(0.0f, 0.0f) } -> std::same_as<float>;
};
// 渐变形状 concept(同时满足 Shape)
template <typename T>
concept GradientShape = Shape<T> && requires(T shape) {
{ shape.getGradientColor(0.0f, 0.0f) } -> std::same_as<Color>;
};
// 基础版本:所有形状
template <Shape T>
Color computePixelColor(const T& shape, float x, float y) {
return shape.getDistanceFrom(x, y) < 0 ? Color::white : Color::black;
}
// 渐变版本:有渐变的形状
template <GradientShape T>
Color computePixelColor(const T& shape, float x, float y) {
float dist = shape.getDistanceFrom(x, y);
return dist < 0 ? shape.getGradientColor(x, y) : Color::black;
}
// GradientCircle 同时满足 Shape 和 GradientShape
// 编译器会选择第二个(更具体的)重载
GradientCircle circle;
auto color = computePixelColor(circle, 1.0f, 2.0f);
关键点:
GradientShape继承Shape的要求,确保满足GradientShape的类型一定满足Shape- 编译器在重载决议时选择约束最具体的版本
- 这比 SFINAE 技巧简洁直观得多
constexpr:编译期求值减少启动开销
(21:09)
// 编译期解析颜色
constexpr Color fromHexCode(const char* hex) {
// 解析 #RRGGBB 字符串
return Color{hexToInt(hex, 0), hexToInt(hex, 2), hexToInt(hex, 4)};
}
// 调色板在编译期初始化
constexpr Color colorPalette[] = {
fromHexCode("#FF0000"), // 编译期求值
fromHexCode("#00FF00"),
fromHexCode("#0000FF")
};
关键点:
constexpr标记函数可在编译期执行constexpr变量保证在编译期初始化- Xcode 14 增强了标准库的 constexpr 支持,更多类型和算法可在编译期使用
- 编译期初始化减少 App 启动时的运行时开销
升级到 C++20
(26:29)
Xcode 14 完整支持 C++20。在 Build Settings 中把 “C++ Language Dialect” 改为 C++20 即可使用 concepts 等特性。C++20 没有最低部署目标要求,可以针对现有支持的 OS 版本发布。
核心启发
-
给现有模板加上 concept 约束 找出项目里常用的函数模板和类模板,用标准库 concept 或自定义 concept 加上类型约束。错误信息会立刻变清晰,维护成本降低。
-
用 concept 替代复杂的 SFINAE 技巧 如果代码里有
enable_if、void_t等模板元编程技巧,用 concept 重写。代码更易读,编译错误更易懂。 -
把启动时的常量初始化改为 constexpr 检查项目中的常量数组、查找表、配置数据。如果初始化逻辑可以用 constexpr 在编译期完成,启动时间会减少。
-
为领域模型定义 concept 体系 比如渲染引擎定义
Shape、GradientShape、TexturedShape等 concept。新类型只要满足 concept 就能无缝接入,接口文档即代码。
关联 Session
- What’s new in Xcode 14 — Xcode 14 全面支持 C++20
- Demystify parallelization in Xcode builds — 构建系统优化,C++ 项目同样受益
- Improve app size and runtime performance — 编译器优化,constexpr 减少运行时开销
- Link fast: Improve build and launch times — 链接优化,C++ 项目构建加速
评论
GitHub Issues · utterances