There is no such guarantee.
Be careful about strict aliasing, std::uint8_t[4]
is not one of the allowed aliasing types for reinterpret_cast
.
Also, there is no reason for a workaround at all since if you leave out the name of the union, its members are moved to the outer namespace, i.e the following works:
#include <cstdint>
struct using_union
{
union
{
std::uint32_t i;
std::uint8_t by[ 4 ];
};
};
int main(){
using_union a;
a.i=10;
}
But this still does not allow you to access any inactive member of an union in standard C++.
Strict aliasing
reinterpret_cast
( cppreference.com )
has many rules. Feel free to look at cppref as it is more friendly than the standard.
Relevant parts
A glvalue of type T1, designating an object x, can be cast to the type
“reference to T2” if an expression of type “pointer to T1” can be
explicitly converted to the type “pointer to T2” using a
reinterpret_cast. The result is that of *reinterpret_cast<T2 *>(p)
where p is a pointer to x of type “pointer to T1”. No temporary is
created, no copy is made, and no constructors ([class.ctor]) or
conversion functions ([class.conv]) are called. [expr.reinterpret.cast][7.6.1.9.11]
An object pointer can be explicitly converted to an object pointer of
a different type.64 When a prvalue v of object pointer type is
converted to the object pointer type “pointer to cv T”, the result is
static_cast<cv T*>(static_cast<cv void*>(v)). [ Note: Converting a
prvalue of type “pointer to T1” to the type “pointer to T2” (where T1
and T2 are object types and where the alignment requirements of T2 are
no stricter than those of T1) and back to its original type yields the
original pointer value. — end note ] [expr.reinterpret.cast][7.6.1.9.7]
If a program attempts to access ([defns.access]) the stored value of
an object through a glvalue whose type is not similar ([conv.qual]) to
one of the following types the behavior is undefined
- (11.1) the dynamic type of the object,
- (11.2) a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
- (11.3) a char, unsigned char, or std::byte type.
[basic.lval][7.2.1]
The types are not similar, and std::uint8_t[4]
is not guaranteed to be any of the types below -> UB. I do not think that std::uint8_t(*)[4]
decays into std::uint8_t*
, but I am not 100% sure. Either way it is definitely UB because even if it did decay, std::uint8_t
is not the same as unsigned char
(but in practice it almost always will be).
The proper way how to deal with this in C++ is memcpy
Union type punning
See these very great answers.
TLDR: It is UB to access inactive members in C++ and it is explicitly allowed in C.
If you want to inspect values reinterpret_cast<std::byte*>(&value)
is safe. Use memcpy
to copy to/from std::byte buffer[]
for translating between two incompatible types. Do not worry about the performance, compilers can spot these patterns.