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 表达式精确描述需求。