C++20用requires表达式检查成员可访问性最清晰准确;C++17及以前需std::void_t+成员指针探测,否则易致硬错误;static_assert无法用于运行时表达式obj.member,必须在模板实例化早期完成存在性检查。

直接用 requires 表达式检查成员可访问性,是 C++20 下最清晰、错误提示最准的方式;C++17 及以前只能靠 std::void_t + 成员指针探测,但写错位置就变硬错误,不是 SFINAE。
为什么 static_assert(obj.member) 会崩
这行代码根本过不了编译:它在函数体里求值,而 obj.member 是运行时表达式,且未定义时编译器连函数体都进不去,直接报 error: 'member' is not a member of 'T',堆栈深、定位难。真正要约束的是“类型 T 是否支持 t.member 这种语法”,不是“某个对象有没有这个字段”。
-
static_assert必须作用于编译期常量表达式,obj.member不满足 - 哪怕写成
static_assert(std::is_same_v<decltype int>)</decltype>,前置的t.member访问已导致硬失败 - 所有成员存在性检查必须在模板参数推导/实例化早期完成,不能拖到函数体内
requires(T t) { t.member; } 的实操要点
这是最常用也最容易写错的写法。它检查的是“对左值 t 能否合法使用点号访问 member”,语义上要求该成员是公有、非静态、可读(至少能取地址或取值)。
- 必须写
requires(T t) { t.member; },不能漏掉括号包裹的表达式,否则语法错误 - 若
member是const或volatile限定的,传T const t更稳妥 - 若
member是mutable或引用类型,该写法仍有效——t.member合法即通过 - 不检查类型,只检查可访问性;要加类型约束得写成
{ t.member } -> std::same_as<int></int> - 错误信息类似:
constraint failure: requires (T t) { t.id; } is not satisfied,直接指向调用点
C++17 兼容方案:HasMemberXxx trait 怎么写才不翻车
标准写法依赖 std::void_t 包裹 decltype(&T::member),再用 std::is_member_object_pointer 做二次过滤,避免把成员函数指针误判为成员变量。
立即学习“C++免费学习笔记(深入)”;
- 主模板必须设为
std::false_type,偏特化才启用:template<typename t typename="void"> struct HasMemberXxx : std::false_type {};</typename> - 偏特化里必须用
std::void_t<decltype></decltype>,不能只写decltype(&T::member),否则 SFINAE 失效 - 结尾要接
: std::is_member_object_pointer<decltype></decltype>,否则&T::func也会通过 - 函数模板中启用方式:
template<typename t std::enable_if_t>::value>* = nullptr></typename> - 宏辅助可参数化成员名,但每个成员需独立 trait,无法像 Concepts 那样一行写多个要求
容易被忽略的权限与上下文陷阱
私有成员、继承链中的成员、模板类里的依赖名称,都会让看似简单的 t.member 检查失败,但原因完全不同。
-
requires(T t) { t.member; }对私有成员直接失败,因为点号访问违反访问控制;&T::membertrait 在友元或同名嵌套类中可能意外通过 - 若
member来自基类且未显式引入,ADL 不起作用,t.member找不到,需在派生类中用using Base::member; - 在类模板内部写
requires时,若member是依赖名称(比如T::value_type),必须加typename,但requires里不能直接写typename,得换用requires { typename T::value_type; } - 检查静态成员变量(如
T::max_size)要写{ T::max_size },不能写t.max_size,因为不需要实例


















