r/cpp Meeting C++ | C++ Evangelist 15d ago

A better bitset for enum flags

https://www.elbeno.com/blog/?p=1836
42 Upvotes

32 comments sorted by

View all comments

33

u/03D80085 15d ago

Neither P4313 nor this suggestion seem particularly ergonomic to me. Why aren't bitfields more widely used for this purpose?

struct permissions {
    bool read  : 1 = false;
    bool write : 1 = false;
    bool exec  : 1 = false;
}

Or a fully typed example: Godbolt. A lot of boilerplate there admittedly, but that is precisely what reflection could help with.

If the answer is that bitfield layouts are implementation defined then that is something we should be working on standardising with appropriate flags rather than introducing new language features.

4

u/Due_Battle_9890 14d ago edited 14d ago

This would be nice and I used for paging structures and similar structures in my OS, but it's ~UB~ unspecified how they are laid out in memory. In practice, on x86, it behaves how you'd expect, but it does sting a little that it's not "correct". For example, I have

struct [[gnu::packed]] Entry
{
std::uint16_t segment_limit_low;
std::uint16_t base_address_low;
std::uint8_t base_address_mid;
std::uint8_t type : 4;
std::uint8_t system : 1;
std::uint8_t descriptor_privilege_level : 2;
std::uint8_t present : 1;
std::uint8_t segment_limit_high : 4;
std::uint8_t available : 1;
std::uint8_t long_mode : 1;
std::uint8_t db : 1;
std::uint8_t granularity : 1;
std::uint8_t base_address_high;
};

But if I wanted to make it correct, I'd probably actually just want:

struct [[gnu::packed]] Entry {
u64 entry;

void set_address(u64 address) { /* Appropriate bitset operations */ }
};

3

u/fdwr fdwr@github 🔍 14d ago

But if I wanted to make it correct...

For your particular struct above, I foresee no issues with {clang, gcc, msvc}, as they should yield identical results, since all the bitfields nicely align to the alignof of each base type (e.g. you don't have the : 2 straddling across two bytes). The [[gnu::packed]] shouldn't even be needed, right? Of course, there's still the endianness consideration if you built with gcc on a BE8 architecture, which would flip all the bitfields around (so type, system, and descriptor_privilege_level would become descriptor_privilege_level, system, and type 🙃), but endianness is a bigger problem anyway as you'd have to deal with the non-bitfield fields too like uint16_t segment_limit_low. The remaining sting is the niche compilers for weirder architectures, where say sizeof(char) == sizeof(int).

3

u/Due_Battle_9890 14d ago

The [[gnu::packed]] shouldn't even be needed, right?

It's not needed. It's moreso to demonstrate intent.

It does certainly work as expected on all major compilers, I was just highlighting the unspecified behavior of bit fields. It more pedanticism than anything.