Home > OS >  Type Punning via constexpr union
Type Punning via constexpr union

Time:04-07

I am maintaining an old code base, that is using a union of an integer type with a bit-field struct for type-punning. My compiler is VS2017. As an example, the code is similar to the following:

struct FlagsType
{
    unsigned flag0 : 1;
    unsigned flag1 : 1;
    unsigned flag2 : 1;
};

union FlagsTypeUnion
{
    unsigned  flagsAsInt;
    FlagsType flags;
};

bool isBitSet(unsigned flagNum, FlagsTypeUnion flags)
{
    return ((1u << flagNum) & flags.flagsAsInt);
}

This code has a number of undefined behavior issues. Namely, it is hotly debated whether type punning is defined behavior or not, but on top of that, the implementation of packing bit-fields is implementation-defined. To address these issues, I would like to add static-assert statements to validate that the VS implementation enables using this type of approach. However, when I tried to add the following code, I get error C2131: expression did not evaluate to a constant.

union FlagsTypeUnion
{
    unsigned  flagsAsInt;
    FlagsType flags;
    constexpr FlagsTypeUnion(unsigned const f = 0) : flagsAsInt{ f } {}
};

static_assert(FlagsTypeUnion{ 1 }.flags.flag0,
    "The code currently assumes bit-fields are packed from LSB to MSB");

Is there any way to add compile-time checks to verify the type-punning and bit-packing code works as the runtime code is assuming? Unfortunately, this code is spread throughout the code base, so changing the structures isn't really feasible.

CodePudding user response:

You might use std::bit_cast (C 20):

struct FlagsType
{
    unsigned flag0 : 1;
    unsigned flag1 : 1;
    unsigned flag2 : 1;
    unsigned padding : 32 - 3; // Needed for gcc
};

static_assert(std::is_trivially_constructible_v<FlagsType>);

constexpr FlagsType makeFlagsType(bool flag0, bool flag1, bool flag2)
{
    FlagsType res{};

    res.flag0 = flag0;
    res.flag1 = flag1;
    res.flag2 = flag2;
    return res;
}

static_assert(std::bit_cast<unsigned>(makeFlagsType(true, false, false)) == 1);

Demo

  • clang doesn't support it (yet) though.
  • gcc requires to add explicitly the padding bits for the constexpr check.
  • Related