通过 constexpr union 输入双关语

Type Punning via constexpr union

我正在维护一个旧的代码库,它使用一个整数类型的联合和一个用于类型双关的位域结构。我的编译器是VS2017。例如,代码类似于以下内容:

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);
}

这段代码有一些未定义的行为问题。也就是说,类型双关是否是定义行为的争论很激烈,但最重要的是,打包位域的实现是实现定义的。为了解决这些问题,我想添加静态断言语句来验证 VS 实现是否支持使用这种类型的方法。但是,当我尝试添加以下代码时,出现错误 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");

有什么方法可以添加编译时检查来验证类型双关和位打包代码是否像运行时代码所假设的那样工作?不幸的是,此代码遍布整个代码库,因此更改结构并不可行。

您可以使用 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 还不支持它。
  • gcc 需要为 constexpr 检查显式添加填充位。