C++20 concept 的 requires 表达式是唯一能早期、精准、可读地约束“必须有某 public 成员变量”的标准方案,因 static_assert 依赖运行时语义且触发硬错误,而 requires 在模板推导阶段即验证成员可访问性。

必须用 requires 表达式检查成员变量可访问性,不能靠 static_assert 或裸写 T::member;C++20 的 concept 是目前唯一能精准、早期、可读地约束“必须有某 public 成员变量”的标准方案。
为什么 static_assert(obj.member) 会直接崩溃
这种写法在函数体里出现,就等于要求 obj 已构造且 member 可访问——但模板还没实例化完,编译器根本不会走到函数体,obj.member 就已触发硬错误。更糟的是,错误堆栈指向模板内部实现,而非你调用的地方。
-
static_assert是运行时语义的误用:它依赖表达式求值,而成员存在性是类型层面问题 - 哪怕加了
decltype(obj.member),也得先有obj,仍逃不开构造和访问权限检查 - 真正该做的是在参数推导阶段就拒绝不合法类型,而不是等进函数再报错
requires(T t) { t.member; } 怎么写才安全
这是最常用也最容易出错的写法。关键不是“语法对”,而是“语义准”:它检查的是“左值对象能否以点号访问该成员”,隐含要求成员是 public、非静态、非函数、非模板。
- 必须写
requires(T t) { t.member; },不能漏掉括号或参数声明;requires { t.member; }是非法语法 - 若
member是const成员,应传const T:requires(const T t) { t.member; } - 若
member是mutable或引用类型,此写法仍有效;但私有成员会失败(这是预期行为) - 不要写
{ &T::member }——那是 SFINAE 路线,requires不接受成员指针表达式
如何同时检查成员存在性和类型
只确认 t.id 存在不够,常还需它是个整型或可转为字符串。这时要用带返回类型约束的花括号表达式。
立即学习“C++免费学习笔记(深入)”;
- 检查是否为
int:{ t.id } -> std::same_as<int></int> - 检查是否可隐式转为
std::string:{ t.name } -> std::convertible_to<:string></:string> - 检查是否可读可写:
t.value = 42; { t.value } -> std::convertible_to<int></int> - 多个条件并列写在同一
requires块里,编译器会逐条验证,任一失败即约束不满足
定义可复用的 concept 时的坑
把 requires(T t) { t.id; } 封装成命名 concept 很自然,但容易忽略两件事:一是命名污染,二是误判私有成员。
- 别用太泛的名字如
HasId,工程中建议带上下文,比如LoggableHasId,避免和第三方库冲突 -
requires(T t) { t.id; }对私有id失败,这通常是好事——你本就不该依赖私有实现 - 如果真要兼容私有成员(比如友元场景),就得退回到
&T::id+std::is_member_object_pointer的 SFINAE 方案,requires做不到 - 别在
concept定义里调用实际函数或构造对象,所有内容必须是未求值表达式
最易被忽略的一点:requires 检查的是“对象表达式可访问性”,不是“类型声明存在性”。想查 typename T::value_type 这种嵌套类型,得换写法——{ typename T::value_type; },少一个花括号或写成 typename T::value_type{} 都会触发构造要求,导致约束失效。


















