进入C++20后,C++正式进入现代阶段,引入了更多方便开发的特性。本期我们就来介绍一下
相关代码提交至gitee:楼田莉子/Linux学习
官方文档:Constraints and concepts (since C++20) - cppreference.com
相关的头文件:Standard library header <concepts> (C++20) - cppreference.com
目录
[综合运用:偏序 + 普通重载规则](#综合运用:偏序 + 普通重载规则)
概念
**概念(concept)**是C++20引入的模板参数约束机制,它允许程序员明确指定模板参数必须满足的条件。概念本质上是一个编译时谓词,用于验证模板参数是否满足特定要求。概念会在模板实例化前检查类型是否满足条件,而不是在实例化后产生难以理解的错误。
在C++20之前,模板元编程向来是很困难的,其中分支编写困难和错误信息晦涩是主要原因。主要为:
接口模糊 :template<typename T> void sort(T& c); 仅靠参数名"T"无法表达它需要容器支持 begin()、end()、迭代器可随机访问等要求。
SFINAE/enable_if 泛滥 :为了对重载进行细粒度控制,代码中遍布类似 typename std::enable_if<...>::type 的模板黑魔法,可读性极差。
static_assert 时机太晚 :在函数体内使用 static_assert 报错时,模板已经被实例化,错误信息混杂了实例化上下文,且无法阻止重载决议选中不合适的版本。
错误信息灾难:一旦模板实例化失败,编译器从最深层报错,沿着实例化栈一路展开,产生海量文本,排查根本原因犹如大海捞针。
比如说C++20之前如果我们要约束迭代器,需要这么写
cpp
// C++17 典型写法:使用 enable_if 约束迭代器类型
template<typename Iter,
typename = std::enable_if_t<
std::is_base_of_v<std::random_access_iterator_tag,
typename std::iterator_traits<Iter>::iterator_category>>>
void advance(Iter& it, int n) {
it += n;
}
在 C++20 中,使用 `concept` 关键字来定义概念。基本语法如下:
cpp
// template-parameter-list 是模板参数列表
// concept-name 是概念的名称
// constraint-expression 是一个可以被求值为 bool 的常量表达式(通常是各种约束的组合)
template <template-parameter-list>
concept concept-name = constraint-expression;
概念的require主要有以下几种使用方式:
cpp
//方式一:直接使用 requires 表达式定义概念
//适用场景:需要检查类型是否具备某个成员函数或特定表达式语法时,例如容器、迭代器、可调用对象等。
template<typename T>
concept Hashable = requires(T a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
//方式二:基于类型特征(Type Traits)直接定义
//适用场景:简单的类型属性判断,如是否为整数、是否可平凡拷贝等。
template<typename T>
concept Integral = std::is_integral_v<T>;
template<typename T>
concept FloatingPoint = std::is_floating_point_v<T>;
//方式三:组合现有概念或类型特征
//适用场景:需要在已有概念基础上进行精化或交集/并集约束,构建层次化的概念体系。
template<typename T>
concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>;
template<typename T>
concept Sortable = std::permutable<T> && std::totally_ordered<T>;
//方式四:通过约束模板参数本身定义
//适用场景:当你想在已有概念基础上逐步添加约束,且希望重用概念名称作为模板参数声明的一部分
template<typename T>
concept Addable = requires(T a, T b) { a + b; };
template<Addable T> // 模板参数T已经被Addable约束
concept Multipliable = requires(T a, T b) { a * b; };
//也可以等效为:
template<typename T>
concept Multipliable = Addable<T> && requires(T a, T b) { a * b; };
相关接口
核心语言概念
| 概念 | 描述 |
|---|---|
same_as<T, U> |
指定 T 与 U 是同一类型 |
derived_from<T, U> |
指定 T 派生自 U |
convertible_to<From, To> |
指定 From 可隐式转换为 To |
common_reference_with<T, U> |
指定 T 与 U 共享一个公共引用类型 |
common_with<T, U> |
指定 T 与 U 共享一个公共类型 |
assignable_from<LHS, RHS> |
指定 RHS 类型的值可赋值给 LHS 类型 |
swappable<T> / swappable_with<T, U> |
指定对象可交换,或两种类型的对象可互相交换 |
算术概念
| 概念 | 描述 |
|---|---|
integral<T> |
指定 T 为整型 |
signed_integral<T> |
指定 T 为有符号整型 |
unsigned_integral<T> |
指定 T 为无符号整型 |
floating_point<T> |
指定 T 为浮点类型 |
生存期与构造概念
| 概念 | 描述 |
|---|---|
destructible<T> |
指定对象可销毁 |
constructible_from<T, Args...> |
指定 T 可从 Args... 构造 |
default_initializable<T> |
指定对象可默认初始化 |
move_constructible<T> |
指定对象可移动构造 |
copy_constructible<T> |
指定对象可拷贝构造和移动构造 |
比较概念
| 概念 | 描述 |
|---|---|
equality_comparable<T> |
指定 operator== 是一个等价关系 |
equality_comparable_with<T, U> |
指定 T 和 U 可跨类型做相等比较 |
totally_ordered<T> |
指定类型上的比较运算符产生全序 |
totally_ordered_with<T, U> |
指定 T 和 U 可跨类型做全序比较 |
对象概念
| 概念 | 描述 |
|---|---|
movable<T> |
指定对象可移动和交换 |
copyable<T> |
指定对象可拷贝、移动和交换 |
semiregular<T> |
指定对象可拷贝、移动、交换且可默认构造 |
regular<T> |
指定类型为正规类型,即既满足 semiregular 又满足 equality_comparable |
可调用概念
| 概念 | 描述 |
|---|---|
invocable<F, Args...> |
指定可调用类型 F 可用 Args... 调用 |
regular_invocable<F, Args...> |
指定 F 是保持相等性的可调用对象 |
predicate<F, Args...> |
指定 F 是一个返回布尔值的谓词 |
relation<F, T, U> |
指定 F 是一个二元关系 |
equivalence_relation<F, T, U> |
指定 F 是一个等价关系 |
strict_weak_order<F, T, U> |
指定 F 是一个严格弱序 |
约束
cpp
#include <iostream>
#include <type_traits>
#include <concepts>
#include <vector>
#include <string>
#include <algorithm>
// ============================================================
// 1. 自定义 concept --- 判断类型是否可打印 (支持 << 运算符)
// ============================================================
template<typename T>
concept Printable = requires(std::ostream& os, const T& val) {
{ os << val } -> std::convertible_to<std::ostream&>;
};
// ============================================================
// 2. 自定义 concept --- 判断类型是否为容器 (有 begin()/end())
// ============================================================
template<typename T>
concept Container = requires(T& c) {
typename T::value_type;
{ c.begin() } -> std::input_iterator;
{ c.end() } -> std::input_iterator;
};
// ============================================================
// 3. 使用 requires 子句约束函数模板
// ============================================================
template<typename T>
requires std::integral<T> || std::floating_point<T>
auto add(T a, T b) {
return a + b;
}
// ============================================================
// 4. 约束重载 --- 针对 Printable 类型的 print 函数
// ============================================================
template<Printable T>
void print(const T& val) {
std::cout << val << '\n';
}
// 针对容器的 print 重载 (要求容器元素也可打印)
// 排除 std::string 避免与 Printable 重载歧义 --- string 同时满足 Container 和 Printable
template<Container T>
requires Printable<typename T::value_type> && (!std::same_as<std::remove_cvref_t<T>, std::string>)
void print(const T& container) {
std::cout << "[ ";
bool first = true;
for (const auto& elem : container) {
if (!first) std::cout << ", ";
std::cout << elem;
first = false;
}
std::cout << " ]\n";
}
// ============================================================
// 5. 尾部 requires 子句 --- 简洁语法糖
// ============================================================
template<typename T>
auto multiply(T a, T b) requires std::integral<T> {
return a * b;
}
// ============================================================
// 6. requires 表达式 --- 编译期检测类型能力
// ============================================================
template<typename T>
constexpr bool has_size_method = requires(const T& obj) {
{ obj.size() } -> std::convertible_to<std::size_t>;
};
// ============================================================
// 7. 非类型模板参数的约束
// ============================================================
template<auto N>
requires (N > 0)
constexpr auto factorial() {
auto result = N;
for (auto i = N - 1; i > 0; --i)
result *= i;
return result;
}
// ============================================================
// 8. 嵌套约束 --- 要求类型既是整数又可比较
// ============================================================
template<typename T>
concept SortableIntegral = std::integral<T> && std::equality_comparable<T>;
template<SortableIntegral T>
void sort_print(std::vector<T>& vec) {
std::sort(vec.begin(), vec.end());
print(vec);
}
// ============================================================
// main --- 演示所有功能
// ============================================================
int main() {
// --- 约束函数模板 ---
std::cout << "add(3, 5) = " << add(3, 5) << '\n';
std::cout << "add(2.5, 1.5) = " << add(2.5, 1.5) << '\n';
std::cout << "multiply(4, 7) = " << multiply(4, 7) << '\n';
// add("hello", "world"); // 编译错误: const char* 不满足 integral/floating_point
// --- Printable 约束重载 ---
print(42);
print(3.14159);
print(std::string("hello C++20 constraints"));
// --- Container 约束重载 ---
std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};
print(vec);
// --- requires 表达式检测 ---
std::cout << "\n=== requires 表达式检测 ===\n";
std::cout << "std::vector<int> has size()? " << has_size_method<std::vector<int>> << '\n';
std::cout << "int has size()? " << has_size_method<int> << '\n';
// --- 非类型模板参数约束 ---
std::cout << "\n=== 非类型参数约束 ===\n";
std::cout << "factorial<1>() = " << factorial<1>() << '\n';
std::cout << "factorial<5>() = " << factorial<5>() << '\n';
std::cout << "factorial<10>() = " << factorial<10>() << '\n';
// factorial<0>(); // 编译错误: 不满足 N > 0 约束
// --- 嵌套约束 ---
std::cout << "\n=== 嵌套约束排序 ===\n";
std::vector<int> unsorted = {5, 3, 8, 1, 2};
sort_print(unsorted);
return 0;
}
约束的类型
原子约束
原子约束是约束体系的最小单元,由一个表达式 E 和一个从模板形参到模板实参的映射 (parameter mapping)组成。它不是直接编写的,而是由编译器在规范化过程中生成的。原子约束的逻辑值取决于表达式 E 的求值结果(true 或 false)。
特点:
-
原子约束不可再分,是约束满足性检查的基础。
-
两个原子约束相同,当且仅当它们的表达式和映射都相同,这对约束的可包含性(subsumption)判断至关重要。
-
即使两个原子约束的表达式看起来完全相同,如果它们起源于不同的源代码位置,可能被视为不同的原子约束(由于映射不同)。
cpp
template<typename T>
concept Integral = std::is_integral_v<T>; // 原子约束:std::is_integral_v<T>
合取
合取就是多个约束的"与"(&&)组合。模板参数的约束检查时,会逐个验证合取中的每个约束,短路求值。
特点:
-
合取用于同时要求多个条件,是概念精化(refinement)的自然方式。
-
在概念包含规则中,一个合取约束可以被其子集所包含,支持概念的重载和特化。
cpp
template<typename T>
concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>;
析取
析取是多个约束的"或"(||)组合。同样采用短路求值,只要有一个约束满足,整个析取就满足。
特点:
-
析取允许模板接受多种不同类型中的任何一种,常用于定义"变体"概念。
-
要注意析取可能削弱概念的特异性,影响重载决议的精确性。
cpp
template<typename T>
concept Numeric = std::is_integral_v<T> || std::is_floating_point_v<T>;
代码示例:
cpp
#include <iostream>
#include <concepts>
#include <type_traits>
#include <vector>
#include <string>
#include <algorithm>
// ===================================================================
// PART 1: concept 定义 --- 命名编译期谓词的两种写法
// ===================================================================
// 写法A: 用 requires 表达式定义(最灵活,可检测任意语法)
template<typename T>
concept Addable = requires(T a, T b) {
a + b; // ① 简单要求: 仅检查 a+b 是否"能编译通过"
};
// 写法B: 直接组合已有 concept 或类型萃取(简洁)
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// ===================================================================
// PART 2: requires 子句 --- 四种书写位置
// ===================================================================
// 位置1: 模板参数列表之后
template<typename T>
requires Numeric<T>
auto twice(T val) { return val * 2; }
// 位置2: 函数签名尾部 (trailing requires)
template<typename T>
auto half(T val) requires std::floating_point<T> {
return val / 2;
}
// 位置3: 非类型模板参数的约束
template<auto N>
requires (N > 0 && N < 100)
constexpr auto small_factorial() {
auto r = N;
for (auto i = N - 1; i > 0; --i) r *= i;
return r;
}
// 位置4: 成员函数上的约束
struct Calculator {
template<typename T>
requires Numeric<T>
T square(T x) { return x * x; }
};
// ===================================================================
// PART 3: requires 表达式 --- 四种"要求"详解
// ===================================================================
// --- ① 简单要求 (Simple Requirement) ---
// 仅检查表达式"能否编译",不关心结果类型
template<typename T>
concept HasIncrement = requires(T x) {
++x; // 检查:x 能否前置自增?
x++; // 检查:x 能否后置自增?
};
// --- ② 类型要求 (Type Requirement) ---
// 检查某个嵌套类型 / 别名 / 特化是否存在
template<typename T>
concept HasValueType = requires {
typename T::value_type; // T 是否有 value_type 成员类型?
typename T::iterator; // T 是否有 iterator 成员类型?
};
// --- ③ 复合要求 (Compound Requirement) ---
// 检查表达式语法合法 AND 返回值满足某个 concept
template<typename T>
concept HasStdApi = requires(const T& obj) {
{ obj.size() } -> std::convertible_to<std::size_t>; // 返回值可转 size_t
{ obj.empty() } -> std::convertible_to<bool>; // 返回值可转 bool
};
// --- ④ 嵌套要求 (Nested Requirement) ---
// 在 requires 内部再次对模板参数施加布尔约束
template<typename T>
concept SortableContainer = requires(T& c) {
typename T::value_type;
requires std::equality_comparable<typename T::value_type>; // 嵌套: 值类型必须可比较
{ c.begin() } -> std::input_iterator;
{ c.end() } -> std::input_iterator;
{ std::sort(c.begin(), c.end()) }; // 简单: 排序表达式合法
};
// ===================================================================
// PART 4: 类型约束 (Type Constraint) --- concept 替代 typename
// ===================================================================
// 形式A: 在模板参数列表中用 concept 替代 typename
template<Numeric T>
T add(T a, T b) { return a + b; }
// 形式B: concept auto --- abbreviated function template
auto subtract(Numeric auto a, Numeric auto b) { return a - b; }
// 形式C: 混合使用,既约束类型又保持参数关联
template<typename T, Numeric U>
requires std::convertible_to<T, U>
U scale(T factor, U base) {
return static_cast<U>(factor) * base;
}
// ===================================================================
// PART 5: 约束组合 --- 合取 / 析取 / 原子约束
// ===================================================================
// 合取 && : T 必须同时满足多个约束(短路求值,从左到右)
template<typename T>
concept PrintableAndNumeric = Numeric<T> &&
requires(std::ostream& os, const T& val) { os << val; };
// 析取 || : T 满足任意一个即可
template<typename T>
concept StringOrNumber = std::integral<T> || std::floating_point<T> ||
std::same_as<std::remove_cvref_t<T>, std::string>;
// 合取 + 析取混合
template<typename T>
requires (Numeric<T> || std::same_as<T, std::string>) && HasStdApi<T>
void describe(const T& val) {
std::cout << "value has size = " << val.size() << '\n';
}
// ===================================================================
// PART 6: 约束在特殊位置的应用
// ===================================================================
// 6.1 类模板偏特化约束 --- 根据约束匹配不同的特化版本
template<typename T>
struct Category { static constexpr const char* name = "unknown"; };
template<Numeric T>
struct Category<T> { static constexpr const char* name = "numeric"; };
template<std::same_as<std::string> T>
struct Category<T> { static constexpr const char* name = "string"; };
// 6.2 非类型模板参数约束 --- 用 constexpr 函数代替变量模板偏特化
template<auto N>
requires (N >= 0)
constexpr bool is_even() { return N % 2 == 0; }
// ===================================================================
// PART 7: 约束偏序规则 --- 更"严格"的约束优先匹配
// ===================================================================
template<typename T>
requires Numeric<T>
const char* type_name(T) { return "numeric"; }
template<typename T>
requires std::integral<T> // integral 比 Numeric 更严格
const char* type_name(T) { return "integral (more constrained)"; }
// ===================================================================
// main --- 驱动演示
// ===================================================================
int main() {
std::cout << "=== PART 1: concept 定义 ===\n";
std::cout << std::boolalpha;
std::cout << "Addable<int>: " << Addable<int> << '\n';
std::cout << "Addable<ostream>: " << Addable<std::ostream> << '\n';
std::cout << "\n=== PART 2: requires 子句位置 ===\n";
std::cout << "twice(21) = " << twice(21) << '\n';
std::cout << "half(9.0) = " << half(9.0) << '\n';
std::cout << "small_factorial<5>() = " << small_factorial<5>() << '\n';
Calculator calc;
std::cout << "calc.square(7) = " << calc.square(7) << '\n';
std::cout << "\n=== PART 3: requires 表达式四种要求 ===\n";
std::cout << "HasIncrement<int>: " << HasIncrement<int> << '\n';
std::cout << "HasIncrement<string>: " << HasIncrement<std::string> << '\n';
std::cout << "HasValueType<vector<int>>: " << HasValueType<std::vector<int>> << '\n';
std::cout << "HasValueType<int>: " << HasValueType<int> << '\n';
std::cout << "HasStdApi<vector<int>>: " << HasStdApi<std::vector<int>> << '\n';
std::cout << "SortableContainer<vector<int>>: " << SortableContainer<std::vector<int>> << '\n';
std::cout << "\n=== PART 4: 类型约束 ===\n";
std::cout << "add(3, 8) = " << add(3, 8) << '\n';
std::cout << "add(1.5, 2.5) = " << add(1.5, 2.5) << '\n';
std::cout << "subtract(10, 3.5) = " << subtract(10, 3.5) << '\n';
std::cout << "scale(2.5, 10) = " << scale(2.5, 10) << '\n';
std::cout << "\n=== PART 5: 约束组合 ===\n";
std::cout << "PrintableAndNumeric<int>: " << PrintableAndNumeric<int> << '\n';
std::cout << "StringOrNumber<string>: " << StringOrNumber<std::string> << '\n';
describe(std::string("constraint combo demo")); // string 满足 Numeric||string + HasStdApi
std::cout << "\n=== PART 6: 类模板特化 + 非类型约束 ===\n";
std::cout << "Category<int>: " << Category<int>::name << '\n';
std::cout << "Category<double>: " << Category<double>::name << '\n';
std::cout << "Category<string>: " << Category<std::string>::name << '\n';
std::cout << "Category<char*>: " << Category<char*>::name << '\n';
// lambda 中也可以直接使用 concept auto
auto squared = [](Numeric auto x) { return x * x; };
std::cout << "lambda squared(3.14) = " << squared(3.14) << '\n';
std::cout << "is_even<4>() = " << is_even<4>() << '\n';
std::cout << "is_even<7>() = " << is_even<7>() << '\n';
std::cout << "\n=== PART 7: 约束偏序 ===\n";
std::cout << "type_name(42): " << type_name(42) << '\n';
std::cout << "type_name(3.14): " << type_name(3.14) << '\n';
return 0;
}
约束的偏序规则
在C++20的约束体系中,偏序规则(Partial Ordering of Constraints) 的核心是通过约束包含(Constraint Subsumption) 来决定模板重载的优先级。它使得编译器能够精准选择"最受约束"的模板版本,彻底告别了过去需要手写 std::enable_if 或标签分派来人为排序的重载控制时代。
核心要点
约束包含是这样一种编译期关系:我们说约束 P 包含(subsume)约束 Q ,当且仅当 P 蕴含 Q,即满足 P 的模板实参必然满足 Q。此时,约束 P 比 Q 更受约束(constrained)。在模板重载决议中,更受约束的版本会被优先匹配。
约束包含是基于原子约束的同一性 (identity)进行判断的,而不是基于表达式的逻辑等价性。编译器会对概念和约束进行规范化(normalization),将其分解为原子约束的合取/析取表达式,然后检查一个约束的原子集合是否是另一个约束原子集合的超集。
主要包含以下内容:
1. 原子约束 (Atomic Constraint)
约束归一化后不可再分的单元。表达式 P && Q 有两个原子约束 P 和 Q;
P || Q 也是两个原子约束(析取范式中仍各自独立)。
2. 包含关系 (Subsumption)
约束 P "包含"(subsume) 约束 Q 当且仅当:
-
P 的原子约束集合是 Q 的原子约束集合的子集(在析取范式中)
-
编译器通过原子约束的"字面同一性"来判断,而非逻辑等价
3. 判定规则
执行重载决议时:
-
对每个候选模板,将其约束归一化
-
如果模板A的约束包含模板B的约束,且反向不成立
→ A 比 B "更受约束"(more constrained) → A 优先
关键陷阱:只有字面相同的表达式才会触发包含关系。
integral<T> 和 is_same_v<T, int> || is_same_v<T, long> || ...
编译器不会推理它们的语义等价。
应用场景
函数模板重载决议
当多个函数模板可用且各自具有不同的约束时,编译器会构造约束偏序:
-
如果模板 A 的约束包含模板 B 的约束,并且 B 的约束不包含 A 的约束,则 A 比 B 更特化(在约束意义上),A 被优先选择。
-
如果约束不可比较(即没有包含关系),则重载集产生二义性,编译错误。
cpp
template<typename T>
concept Integral = std::is_integral_v<T>;
template<typename T>
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
template<Integral T> void f(T) { /* 1 */ }
template<SignedIntegral T> void f(T) { /* 2 */ }
f(1); // 调用版本2,因为SignedIntegral包含Integral
f(1u); // 调用版本1,unsigned满足Integral但不满足SignedIntegral
非模板函数与约束模板函数
-
如果存在完全匹配的非模板函数,它通常比任何模板更优(这是通用规则,与约束无关)。
-
如果都是模板,则约束偏序起作用。
综合运用:偏序 + 普通重载规则
约束偏序是重载决议的最后阶段(在可行函数集确定后)的一种排序机制。它发生在模板实参推导之后、更好的转换序列比较之后。如果一个约束模板比其他约束模板"更受约束",它将被认为比另一个更特化(specialized),从而胜出。
约束包含的规则细节
-
规范化(Normalization)
编译器将概念名和
requires表达式转换为规范形式:概念名被替换为其定义的约束表达式,requires表达式中的各个要求也被分解为原子约束序列。规范化后的约束表示为原子约束的合取(&&)和析取(||)的集合。 -
原子约束的同一性
两个原子约束被视为相同,当且仅当它们来自同一个词法位置且具有相同的模板实参映射。例如:
cpptemplate<typename T> concept A = std::is_integral_v<T>; template<typename T> concept B = std::is_integral_v<T>;尽管
A<int>和B<int>都是true,但它们是不同的原子约束 ,因为它们来自不同的概念定义。因此A不包含B,B也不包含A。这容易导致意外,所以理想的做法是通过继承概念来复用原子约束:cpptemplate<typename T> concept B = A<T> && ...; // B包含A -
合取与析取的包含关系
-
若
P是P1 && P2,则P包含P1,也包含P2(合取蕴含各个子项)。 -
若
Q是Q1 || Q2,则Q1包含Q,Q2包含Q(子项蕴含析取)。 -
合取不包含析取,除非每个合取子项都单独包含析取。
-
-
requires表达式中的包含规则-
一个
requires表达式包含另一个,如果前者的每个要求都包含后者的对应要求。 -
嵌套的
requires子句在规范化时也会被展开为原子约束序列。
-
代码
cpp
#include <iostream>
#include <concepts>
#include <string>
// ===================================================================
// 演示1: 基础包含 --- integral 包含 signed_integral
// ===================================================================
// signed_integral = integral + signed,原子约束更严格
template<typename T>
concept MySignedIntegral = std::integral<T> && std::signed_integral<T>;
template<std::integral T> // 泛:
const char* f1(T) { return "integral (泛)"; }
template<MySignedIntegral T> // 细: integral + signed → 包含前者
const char* f1(T) { return "signed_integral (细, 优先)"; }
// ===================================================================
// 演示2: 合取包含 --- (A && B) 包含 A,也包含 B
// ===================================================================
template<typename T>
concept Big = std::integral<T> && sizeof(T) >= 4;
template<std::integral T> // 只要求 integral
const char* f2(T) { return "integral only"; }
template<Big T> // 要求 integral + sizeof>=4 → 更细
const char* f2(T) { return "integral && sizeof>=4 (优先)"; }
// ===================================================================
// 演示3: 析取不包含 --- A 不包含 (A || B)
// ===================================================================
// 注意:integral 的原子约束并不等于 (integral || floating_point) 中的
// integral 原子约束 ------ 它们是同一个概念在源码中的字面引用,
// 所以 integral 包含 integral||floating_point 中的 integral 原子约束
// 但 (integral||floating) 不包含 integral(析取向对方不成立)
template<typename T>
concept IntegralOrFloat = std::integral<T> || std::floating_point<T>;
template<std::integral T> // 只含一个原子: integral<T>
const char* f3(T) { return "integral (细, 优先)"; }
template<IntegralOrFloat T> // 含: integral<T> || floating<T>
const char* f3(T) { return "integral||floating (泛)"; }
// ===================================================================
// 演示4: 字面同一性 --- 别名不影响包含
// ===================================================================
template<typename T>
concept IntA = std::integral<T>;
template<typename T>
concept IntB = std::integral<T>; // 与 IntA 使用相同的原子约束 integral<T>
template<IntA T>
const char* f4(T) { return "IntA"; }
template<IntB T>
const char* f4(T) { return "IntB"; } // 与 f4<IntA> 约束完全相同, 编译歧义!
// ===================================================================
// 演示5: 陷阱 --- 语义等价 ≠ 字面同一
// ===================================================================
// 陷阱场景: 两个语义等价的约束,但编译器不认为它们有包含关系
template<typename T>
constexpr bool is_small_v = sizeof(T) <= 8;
template<typename T>
concept SmallBySizeof = (sizeof(T) <= 8);
// 下面两个约束语义完全相同,但一个是直接表达式,一个是bool常量
template<typename T>
requires (sizeof(T) <= 8)
const char* f5(T) { return "requires sizeof(T) <= 8"; }
// template<typename T>
// requires is_small_v<T>
// const char* f5(T) { return "requires is_small_v<T>"; }
// 这两个重载不会被编译器判定为有偏序关系!
// 因为 is_small_v<T> 和 (sizeof(T) <= 8) 不是字面相同的表达式
// → 编译器报歧义错误 (ambiguous)
// ===================================================================
// 演示6: 多级包含链
// ===================================================================
template<typename T>
concept L1 = std::integral<T>;
template<typename T>
concept L2 = L1<T> && std::signed_integral<T>;
template<typename T>
concept L3 = L2<T> && (sizeof(T) == 4);
template<L1 T> const char* f6(T) { return "L1: integral"; }
template<L2 T> const char* f6(T) { return "L2: integral && signed (优先于L1)"; }
template<L3 T> const char* f6(T) { return "L3: integral && signed && sizeof==4 (最优先)"; }
// ===================================================================
// main
// ===================================================================
int main() {
std::cout << std::boolalpha;
// --- 演示1 ---
std::cout << "=== 1. 基础包含 ===\n";
std::cout << "f1(42): " << f1(42) << '\n';
std::cout << "f1(42u): " << f1(42u) << '\n'; // unsigned → 不满足 signed
std::cout << "f1(3.14): (浮点数不满足 integral, 编译错误)\n";
// --- 演示2 ---
std::cout << "\n=== 2. 合取包含 ===\n";
std::cout << "f2(short): " << f2(short{1}) << '\n'; // sizeof=2 < 4 → 只满足 integral
std::cout << "f2(int): " << f2(42) << '\n'; // sizeof=4 → 满足 Big
// --- 演示3 ---
std::cout << "\n=== 3. 析取 vs 单一 ===\n";
std::cout << "f3(42): " << f3(42) << '\n'; // integral 更细 → 优先
std::cout << "f3(3.14): " << f3(3.14) << '\n'; // 只满足 IntegralOrFloat
// --- 演示4 ---
std::cout << "\n=== 4. 字面同一性 ===\n";
// f4(42); // 编译错误! IntA 和 IntB 的原子约束字面相同 → 歧义
std::cout << "注释掉 f4 调用即可编译。(IntA 和 IntB 都展开为 integral<T>)\n";
// --- 演示5 ---
std::cout << "\n=== 5. 语义等价 != 字面同一 ===\n";
std::cout << "SmallBySizeof<int>: " << SmallBySizeof<int> << '\n';
std::cout << "is_small_v<int>: " << is_small_v<int> << '\n';
std::cout << "两者语义等价, 但编译器不会判定偏序 → 重载歧义\n";
// --- 演示6 ---
std::cout << "\n=== 6. 多级包含链 ===\n";
std::cout << "f6(int): " << f6(42) << '\n'; // sizeof=4,signed → L3
std::cout << "f6(long): " << f6(42L) << '\n'; // sizeof=8,signed → L2
std::cout << "f6(uint32_t): " << f6(42u) << '\n'; // unsigned → L1
return 0;
}
综合使用
cpp
#include <iostream>
#include <concepts>
#include <functional> // std::hash
#include <string>
#include <string_view>
#include <vector>
#include <iomanip>
// ===================================================================
// HashType concept
// 对于 T 类型的值 a,表达式 std::hash<T>{}(a) 可以编译
// 并且它的结果可以转换到 std::size_t
// ===================================================================
template<typename T>
concept HashType = requires(const T& a) {
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
// ===================================================================
// 自定义类型 --- 有 std::hash 特化的 / 没有的
// ===================================================================
// Point2D: 没有 std::hash 特化 → 不满足 HashType
struct Point2D {
int x, y;
bool operator==(const Point2D&) const = default;
};
// Color: 有 std::hash 特化 → 满足 HashType
struct Color {
unsigned char r, g, b;
bool operator==(const Color&) const = default;
};
// Color 的输出运算符
std::ostream& operator<<(std::ostream& os, const Color& c) {
return os << '#' << std::hex << std::setfill('0')
<< std::setw(2) << static_cast<int>(c.r)
<< std::setw(2) << static_cast<int>(c.g)
<< std::setw(2) << static_cast<int>(c.b) << std::dec;
}
// 为 Color 特化 std::hash
template<>
struct std::hash<Color> {
std::size_t operator()(const Color& c) const noexcept {
return (static_cast<std::size_t>(c.r) << 16) |
(static_cast<std::size_t>(c.g) << 8) |
static_cast<std::size_t>(c.b);
}
};
// ===================================================================
// 1. 约束重载 --- 可哈希类型
// ===================================================================
template<HashType T>
void hash_info(const T& val) {
auto h = std::hash<T>{}(val);
std::cout << " hash = " << h << '\n';
}
// ===================================================================
// 2. 约束重载 --- 不可哈希类型(回退)
// ===================================================================
template<typename T>
requires (!HashType<T>)
void hash_info(const T& /*val*/) {
std::cout << " (no std::hash specialization available)\n";
}
// ===================================================================
// 3. 利用 HashType 约束实现泛型哈希容器 Key 检查
// ===================================================================
template<HashType T>
struct KeyWrapper {
T value;
std::size_t key() const { return std::hash<T>{}(value); }
void print() const {
std::cout << " value = " << value << ", key = " << key() << '\n';
}
};
// ===================================================================
// 4. type_trait 风格的编译期分支 --- 用 requires 表达式做 if constexpr
// ===================================================================
template<typename T>
std::size_t safe_hash(const T& val) {
if constexpr (HashType<T>) {
return std::hash<T>{}(val);
} else {
// 回退: 将指针地址作为哈希(仅用于演示, 非生产级)
return std::hash<const void*>{}(std::addressof(val));
}
}
// ===================================================================
// 5. concept 嵌套约束 --- HashableContainer
// ===================================================================
template<typename T>
concept HashableContainer =
requires { typename T::value_type; } &&
requires(T& c) { c.begin(); c.end(); } &&
HashType<typename T::value_type>; // 嵌套: 值类型必须可哈希
template<HashableContainer T>
void container_hash_summary(const T& container) {
std::size_t combined = 0;
for (const auto& elem : container) {
combined ^= std::hash<typename T::value_type>{}(elem) + 0x9e3779b9 +
(combined << 6) + (combined >> 2);
}
std::cout << " container combined hash = " << combined << '\n';
}
// ===================================================================
// main
// ===================================================================
int main() {
std::cout << std::boolalpha;
// --- 类型检测 ---
std::cout << "=== HashType 检测 ===\n";
std::cout << "HashType<int>: " << HashType<int> << '\n';
std::cout << "HashType<long>: " << HashType<long> << '\n';
std::cout << "HashType<std::string>: " << HashType<std::string> << '\n';
std::cout << "HashType<std::string_view>: " << HashType<std::string_view> << '\n';
std::cout << "HashType<const char*>: " << HashType<const char*> << '\n';
std::cout << "HashType<Point2D>: " << HashType<Point2D> << '\n'; // false
std::cout << "HashType<Color>: " << HashType<Color> << '\n'; // true (有特化)
// --- 约束重载分派 ---
std::cout << "\n=== 约束重载分派 ===\n";
std::cout << "int(42):\n"; hash_info(42);
std::cout << "string(\"abc\"):\n"; hash_info(std::string("abc"));
std::cout << "string_view:\n"; hash_info(std::string_view("hello"));
std::cout << "Point2D{1,2}:\n"; hash_info(Point2D{1, 2});
std::cout << "Color{255,128,64}:\n"; hash_info(Color{255, 128, 64});
// --- 泛型 KeyWrapper ---
std::cout << "\n=== KeyWrapper (仅限 HashType) ===\n";
KeyWrapper<int> kw_int{42};
kw_int.print();
KeyWrapper<std::string> kw_str{"key_demo"};
kw_str.print();
KeyWrapper<Color> kw_color{{255, 0, 127}};
kw_color.print();
// KeyWrapper<Point2D> kw_pt{{1,2}}; // 编译错误: Point2D 不满足 HashType
// --- safe_hash 编译期分支 ---
std::cout << "\n=== safe_hash (if constexpr 分支) ===\n";
std::cout << "safe_hash(100): " << safe_hash(100) << '\n';
std::cout << "safe_hash(Point2D{3,4}): " << safe_hash(Point2D{3, 4}) << " (指针退避)\n";
// --- HashableContainer ---
std::cout << "\n=== HashableContainer ===\n";
std::vector<int> vi = {10, 20, 30};
container_hash_summary(vi);
std::vector<std::string> vs = {"foo", "bar", "baz"};
container_hash_summary(vs);
// std::vector<Point2D> vp = {{1,2}}; container_hash_summary(vp); // 编译错误
return 0;
}
封面图自取:
