WWDC Quick Look 💓 By SwiftGGTeam
Simplify C++ templates with concepts

Simplify C++ templates with concepts

观看原视频

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 能否隐式转换为类型 B
  • move_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_ifvoid_t 等模板元编程技巧,用 concept 重写。代码更易读,编译错误更易懂。

  • 把启动时的常量初始化改为 constexpr 检查项目中的常量数组、查找表、配置数据。如果初始化逻辑可以用 constexpr 在编译期完成,启动时间会减少。

  • 为领域模型定义 concept 体系 比如渲染引擎定义 ShapeGradientShapeTexturedShape 等 concept。新类型只要满足 concept 就能无缝接入,接口文档即代码。

关联 Session

评论

GitHub Issues · utterances