该列表的目的不是为您提供访问对象的替代方法,而是如列表脚注所示,列出对象可能被别名的所有方式。考虑以下示例:
struct foo
{
char x;
float y;
int z;
bool w;
};
void func( foo &F, int &I, double &D )
{
//...
}
该列表的意思是访问F 也可能访问与访问I 相同的底层对象。如果您为I 传递了对F.z 的引用,则可能会发生这种情况,如下所示:
func(F, F.z, D);
另一方面,您可以放心地假设对F 的访问不会访问与D 相同的底层对象,因为struct foo 不包含double 类型的任何成员。
即使某些小丑这样做也是如此:
union onion
{
struct foo F;
double D;
};
onion o;
int i;
func( o.F, i, o.D ); // [class.union] (9.5) wants a word with you. UB.
我不确定union 是否是您问题的核心。但是union 示例之前的部分强调了聚合规则存在的原因。
现在让我们考虑您的示例:reinterpret_cast<Foo&>(a).y = 0; [expr.reinterpret.cast] (5.2.10),第 11 段有这样的说法:
T1 类型的左值表达式可以转换为“reference to
T2”如果类型为“指向T1”的表达式可以显式
使用reinterpret_cast 转换为“指向T2 的指针”类型。那
也就是说,引用转换 reinterpret_cast<T&>(x) 具有相同的效果
转换*reinterpret_cast<T*>(&x) 与内置& 和*
运算符(reinterpret_cast<T&&>(x) 也是如此)。结果
引用与源左值相同的对象,但具有不同的
类型。结果是左值引用类型的左值或
对函数类型的右值引用和右值的 xvalue
引用对象类型。没有暂时的
创建,不复制,构造函数
(12.1) 或转换函数 (12.3) 未被调用。71
71 这有时被称为双关语。
在您的示例的上下文中,如果将指向int 的指针转换为指向Foo 的指针是合法的,那么您的reinterpret_cast<Foo&)(a) 是合法的并产生一个左值。 (第 1 段告诉我们它将是一个左值。)而且,当我读到它时,根据第 7 段,指针转换本身是可以的:
指向对象的指针可以显式转换为指向对象的指针
不同的对象类型。当类型为“指向T1”的prvalue v 是
转换为“指向 cv T2 的指针”类型,如果 T1 和 T2 都是标准布局类型 (3.9) 和对齐方式,则结果为 static_cast<cv
T2*>(static_cast<cv void*>(v))
T2 的要求并不比T1 的要求严格。将类型为“pointer to T1”的纯右值转换为
键入“指向T2 的指针”(其中T1 和T2 是对象类型,T2 的对齐要求不是
比T1) 更严格,并返回其原始类型会产生原始指针值。任何结果
其他此类指针转换未指定。
您拥有具有兼容对齐约束的标准布局类型。因此,您所拥有的是一种产生左值的双关语类型。您列出的规则本身并没有使其未定义。
那么什么可能使它未定义?好吧,首先,[class.mem] (9.2) 第 21 段提醒我们,指向标准布局结构对象的指针指向其初始成员,反之亦然。因此,在输入双关语之后,您将得到对Foo 的引用,这样Foo 的x 与a 位于同一位置。
而且...这就是我的语言律师逐渐消失的地方。我凭直觉知道,通过该 franken-reference 访问 Foo 是在 best 实现定义或未指定。我找不到它被明确地放逐到 undefined 行为领域的地方。
但是,我想我回答了您最初的问题:为什么存在聚合规则?它为您提供了一种非常基本的方法来判断潜在的别名,而无需进一步的指针分析。