Skip to content

C++20 Concepts

Concepts 是 C++20 最重要的特性之一,让模板约束从晦涩的 SFINAE 变成清晰可读的接口契约。

为什么需要 Concepts

cpp
// C++17 之前:SFINAE 错误信息极其难读
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T> add(T a, T b) { return a + b; }

add("hello", "world");
// 错误信息:一大堆模板实例化失败的噪音...

// C++20 Concepts:清晰的约束 + 友好的错误信息
template<std::integral T>
T add(T a, T b) { return a + b; }

add("hello", "world");
// 错误:constraints not satisfied for 'add' [with T = const char*]
// note: 'const char*' does not satisfy 'integral'

定义 Concept

cpp
#include <concepts>

// 基本语法:concept 名称 = 约束表达式
template<typename T>
concept Numeric = std::is_arithmetic_v<T>;

template<typename T>
concept Printable = requires(T t) {
    { std::cout << t };  // 要求表达式合法
};

template<typename T>
concept Comparable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;
    { a > b } -> std::convertible_to<bool>;
    { a == b } -> std::convertible_to<bool>;
};

// 组合 concept
template<typename T>
concept SortableElement = Comparable<T> && std::copyable<T>;

// 带返回类型约束
template<typename T>
concept Container = requires(T c) {
    typename T::value_type;           // 要求有 value_type 类型成员
    { c.begin() } -> std::same_as<typename T::iterator>;
    { c.end()   } -> std::same_as<typename T::iterator>;
    { c.size()  } -> std::convertible_to<std::size_t>;
};

使用 Concept 的四种语法

cpp
template<typename T>
concept Addable = requires(T a, T b) { a + b; };

// 语法 1:requires 子句
template<typename T>
    requires Addable<T>
T sum(T a, T b) { return a + b; }

// 语法 2:concept 直接替换 typename
template<Addable T>
T sum2(T a, T b) { return a + b; }

// 语法 3:缩写函数模板(最简洁)
Addable auto sum3(Addable auto a, Addable auto b) { return a + b; }

// 语法 4:requires 表达式内联
template<typename T>
    requires requires(T a, T b) { a + b; }
T sum4(T a, T b) { return a + b; }

标准库内置 Concepts

cpp
#include <concepts>

// 类型关系
std::same_as<T, U>          // T 和 U 完全相同
std::derived_from<T, Base>  // T 派生自 Base
std::convertible_to<T, U>   // T 可转换为 U
std::common_with<T, U>      // T 和 U 有公共类型

// 类型分类
std::integral<T>            // 整数类型
std::floating_point<T>      // 浮点类型
std::signed_integral<T>     // 有符号整数
std::unsigned_integral<T>   // 无符号整数

// 对象概念
std::copyable<T>            // 可拷贝
std::movable<T>             // 可移动
std::regular<T>             // 正则类型(可拷贝、可比较等)
std::semiregular<T>         // 半正则类型

// 可调用概念
std::invocable<F, Args...>  // F 可以用 Args 调用
std::predicate<F, Args...>  // F 是谓词(返回 bool)

// 比较概念
std::equality_comparable<T>
std::totally_ordered<T>

// 范围概念(<ranges>)
std::ranges::range<R>
std::ranges::sized_range<R>
std::ranges::input_range<R>
std::ranges::random_access_range<R>

实战:自定义 Concept 约束

cpp
#include <concepts>
#include <string>

// 序列化接口约束
template<typename T>
concept Serializable = requires(T obj, std::string& buf) {
    { obj.serialize(buf) } -> std::same_as<void>;
    { T::deserialize(buf) } -> std::same_as<T>;
};

// 哈希表键约束
template<typename K>
concept HashKey = std::equality_comparable<K> && requires(K k) {
    { std::hash<K>{}(k) } -> std::convertible_to<std::size_t>;
};

// 数值范围约束
template<typename T>
concept NonNegative = std::is_arithmetic_v<T> && requires(T v) {
    requires std::is_unsigned_v<T> || requires { v >= T{0}; };
};

// 使用约束的泛型函数
template<Container C>
    requires SortableElement<typename C::value_type>
void sort_container(C& c) {
    std::sort(c.begin(), c.end());
}

// 约束类模板
template<HashKey K, std::copyable V>
class HashMap {
    // ...
};

Concept 与重载决议

cpp
// Concepts 参与重载决议,更具体的约束优先
template<typename T>
void process(T x) {
    std::cout << "通用版本\n";
}

template<std::integral T>
void process(T x) {
    std::cout << "整数版本\n";
}

template<std::floating_point T>
void process(T x) {
    std::cout << "浮点版本\n";
}

process(42);    // 整数版本
process(3.14);  // 浮点版本
process("hi");  // 通用版本

关键认知

Concepts 让模板接口从"隐式约定"变成"显式契约",大幅改善了错误信息和代码可读性。优先使用标准库提供的 concept,自定义 concept 时用 requires 表达式精确描述需求。

系统学习 C++ 生态,深入底层架构