指针可互换类型和联合数组

Pointer-Interconvertible Types and Arrays of Unions

如果我有 union.

struct None {};

template<typename T>
union Storage {
    /* Ctors and Methods */

    T obj;
    None none;
};

pointer-interconvertible 类型表示执行以下转换是合法的:

Storage<T> value(/* ctor args */);
T* obj = static_cast<T*>(static_cast<void*>(&value));

Storage<T>的数组当成T的数组是合法的吗?

Storage<T> values[20] = { /* initialisation */ };
T* objs = static_cast<T*>(static_cast<void*>(values));

for(auto i = 0; i < 20; ++i) {
    objs[i].method(); // Is this pointer access legal?
}

不,这是不合法的。关于 pointer-arithmetic 唯一可以被视为数组的是数组(以及由不是数组元素的对象形成的假想 single-element 数组)。所以这里与objs[i]中的指针运算相关的“数组”是由第一个数组元素的obj形成的假设的single-element数组,因为它本身不是数组的元素。对于 i >= 1objs[i] 不会指向一个对象,因此可能不会在其上调用 method

实际上,如果 T 的大小和联合的大小不一致,就会出现问题,因为在这种情况下,地址的算术运算也会失效。不能保证这两个大小一致(即使 Nonesizeofalignof 等于 1)。

除了那个问题,我怀疑编译器是否真的利用这种未定义的行为来进行优化。不过我不能保证。

另请注意,如果obj是联合体的活跃成员,则只能通过强制转换获得的指针访问obj,这意味着obj是成员在示例中已初始化。

你表示你打算在常量表达式中使用它,在这种情况下,编译器需要诊断未定义的行为,并且可能会拒绝这样的程序,而不管优化器的实际考虑如何。

此外,在常量表达式中,不允许从 void* 强制转换为不同的对象类型(或 reinterpret_cast)。所以 static_cast<T*>(static_cast<void*>(values)); 无论如何都会导致它失败。尽管这可以通过直接将指针指向联合成员(例如 &values[0].obj)来简单地补救。没有理由在这里使用强制转换。