I'm trying to get a bitmask from a bitfield struct, at compile time. One of the tricks that I tried, which looks promising to me, is using std::bit_cast
, because it is supposed to be constexpr.
My test code can be found here: https://godbolt.org/z/er48M63sh
This is the source:
#include <bit>
struct Bitfield {
int :3;
int x:3;
};
constexpr int mask() noexcept {
Bitfield bf{};
bf.x -= 1;
return std::bit_cast<int>(bf);
}
int test() {
int mask1 = mask();
// constinit static int mask2 = mask(); // Why doesn't this compile?
return 0;
}
As you can see, it doesn't actually calculate the bitmask at compile time, but at runtime, so for some reason the constexpr trick isn't working. I fail to see why, however, since cppreference doesn't seem to list my case as one that defeats constexpr in std::bit_cast
.
Does anybody see what's wrong? Does this thing have a chance of working?
The problem is that using std::bit_cast
with padding bits is undefined behavior in most cases:
[bit.cast] p2
A bit in the value representation of the result is indeterminate if it does not correspond to a bit in the value representation of
from
or corresponds to a bit of an object that is not within its lifetime or has an indeterminate value.
Most of the upper (or lower) bits in the int
that results from std::bit_cast<int>(bf)
are indeterminate, because it they don't correspond to bits in the value representation of the bit-field. The Bitfield
class has plenty of padding bits, i.e. which are only in the object representation, not the value representation of Bitfield
.
For each bit in the value representation of the result that is indeterminate, the smallest object containing that bit has an indeterminate value; the behavior is undefined unless that object is of unsigned ordinary character type or
std::byte
type. The result does not otherwise contain any indeterminate values.
int
is not a std::byte
, so it isn't exempt, and producing an int
through std::bit_cast
which has some indeterminate bits is undefined behavior. You only notice that it's UB in a constant expression because compilers aren't required to diagnose UB outside constant expressions.
std::bit_cast
, and manually serialize instead with bf.first | bf.second << 3
, assuming you've given all the bit-field members a namestruct
static_assert(std::has_unique_object_representations_v<Bitfield>)