解析C++中两个混合单双大括号的requires子句匹配规则
C++类成员数量统计方法中requires子句解析
这是Reddit上一份用来统计C类/结构体成员数量的代码,利用C20的consteval和requires特性实现:
struct UniversalType { template<typename T> operator T(); }; template<typename T, typename... A0> consteval auto MemberCounter(auto ...c0) { if constexpr (requires { T{ {A0{}}..., {UniversalType{}}, c0... }; } == false && requires { T{ {A0{}}..., c0..., UniversalType{} }; } == false ) { return sizeof...(A0) + sizeof...(c0); } else if constexpr (requires { T{ {A0{}}..., {UniversalType{}}, c0... }; }) { return MemberCounter<T,A0...,UniversalType>(c0...); } else { return MemberCounter<T,A0...>(c0...,UniversalType{}); } } using TestType = struct { int x[3]; float y; char z; }; static_assert (MemberCounter<TestType>() == 3); int main() {}
代码里两个混合使用单、双大括号的requires子句是实现核心,下面分别解析它们的作用:
第一个requires子句:requires { T{ {A0{}}..., {UniversalType{}}, c0... }; }
- 这里的
T{ ... }是聚合初始化逻辑(目标是无自定义构造函数的普通结构体),内部的大括号序列对应结构体成员的初始化项 {A0{}}...:展开模板参数包A0,每个A0{}外层套大括号,用来匹配之前识别出的需要大括号初始化的聚合类型成员(比如数组、嵌套结构体){UniversalType{}}:UniversalType是能隐式转换成任意类型的“万能类型”,外层套大括号后,用来尝试匹配下一个聚合类型成员——如果结构体当前待匹配的成员是聚合类型(比如示例里的int x[3]),这种带大括号的万能类型可以完成初始化,触发该requires表达式为真c0...:展开函数参数包c0,这些是不带大括号的UniversalType实例,对应已经识别出的非聚合类型成员
简单说,这个表达式的作用是:检查能否在已匹配的成员之后,用带大括号的万能类型匹配下一个聚合类型成员。如果可以,就将UniversalType加入模板参数包A0(对应递归调用MemberCounter<T,A0...,UniversalType>(c0...)),标记该成员为聚合类型。
第二个requires子句:requires { T{ {A0{}}..., c0..., UniversalType{} }; }
- 同样基于聚合初始化逻辑,
UniversalType{}没有额外套大括号,代表尝试用非大括号形式初始化下一个成员 - 这个表达式用来匹配非聚合类型成员(比如示例里的
float y、char z):这类成员可以直接通过UniversalType的隐式转换完成初始化,不需要额外大括号包裹 - 如果该表达式为真,就将不带大括号的
UniversalType加入函数参数包c0(对应递归调用MemberCounter<T,A0...>(c0...,UniversalType{})),标记该成员为非聚合类型
整体逻辑总结
递归过程中,我们不断用两种方式尝试为结构体添加一个成员的初始化项:
- 优先尝试带大括号的万能类型,匹配聚合类型成员
- 若失败则尝试不带大括号的,匹配非聚合类型成员
直到两种方式都失败(说明所有成员都匹配完成),此时sizeof...(A0) + sizeof...(c0)的结果就是结构体的成员总数量。
内容的提问来源于stack exchange,提问作者abcdefg
相关产品推荐
相关产品推荐

