0

I've been trying to figure this one out, and thought it would be a fun one to take a look at :)

Ok, so I'm creating a type as constexpr using bitfields. Since bitfields can change from one implementation or platform to another, I want to static_assert that the bits are in the right place. With me so far? Here's the code for the type I'm creating:

// Currently hard-coded for 64bit, will change later when I get past this part...
constexpr int getPointerBitCount()          { return 64; }
constexpr int getPointerByteWidth()         { return getPointerBitCount() / 8; }
constexpr int getPointerDataWidth()         { return 3; }
constexpr int getPointerPointerWidth()      { return getPointerBitCount() - getPointerDataWidth(); }
struct PointerData
{
    bool _invalid       :1;
    bool _delete        :1;
    bool _unused        :1;
    uint64_t _pointer   :getPointerPointerWidth();

    constexpr PointerData(bool invalid, bool isDelete, bool unused)
        : _invalid{invalid}
        , _delete{isDelete}
        , _unused{unused}
        , _pointer{0}
    {
    }
};

So now what I would like to do is this:

static_assert(reinterpret_cast<uint64_t>(PointerData(true, false, false)) == 1, "PointerData not supported on this platform");
static_assert(reinterpret_cast<uint64_t>(PointerData(false, true, false)) == 2, "PointerData not supported on this platform");
static_assert(reinterpret_cast<uint64_t>(PointerData(false, false, true)) == 4, "PointerData not supported on this platform");

Basically, I need to create the PointerData type as constexpr, and then run tests on it as if the entire thing was a uint64_t (or uint32_t on 32 bit). But, my compiler says I can't use reinterpret_cast here. Thoughts on how I can do this?

Running bitwise operators to compute the value will not achieve what I'm looking for. I need to access the variable "as is". The test here is to see if the first bit in a bitfield is equal to 1. (and the second = 2, and the 3rd = 4).

I looked into creating a union out of this, but I'm not sure if that's appropriate given the bitfields. Unsure.

I'm new to the constexpr stuff, so help would be appreciated!

Michael Gazonda
  • 2,720
  • 1
  • 17
  • 33

1 Answers1

0

reinterpret_cast is not compatible with static_assert, even when its behavior is well-defined, but your usage has implementation-specific behavior. The compiler would be allowed to reject such code, or the machine would be allowed to crash and burn if you execute such a cast.

To get it to work, add a constexpr operator uint64_t () const to PointerData which returns the value you expect the reinterpret_cast to yield. Then change the reinterpret_cast to static_cast.

EDIT: No, C++ does not support what you are attempting. Constant expression evaluation (as in constexpr) asks the compiler to emulate the target platform to a certain degree. This emulator is required to flag undefined behavior as an error, as well as many implementation-specific cases including all reinterpret_cast expressions.

The architecture of your reinterpret_cast doesn't work because it is performing its introspection inside an emulator. You need to move it to a build system "canary" or program startup instead.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Can you write the `reinterpret_cast` inside the new operator function? If not, what's a reasonable alternative? – Lightness Races in Orbit Jul 12 '14 at 01:24
  • @LightnessRacesinOrbit No, it would just cause the same issue. (You could write it, but the call would not be a constexpr invocation.) A reasonable alternative is bitwise arithmetic, I don't see the problem with that. – Potatoswatter Jul 12 '14 at 01:26
  • 1
    If I use bitwise arithmetic, I defeat the purpose of checking this, as my static_assert will always pass. It should fail if the first bit in a bitfield is not equal to 1. – Michael Gazonda Jul 12 '14 at 01:30
  • @MGaz I misunderstood the purpose of your program. No, C++ simply doesn't support that. The behavior of `reinterpret_cast` on non-reference, non-pointer types is implementation-defined, and you'd find more portability by using `uint64_t &` instead, although that would violate the aliasing rules and cause UB. – Potatoswatter Jul 12 '14 at 01:35
  • I hope that you're wrong, and that there's a way. I'm looking into using a union. – Michael Gazonda Jul 12 '14 at 01:47
  • 1
    @MGaz No, this is a very fundamental issue. I don't know how to emphasize that more. Reading from a union member that wasn't the last one written is also disallowed in constant expressions. Even if you found a loophole, it would be a language defect. – Potatoswatter Jul 12 '14 at 02:01
  • Well, I found one. You can tell me why it's wrong in a sec. – Michael Gazonda Jul 12 '14 at 02:02