Don't use a `union` for type punning
Reason
It is undefined behavior to read a union member with a different type from the one with which it was written. Such punning is invisible, or at least harder to spot than using a named cast. Type punning using a union is a source of errors.
Example, bad
union Pun {
int x;
unsigned char c[sizeof(int)];
};
The idea of Pun is to be able to look at the character representation of an int.
void bad(Pun& u)
{
u.x = 'x';
cout << u.c[0] << '\n'; // undefined behavior
}
If you wanted to see the bytes of an int, use a (named) cast:
void if_you_must_pun(int& x)
{
auto p = reinterpret_cast<std::byte*>(&x);
cout << to_integer<unsigned>(p[0]) << '\n'; // OK; better
// ...
}
Accessing the result of a reinterpret_cast from the object's declared type to char*, unsigned char*, or std::byte* is defined behavior. (Using reinterpret_cast is discouraged, but at least we can see that something tricky is going on.)
Note
Unfortunately, unions are commonly used for type punning. We don't consider "sometimes, it works as expected" a conclusive argument.
Modern C++ introduced std::byte (C++17) and std::bit_cast (C++20) to facilitate operations on raw object representations. Use reinterpret_cast along with std::byte instead of unsigned char or char for these operations.
Enforcement
???
Enum: Enumerations
Enumerations are used to define sets of integer values and for defining types for such sets of values. There are two kinds of enumerations, "plain" enums and class enums.
Enumeration rule summary:
- Enum.1: Prefer enumerations over macros
- Enum.2: Use enumerations to represent sets of related named constants
- Enum.3: Prefer
enum classes over "plain"enums - Enum.4: Define operations on enumerations for safe and simple use
- Enum.5: Don't use
ALL_CAPSfor enumerators - Enum.6: Avoid unnamed enumerations
- Enum.7: Specify the underlying type of an enumeration only when necessary
- Enum.8: Specify enumerator values only when necessary