Ch 26: C++20 Concepts
- 理解为什么需要 Concepts(概念)
- 掌握标准库内置 Concepts
- 学会定义和使用自定义 Concepts
- 熟练使用 requires 子句和 requires 表达式
- 理解 Concepts 如何改善错误信息和代码质量
26.1 Python Duck Typing vs C++ Concepts
Section titled “26.1 Python Duck Typing vs C++ Concepts”Python 的运行时 Duck Typing
Section titled “Python 的运行时 Duck Typing”# Python - 运行时 duck typingdef add(a, b): return a + b
# 任何实现了 __add__ 的对象都可以add(1, 2) # OK:int 支持 +add("a", "b") # OK:str 支持 +add([1], [2]) # OK:list 支持 +add(1, "a") # 运行时错误!TypeErrorPython 的问题:
- 类型错误在运行时才发现
- 错误信息可能不直观
- 难以进行静态分析
C++ 传统模板
Section titled “C++ 传统模板”// C++ - 编译时检查(模板)template<typename T>T add(T a, T b) { return a + b; // T 必须支持 + 运算}
add(1, 2); // OKadd("a", "b"); // OKadd(1, "a"); // 编译错误!但错误信息可能很长传统模板的问题:
- 错误信息涉及模板内部膨胀
- 难以判断”什么是真正的问题”
- 不清楚需要什么约束
C++20 Concepts:显式约束
Section titled “C++20 Concepts:显式约束”#include <concepts>#include <iostream>
// 定义 Concept:Numeric(数值类型)template<typename T>concept Numeric = std::integral<T> || std::floating_point<T>;
// 使用 Concept 约束模板参数template<Numeric T>T add(T a, T b) { return a + b;}
int main() { std::cout << add(1, 2) << std::endl; // 3 std::cout << add(1.5, 2.5) << std::endl; // 4 // add("a", "b"); // 编译错误!清晰指出 "a" 不是 Numeric}Concepts 的优势:
- 清晰的错误信息
- 编译期检查,更早发现问题
- 代码意图明确
- IDE 支持更好(自动补全、类型检查)
26.2 标准库 Concepts 详解
Section titled “26.2 标准库 Concepts 详解”类型分类 Concepts
Section titled “类型分类 Concepts”#include <concepts>#include <type_traits>
// 整数类型static_assert(std::integral<int>); // truestatic_assert(std::integral<char>); // truestatic_assert(std::integral<unsigned>); // truestatic_assert(!std::integral<double>); // falsestatic_assert(!std::integral<int*>); // false
// 浮点类型static_assert(std::floating_point<double>); // truestatic_assert(std::floating_point<float>); // truestatic_assert(!std::floating_point<int>); // false
// 有符号/无符号整数static_assert(std::signed_integral<int>); // truestatic_assert(!std::signed_integral<unsigned>); // truestatic_assert(std::unsigned_integral<unsigned>); // truestatic_assert(!std::unsigned_integral<int>); // true
// 布尔static_assert(std::same_as<bool, std::integral_constant<bool, true>::type>);可移动性和可拷贝性
Section titled “可移动性和可拷贝性”#include <concepts>#include <memory>
// 可移动:可以 std::movestatic_assert(std::movable<int>); // truestatic_assert(std::movable<std::unique_ptr<int>>); // true
// 可拷贝:可以拷贝构造和拷贝赋值static_assert(std::copyable<int>); // truestatic_assert(std::copyable<std::string>); // true
// 不可拷贝static_assert(!std::copyable<std::unique_ptr<int>>); // unique_ptr 不可拷贝
// 可默认构造static_assert(std::default_initializable<int>); // true
struct NoDefault { NoDefault() = delete;};static_assert(!std::default_initializable<NoDefault>);
// 可赋值static_assert(std::assignable<int&, int>); // truestatic_assert(std::assignable<std::string&, std::string>); // true比较 Concepts
Section titled “比较 Concepts”#include <concepts>
// 相等可比较:支持 ==static_assert(std::equality_comparable<int>); // truestatic_assert(std::equality_comparable<double>); // true
struct Point { int x, y; auto operator<=>(const Point&) const = default;};static_assert(std::equality_comparable<Point>); // C++20 自动生成 ==
// 完全有序:支持 < > <= >=static_assert(std::totally_ordered<int>); // truestatic_assert(std::totally_ordered<double>); // true
struct Version { int major, minor; auto operator<=>(const Version&) const = default;};static_assert(std::totally_ordered<Version>); // true范围 Concepts
Section titled “范围 Concepts”#include <concepts>#include <ranges>#include <vector>
// std::ranges::range - 支持 begin()/end()static_assert(std::ranges::range<std::vector<int>>); // truestatic_assert(std::ranges::range<int[]>); // truestatic_assert(std::ranges::range<std::string>); // true
// 输入范围static_assert(std::ranges::input_range<std::vector<int>>); // true
// 输出范围static_assert(std::ranges::output_range<std::vector<int>, int>); // true
// 前向范围static_assert(std::ranges::forward_range<std::vector<int>>); // true
// 双向范围static_assert(std::ranges::bidirectional_range<std::vector<int>>); // true
// 随机访问范围static_assert(std::ranges::random_access_range<std::vector<int>>); // true
// 连续范围(内存连续)static_assert(std::ranges::contiguous_range<std::vector<int>>); // true可调用 Concepts
Section titled “可调用 Concepts”#include <concepts>#include <functional>
// 可调用static_assert(std::invocable<int(*)(int), int>); // 函数指针static_assert(std::invocable<decltype([](int x) { return x * 2; }), int>); // Lambda
// 常规可调用static_assert(std::regular_invocable<int(*)(int), int>); // 不抛出
// 函数类型using Fn = int(int, int);static_assert(std::invocable<Fn, int, int>); // true26.3 自定义 Concepts 详解
Section titled “26.3 自定义 Concepts 详解”#include <concepts>
// 简单 Concept:数值类型template<typename T>concept Numeric = std::integral<T> || std::floating_point<T>;
// 简单 Concept:支持 + 和 - 运算template<typename T>concept Addable = requires(T a, T b) { a + b; // 表达式有效 a - b; // 表达式有效};
// 带返回类型约束template<typename T>concept AddableWithResult = requires(T a, T b) { { a + b } -> std::convertible_to<T>; // 结果可转换为 T};
// 组合 Conceptstemplate<typename T>concept Container = requires(T t) { t.begin(); t.end(); t.size();};
// 更严格的容器:支持随机访问template<typename T>concept RandomAccessContainer = Container<T> && requires(T t) { t[0];};requires 表达式详解
Section titled “requires 表达式详解”#include <concepts>#include <vector>#include <list>#include <iostream>
// requires 表达式语法template<typename T>concept HasPlus = requires(T a, T b) { // 简单要求:表达式有效 a + b;
// 类型要求:类型存在 typename T::value_type;
// 复合要求:表达式及其属性 { a + b } -> std::convertible_to<T>;
// 带常量表达式要求 requires std::is_integral_v<T>;};
// 完整示例:Hashable Concepttemplate<typename T>concept Hashable = requires(T a) { // 必须有 std::hash 特化 typename std::hash<T>;
// std::hash<T>{} 必须可调用并返回 size_t { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
// 必须支持 == { a == a } -> std::convertible_to<bool>;};
static_assert(Hashable<int>); // truestatic_assert(Hashable<std::string>); // true
struct NoHash {};static_assert(!Hashable<NoHash>); // falserequires 子句
Section titled “requires 子句”#include <concepts>#include <iostream>
// 1. 作为模板参数约束(最常用)template<std::integral T>T factorial(T n) { if (n <= 1) return 1; return n * factorial(n - 1);}
// 2. requires 约束表达式(更灵活)template<typename T> requires std::integral<T>T safe_factorial(T n) { if (n < 0) return 0; return factorial(n);}
// 3. 多重约束(使用 &&)template<typename T> requires std::integral<T> || std::floating_point<T>T double_value(T v) { return v * 2;}
// 4. C++20 requires 表达式(内联约束)template<typename T>concept NumericOperation = std::integral<T> || std::floating_point<T>;
template<typename T> requires NumericOperation<T>T triple(T v) { return v * 3;}完整示例:自定义 Concept
Section titled “完整示例:自定义 Concept”#include <concepts>#include <iostream>#include <vector>#include <list>
// ===== 定义自己的 Concepts =====
// 1. 基本 Concept:支持相加template<typename T>concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>;};
// 2. 可序列化 Concepttemplate<typename T>concept Serializable = requires(T t) { // 必须有 to_string() 方法 t.to_string();};
// 3. 可迭代并可转换为整数template<typename T>concept IterableToInt = requires(T t) { // 可以解引用 *t; // 可以递增 ++t; // 解引用结果可转换为整数 { **t } -> std::convertible_to<int>;};
// ===== 使用 Concepts =====
template<Addable T>T sum(T a, T b) { return a + b;}
int main() { // Addable 测试 std::cout << "sum(int): " << sum(1, 2) << std::endl; // 3 std::cout << "sum(double): " << sum(1.5, 2.5) << std::endl; // 4
// 编译错误:std::vector 不支持 + // sum(std::vector<int>{1}, std::vector<int>{2}); // 编译错误}26.4 requires 子句和表达式
Section titled “26.4 requires 子句和表达式”requires 子句(约束模板参数)
Section titled “requires 子句(约束模板参数)”#include <concepts>
// 单个约束template<typename T> requires std::integral<T>T abs(T n) { return n < 0 ? -n : n;}
// 多个约束(AND)template<typename T> requires std::integral<T> && std::signed_integral<T>T signed_abs(T n) { return n < 0 ? -n : n;}
// OR 约束template<typename T> requires std::integral<T> || std::floating_point<T>T twice(T n) { return n * 2;}
// 使用 concept 名称template<std::integral T>T triple(T n) { return n * 3;}
// 使用标准 concept 组合template<std::totally_ordered T>const T& min(const T& a, const T& b) { return b < a ? b : a;}requires 表达式(编译期条件)
Section titled “requires 表达式(编译期条件)”#include <concepts>#include <type_traits>
// requires 表达式返回 booltemplate<typename T>constexpr bool is_proxy_v = requires(T t) { // 如果 T 有这些成员,则是代理类 typename T::proxy_type; { t.get() };};
// C++20 类型特征template<typename T>struct is_procedure : std::bool_constant< requires(T t) { typename T::procedure_type; }> {};
// 使用static_assert(is_proxy_v<int> == false);requires 表达式详细语法
Section titled “requires 表达式详细语法”#include <concepts>
// 简单要求(Simple requirements)template<typename T>concept Addable = requires(T a, T b) { a + b; // 验证 a + b 是有效表达式 a - b; // 验证 a - b 是有效表达式};
// 类型要求(Type requirements)template<typename T>concept HasValueType = requires { typename T::value_type; // T 必须有内嵌类型 value_type typename T::iterator; // T 必须有内嵌类型 iterator};
// 复合要求(Compound requirements)template<typename T>concept AddableWithResult = requires(T a, T b) { { a + b } -> std::convertible_to<T>; // a + b 结果可转换为 T { a - b } -> std::convertible_to<T>;};
// 带异常规范的复合要求template<typename T>concept NothrowAddable = requires(T a, T b) { { a + b } noexcept -> std::same_as<T>; // a + b 不抛异常且返回 T};
// 嵌套要求(Nested requirements)template<typename T>concept Numeric = requires(T t) { // 类型要求 typename std::make_unsigned<T>;
// 复合要求 { t + t } -> std::convertible_to<T>; { t - t } -> std::convertible_to<T>; { t * t } -> std::convertible_to<T>;
// 嵌套要求:整数类型 requires std::integral<T>;};26.5 Concepts 在函数模板中的应用
Section titled “26.5 Concepts 在函数模板中的应用”函数重载与 Concepts
Section titled “函数重载与 Concepts”#include <concepts>#include <iostream>#include <vector>#include <list>
// 无约束模板(最不具体)template<typename T>void print(T x) { std::cout << "generic: " << x << "\n";}
// 整数约束template<std::integral T>void print(T x) { std::cout << "integral: " << x << "\n";}
// 浮点约束template<std::floating_point T>void print(T x) { std::cout << "floating: " << x << "\n";}
// 范围约束template<std::ranges::range R>void print(R&& r) { std::cout << "range: "; for (const auto& x : r) { std::cout << x << " "; } std::cout << "\n";}
int main() { print(42); // integral: 42 print(3.14); // floating: 3.14 print("hello"); // generic: hello print(std::vector{1, 2, 3}); // range: 1 2 3}Concepts 约束构造函数
Section titled “Concepts 约束构造函数”#include <concepts>#include <iostream>
class Widget {public: // 使用 Concepts 约束模板构造函数 template<std::integral T> explicit Widget(T value) : value_(static_cast<int>(value)) {}
template<std::floating_point T> Widget(T value) : value_(static_cast<int>(value)) {}
int value() const { return value_; }
private: int value_;};
int main() { Widget w1(42); // 调用 integral 版本 Widget w2(3.14); // 调用 floating_point 版本
// Widget w3("hello"); // 编译错误!没有匹配版本}Concepts 约束类模板
Section titled “Concepts 约束类模板”#include <concepts>#include <vector>
// 约束类模板template<std::ranges::random_access_range Container>class ContainerStats {public: explicit ContainerStats(const Container& c) : container_(c) {}
double mean() const { double sum = 0; for (const auto& x : container_) { sum += x; } return sum / container_.size(); }
auto min() const { return *std::min_element(container_.begin(), container_.end()); }
auto max() const { return *std::max_element(container_.begin(), container_.end()); }
private: const Container& container_;};
int main() { std::vector<int> v = {1, 2, 3, 4, 5}; ContainerStats stats(v); std::cout << "Mean: " << stats.mean() << "\n"; std::cout << "Min: " << stats.min() << "\n"; std::cout << "Max: " << stats.max() << "\n";}26.6 auto 和 Concepts(C++20 缩写语法)
Section titled “26.6 auto 和 Concepts(C++20 缩写语法)”占位符类型约束
Section titled “占位符类型约束”#include <concepts>
// C++20 缩写语法:用 auto 配合 Conceptstd::integral auto add(std::integral auto a, std::integral auto b) { return a + b;}
// 等价于template<std::integral T, std::integral U>auto add(T a, U b) { return a + b;}
// 更复杂的例子template<typename T> requires std::integral<T>T factorial(T n) { T result = 1; for (T i = 2; i <= n; ++i) { result *= i; } return result;}
// C++20 缩写std::integral auto factorial(std::integral auto n) { std::integral auto result = 1; // C++20 支持在变量声明中使用 for (std::integral auto i = 2; i <= n; ++i) { result *= i; } return result;}与传统模板的对比
Section titled “与传统模板的对比”#include <concepts>#include <iostream>
// 传统模板写法template<typename T>T max_value(T a, T b) { return a > b ? a : b;}
// C++20 Concepts 写法(等价)std::totally_ordered auto max_value(std::totally_ordered auto a, std::totally_ordered auto b) { return a > b ? a : b;}
// 使用 requires 子句template<typename T> requires std::integral<T>T gcd(T a, T b) { while (b != 0) { T temp = b; b = a % b; a = temp; } return a;}
// C++20 缩写std::integral auto gcd(std::integral auto a, std::integral auto b) { while (b != 0) { auto temp = b; b = a % b; a = temp; } return a;}
int main() { std::cout << max_value(1, 2) << "\n"; std::cout << max_value(1.5, 2.5) << "\n"; std::cout << gcd(48, 18) << "\n";}26.7 标准库 Concepts 速查表
Section titled “26.7 标准库 Concepts 速查表”#include <concepts>
// ===== 类型分类 =====std::integral<T> // 整数类型std::floating_point<T> // 浮点类型std::signed_integral<T> // 有符号整数std::unsigned_integral<T> // 无符号整数std::same_as<T, U> // T 和 U 是同一类型std::derived_from<T, U> // T 派生自 Ustd::convertible_to<T, U> // T 可转换为 U
// ===== 语义概念 =====std::movable<T> // 可移动std::copyable<T> // 可拷贝std::default_initializable<T> // 可默认构造std::assignable<T, U> // T = U 有效
// ===== 比较概念 =====std::equality_comparable<T> // 支持 ==std::totally_ordered<T> // 支持 < > <= >=
// ===== 可调用概念 =====std::invocable<F, Args...> // 可调用std::regular_invocable<F, Args...> // 常规可调用
// ===== 范围概念 =====std::ranges::range<R> // 范围std::ranges::input_range<R> // 输入范围std::ranges::output_range<R, T> // 输出范围std::ranges::forward_range<R> // 前向范围std::ranges::bidirectional_range<R> // 双向范围std::ranges::random_access_range<R> // 随机访问范围std::ranges::contiguous_range<R> // 连续范围26.8 完整示例:类型安全的算术库
Section titled “26.8 完整示例:类型安全的算术库”#include <concepts>#include <iostream>#include <vector>#include <numeric>
// ===== Concepts 定义 =====
// 可相加template<typename T>concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>;};
// 可相减template<typename T>concept Subtractable = requires(T a, T b) { { a - b } -> std::convertible_to<T>;};
// 可乘template<typename T>concept Multipliable = requires(T a, T b) { { a * b } -> std::convertible_to<T>;};
// 数学向量template<typename T>concept MathVector = requires(T v) { // 必须支持加减 { v + v } -> std::convertible_to<T>; { v - v } -> std::convertible_to<T>; // 必须支持标量乘法(向量 * 标量) { v * typename T::value_type{} } -> std::convertible_to<T>; // 必须有 begin/end v.begin(); v.end(); // 必须有 size() { v.size() } -> std::convertible_to<std::size_t>;};
// ===== 使用 Concepts 的函数 =====
template<Addable T>T add_all(T a, T b) { return a + b;}
template<Addable T, Subtractable T2> requires std::same_as<T, T2>T add_and_subtract(T a, T b) { return (a + b) - (a - b);}
// 向量点乘template<MathVector V>typename V::value_type dot_product(const V& a, const V& b) { typename V::value_type result = 0; for (std::size_t i = 0; i < a.size(); ++i) { result += a[i] * b[i]; } return result;}
// 向量加法template<MathVector V>V vector_add(const V& a, const V& b) { V result; result.reserve(a.size()); for (std::size_t i = 0; i < a.size(); ++i) { result.push_back(a[i] + b[i]); } return result;}
// ===== 测试 =====
struct Vec3 { using value_type = double; std::vector<double> data;
Vec3(std::initializer_list<double> il) : data(il) {}
auto begin() { return data.begin(); } auto end() { return data.end(); } auto begin() const { return data.begin(); } auto end() const { return data.end(); } auto size() const { return data.size(); } auto& operator[](std::size_t i) { return data[i]; } auto operator[](std::size_t i) const { return data[i]; }
auto operator+(const Vec3& other) const { Vec3 result(*this); for (std::size_t i = 0; i < data.size(); ++i) { result.data[i] += other.data[i]; } return result; }
auto operator-(const Vec3& other) const { Vec3 result(*this); for (std::size_t i = 0; i < data.size(); ++i) { result.data[i] -= other.data[i]; } return result; }
auto operator*(double scalar) const { Vec3 result(*this); for (auto& x : result.data) { x *= scalar; } return result; }};
int main() { // 基本运算 std::cout << "add_all(1, 2): " << add_all(1, 2) << "\n";
// 向量运算 Vec3 v1{1.0, 2.0, 3.0}; Vec3 v2{4.0, 5.0, 6.0};
std::cout << "dot_product: " << dot_product(v1, v2) << "\n"; auto v3 = vector_add(v1, v2); std::cout << "vector_add: "; for (auto x : v3) { std::cout << x << " "; } std::cout << "\n";
// 编译期验证 Concepts static_assert(Addable<int>); static_assert(Addable<double>); static_assert(MathVector<Vec3>);}26.9 章节总结
Section titled “26.9 章节总结”为什么需要 Concepts:
| 问题 | 传统模板 | Concepts |
|---|---|---|
| 类型约束 | 无,隐式 | 显式声明 |
| 错误信息 | 膨胀且难懂 | 清晰指出约束 |
| IDE 支持 | 有限 | 自动补全更智能 |
| 代码意图 | 不明确 | 文档化 |
标准库 Concepts 分类:
- 类型分类:
integral、floating_point、same_as - 语义概念:
movable、copyable、assignable - 比较概念:
equality_comparable、totally_ordered - 范围概念:
range、input_range、random_access_range
自定义 Concepts 语法:
template<typename T>concept Name = requires(T t) { // 简单要求 t.member();
// 类型要求 typename T::value_type;
// 复合要求(带属性检查) { t + t } -> std::convertible_to<T>;};requires 子句用法:
template<typename T> requires std::integral<T>T func(T n) { /* ... */ }下章预告:ch27 学习 C++20 Ranges 范围库,实现惰性求值和管道操作。