Skip to content

Ch 26: C++20 Concepts

  • 理解为什么需要 Concepts(概念)
  • 掌握标准库内置 Concepts
  • 学会定义和使用自定义 Concepts
  • 熟练使用 requires 子句和 requires 表达式
  • 理解 Concepts 如何改善错误信息和代码质量
# Python - 运行时 duck typing
def 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") # 运行时错误!TypeError

Python 的问题:

  • 类型错误在运行时才发现
  • 错误信息可能不直观
  • 难以进行静态分析
// C++ - 编译时检查(模板)
template<typename T>
T add(T a, T b) {
return a + b; // T 必须支持 + 运算
}
add(1, 2); // OK
add("a", "b"); // OK
add(1, "a"); // 编译错误!但错误信息可能很长

传统模板的问题:

  • 错误信息涉及模板内部膨胀
  • 难以判断”什么是真正的问题”
  • 不清楚需要什么约束
#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 支持更好(自动补全、类型检查)
#include <concepts>
#include <type_traits>
// 整数类型
static_assert(std::integral<int>); // true
static_assert(std::integral<char>); // true
static_assert(std::integral<unsigned>); // true
static_assert(!std::integral<double>); // false
static_assert(!std::integral<int*>); // false
// 浮点类型
static_assert(std::floating_point<double>); // true
static_assert(std::floating_point<float>); // true
static_assert(!std::floating_point<int>); // false
// 有符号/无符号整数
static_assert(std::signed_integral<int>); // true
static_assert(!std::signed_integral<unsigned>); // true
static_assert(std::unsigned_integral<unsigned>); // true
static_assert(!std::unsigned_integral<int>); // true
// 布尔
static_assert(std::same_as<bool, std::integral_constant<bool, true>::type>);
#include <concepts>
#include <memory>
// 可移动:可以 std::move
static_assert(std::movable<int>); // true
static_assert(std::movable<std::unique_ptr<int>>); // true
// 可拷贝:可以拷贝构造和拷贝赋值
static_assert(std::copyable<int>); // true
static_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>); // true
static_assert(std::assignable<std::string&, std::string>); // true
#include <concepts>
// 相等可比较:支持 ==
static_assert(std::equality_comparable<int>); // true
static_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>); // true
static_assert(std::totally_ordered<double>); // true
struct Version {
int major, minor;
auto operator<=>(const Version&) const = default;
};
static_assert(std::totally_ordered<Version>); // true
#include <concepts>
#include <ranges>
#include <vector>
// std::ranges::range - 支持 begin()/end()
static_assert(std::ranges::range<std::vector<int>>); // true
static_assert(std::ranges::range<int[]>); // true
static_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
#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>); // true
#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
};
// 组合 Concepts
template<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];
};
#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 Concept
template<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>); // true
static_assert(Hashable<std::string>); // true
struct NoHash {};
static_assert(!Hashable<NoHash>); // false
#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;
}
#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. 可序列化 Concept
template<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}); // 编译错误
}
#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;
}
#include <concepts>
#include <type_traits>
// requires 表达式返回 bool
template<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);
#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>;
};
#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
}
#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"); // 编译错误!没有匹配版本
}
#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 缩写语法)”
#include <concepts>
// C++20 缩写语法:用 auto 配合 Concept
std::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;
}
#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";
}
#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 派生自 U
std::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>);
}

为什么需要 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 范围库,实现惰性求值和管道操作。