这个post讲了一个计算班级成员数量的方法:
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 { T{ {A0{}}..., {UniversalType{}}, c0... }; }
requires { T{ {A0{}}..., c0..., UniversalType{} }; }
问题:他们到底允许匹配什么样的物品?
提示就在
TestType
,它是特意设计来演示嵌套{}
解决的问题。
计算成员变量的前提是检查该类型在聚合初始化中可接受的最大参数数量。这可以在不知道成员类型的情况下通过
UniversalType
来完成,它可以伪造为任何类型。例如
struct A { int a, b; };
A{UniversalType{}}; // compiles
A{UniversalType{}, UniversalType{}}; // compiles
A{UniversalType{}, UniversalType{}, UniversalType{}}; // doesn't compile
这告诉我们
A
有两个成员变量。
这样做有一个问题:聚合初始化会递归地初始化子对象。例如
struct B { int a[2], b; };
B{UniversalType{}}; // compiles
B{UniversalType{}, UniversalType{}}; // compiles
B{UniversalType{}, UniversalType{}, UniversalType{}}; // compiles?!
为了解决这个问题,引入了额外的嵌套
{}
。每个{UniversalType{}}
或{A0{}}
即使是聚合也只能初始化单个子对象,解决了问题。