requires 是编译期语义守门员,防止非法类型参与模板实例化;它不处理运行时崩溃(如空指针、环境变量缺失、死锁),专治类型约束失效导致的底层误操作,如无operator+类型参与加法、非容器类型用于范围for、负值作无符号索引等。

requires 不是用来“运行时防崩溃”的工具,而是编译期语义守门员——它不拦截非法变量的使用,而是让非法变量根本无法参与模板实例化。一旦类型或表达式不满足约束,编译直接失败,错误出现在构建阶段,而非生产环境。
明确 requires 的作用边界
它不处理以下场景:
- 运行时读取 null 指针(那是段错误/SIGSEGV,需靠内存安全设计或静态分析)
- Java/C# 中的 NullReferenceException(需用 requireNonNull、??、可空引用类型等)
- 环境变量缺失导致 JNI 崩溃(如 MY_MODEL_PATH 未设,requires 完全不介入)
- 死锁或竞态条件(那是线程调度与锁策略问题,需 std::lock、超时锁等)
真正能用 requires 预防的崩溃类型
它专治“本不该被允许的类型混入泛型逻辑”引发的底层误操作。典型例子:
- 把一个没有
operator+的自定义类型传给模板加法函数,避免链接期隐式转换或运行时未定义行为 - 要求模板参数必须支持
begin()/end(),防止对非容器类型调用范围 for 循环而触发编译通过但行为异常的代码 - 约束数值类型必须是无符号整数,杜绝负值被误解释为极大正数(如 size_t 传 -1 导致越界访问)
实战写法:从防御到契约
别只写“能编译”,要写“语义合法”。例如:
template<typename T>
concept ValidIndex = std::integral<T> &&
requires(T t) {
{ t >= 0 } -> std::same_as<bool>;
};
<p>template<ValidIndex I>
void process_at(I idx) {
// 这里 idx 必然 ≥ 0 且为整型 —— 编译器已担保
buffer[idx] = ...; // 消除下标为负的越界风险
}对比裸用 template<typename i></typename> + 手动 if (idx :后者可能被优化掉、被遗漏、或仅在部分路径检查;而 requires 约束让非法调用(如 <code>process_at(-5))在编译时报错,彻底杜绝该路径进入生产环境。
配合其他机制形成防线
requires 是第一道静态防线,需与以下协同:
- 构造函数验证(如 Go 的 NewAge、C++ 的 explicit 构造 + requires)确保对象创建即合法
- 运行时断言(
assert)保留关键不变量检查,用于调试和开发环境兜底 - 静态分析工具(Clang-Tidy、Cppcheck)扫描未覆盖的表达式边界
- 单元测试覆盖 requires 允许的最小/最大值、边界类型,验证约束逻辑本身正确

















