requires子句通过表达式块静态检查类型是否支持某操作,仅验证语法和语义可行性,不执行代码;它不能替代trait判断,需组合使用以确保类型分类与接口契约均满足。

requires子句怎么检查类型是否支持某个操作
直接用 requires 表达式块列出该类型必须能合法执行的操作,编译器会在模板实例化前验证语法和语义可行性。它不运行代码,只看表达式能否“写出来”且类型匹配。
常见错误是把运行时逻辑(比如 a > 0)当编译期约束写进去——{ a > 0 } 检查的是 operator> 是否存在且可调用,不是检查值是否为正。
-
{ a + b }:仅要求+可被调用,不关心返回类型 -
{ a + b } -> std::same_as<t></t>:额外要求返回类型必须严格等于T -
{ a.begin() }:检查成员函数是否存在,不要求返回值类型 -
requires std::is_integral_v<t></t>:嵌套布尔常量表达式,适合简单 trait 判断
为什么不能只靠 std::is_integral_v 这类 trait
单靠 std::is_integral_v 只能判断类型分类,无法捕获接口契约。比如一个自定义大整数类 BigInt 可能是整型语义,但没重载 operator%;而 requires { a % 2 } 能真正验证它是否支持取模操作。
实际场景中,你往往需要组合:既要类型属于某一大类(如数值),又要具备特定接口(如可比较、可哈希)。这时就得混用:
立即学习“C++免费学习笔记(深入)”;
- 用
requires std::integral_v<t></t>快速筛掉浮点/类类型 - 再用
requires(T a) { { a & a } -> std::same_as<t>; }</t>确保支持位运算 - 二者缺一不可,否则约束不完整
requires 表达式里访问私有成员会失败吗
不会报错,但也不会通过——requires 表达式中的操作遵循和普通代码相同的访问控制规则。如果 a.private_func() 是私有的,且当前上下文无权访问,那么 { a.private_func() } 直接判定为不满足,整个约束失败。
这意味着:你不能用 requires 来“探测”类的私有接口,它只反映公开契约。这也正是设计本意:约束应基于用户可见的行为,而非实现细节。
容易踩的坑包括:
- 在概念定义里误写
{ obj.member }(非函数),结果编译器按“可访问的公有数据成员”检查,而你想查的是 getter 函数 - 传入右值临时对象后,在
requires块里调用只对左值有效的成员函数(如返回T&的data()),导致约束意外失败
多个 requires 子句怎么组合逻辑
可以用 && 和 || 显式连接,优先级和普通布尔表达式一致;也可把复杂条件拆进 concept 再复用。关键点是:所有 requires 都必须同时满足(即隐含 &&),除非你显式写 ||。
例如这个函数模板:
template<typename T>
requires std::default_constructible<T> &&
requires(T t) { t.size(); }
void process_container() { /* ... */ }
它等价于定义一个组合概念:
template<typename T>
concept SizedDefaultConstructible =
std::default_constructible<T> &&
requires(T t) { t.size(); };
后者更推荐——名字自带语义,错误提示里会直接显示 “SizedDefaultConstructible not satisfied”,比一长串 requires 条件易读得多。
注意:requires 子句里的布尔表达式必须是常量表达式(constexpr),不能含变量、函数调用或任何运行时成分。否则编译器直接拒掉。


















