这篇文章讨论了一种计算班级成员数量的方法:
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
,它可以伪装成任何类型。例如它告诉我们
X
有两个成员变量。这里有一个问题:聚合初始化会递归地初始化子对象。例如
为了解决这个问题,
{}
引入了一个额外的嵌套。每个{UniversalType{}}
或{A0{}}
只能初始化一个子对象,即使它是一个聚合,从而解决了这个问题。虽然看起来
{UniversalType{}}
可以初始化任何类型,但有一个例外:空聚合。所以我们需要测试{UniversalType{}}
和UniversalType{}
。话虽如此,但这种实现方式存在几个错误的边缘情况。
对于
A
,A{UniversalType{}}
由于第二个成员b
无法默认初始化而失败,因此计数器返回0
。可以通过向下计数而不是向上计数来解决这个问题。对于
C
,问题更加根本。实现无法记住空聚合的位置。由于{A0{}}
先于c0
,因此没有聚合可以跟在空聚合之后。