#include <concepts>// 定义 Concept:显式描述接口要求
template<typename T>
concept HasSize = requires(T a) {{ a.size() } -> std::convertible_to<std::size_t>;
};// 使用 Concept 约束模板
template<HasSize T>
void process(T& obj) {if (obj.size() > 10) {// ...}
}int main() {Blob b;process(b); // 错误
}
第一部分:定义规则 (The Concept Definition)
template<typename T>
concept HasSize = ...;
- 翻译: 我正在定义一个名为
HasSize的概念(Concept)。 - 含义: 以后只要我说某个类型是
HasSize,就意味着它必须通过等号后面的一系列测试。 - 类比: 比如我定义一个职位叫“合格会计”,这只是一个名称,具体的“任职资格”在等号后面。
第二部分:制定检查流程 (The requires Clause)
这是最让人头大的部分,我们逐行拆解:
requires(T a) {{ a.size() } -> std::convertible_to<std::size_t>;
};
1. requires(T a) —— “假设我们要面试 T”
- 这就像函数的参数列表。
- 意思是:为了测试类型
T是否合规,请编译器帮我虚构一个T类型的对象,名字叫a。 - 注意:这个
a只是用来做语法检测的“假人”,程序运行时并不会真的创建它。
2. { a.size() } —— “试着让它做一个动作”
- 这是复合要求(Compound Requirement)的核心语法。
- 外面的大括号:表示这是一个具体的测试表达式。
- 意思:编译器,请你检查一下,如果我写
a.size()这行代码,能不能通过编译?- 如果
T是Blob(没有 size 方法),这行代码无法编译,测试直接失败。 - 如果
T是std::vector,这行代码合法,继续往后看。
- 如果
3. -> —— “检查动作的结果”
- 这个箭头表示:我对刚才那个动作的返回值有要求。
4. std::convertible_to<std::size_t> —— “结果必须符合的标准”
- 这是 C++ 标准库预定义的一个概念。
- 意思:
a.size()的返回值,必须能被当作size_t(无符号整数)来使用。 - 如果不加这一段,只要有
size()函数就行,不管它返回什么;加了这一段,就限定了返回值必须是数字。
第三部分:执行检查 (The Usage)
// 以前的写法:来者不拒
// template<typename T> // 现在的写法:非诚勿扰
template<HasSize T>
void process(T& obj) { ... }
template<HasSize T>:这是 Concept 的应用。- 翻译:定义一个模板函数
process,参数类型是T。但是!这个T必须通过HasSize的面试。 - 效果:
- 当你传入
int(没有 size 方法) -> 编译器在门口拦截:❌ 拒绝,不满足 HasSize。 - 当你传入
Blob(没有 size 方法) -> 编译器在门口拦截:❌ 拒绝,不满足 HasSize。 - 当你传入
std::vector-> ✅ 通过。
- 当你传入
全文“人话”翻译
把那段看不懂的代码连起来,它的实际意思是:
定义标准: 我定义了一个叫
HasSize的标准。 标准内容是:假设给我一个类型为T的对象a,代码a.size()必须能编译通过,而且它的返回值必须能转换成整数。使用标准: 我有一个函数叫
process。 它接受一个类型T。 前提条件:这个T必须符合HasSize的标准。如果不符合,请不要让它进入函数体,直接在调用处报错。
这种语法的核心逻辑
为什么要用 requires(T a) { {expr} -> constraint; } 这种怪异的写法?
这是为了回答两个问题:
- 语法是否合法?(能不能调用
size()?)—— 由大括号{ a.size() }决定。 - 语义是否正确?(返回值是不是整数?)—— 由箭头
->决定。
这比以前 C++ 模板那种“不仅要能调用,还没法说清返回值是什么”的模糊状态要精确得多。