If I have a union
.
struct None {};
template<typename T>
union Storage {
/* Ctors and Methods */
T obj;
None none;
};
pointer-interconvertible
types means it is legal to perform the following conversion:
Storage<T> value(/* ctor args */);
T* obj = static_cast<T*>(static_cast<void*>(&value));
It is legal to treat an array of Storage<T>
as an array of 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?
}
CodePudding user response:
No, it is not legal. The only thing that may be treated as an array with regards to pointer-arithmetic is an array (and the hypothetical single-element array formed by an object that is not element of an array). So the "array" relevant to the pointer arithmetic in objs[i]
here is the hypothetical single-element array formed by obj
of the first array element, since it is not itself element of an array. For i >= 1
, objs[i]
will not point to an object and so method
may not be called on it.
Practically, there will be an issue in particular if T
's size and the size of the union don't coincide, since even the arithmetic on the addresses will be off in this case. There is no guarantee that the two sizes coincide (even if None
has sizeof
and alignof
equal to 1
).
Aside from that issue, I doubt that compilers actually make use of this undefined behavior for optimization purposes. I can't guarantee it though.
Also note that you are only allowed to access obj
through the pointer obtained by the cast if obj
is the active member of the union, meaning that obj
is the member which was initialized in the example.
You indicate that you intend to use this in a constant expression, in which case the compiler is required to diagnose the undefined behavior and is likely to reject such a program, regardless of the practical considerations about the optimizer.
Also, in a constant expression a cast from void*
to a different object type (or a reinterpret_cast
) is not allowed. So static_cast<T*>(static_cast<void*>(values));
will cause that to fail anyway. Although that is simply remedied by just taking a pointer to the union member directly (e.g. &values[0].obj
). There is no reason to use the casts here.