{"id":1836,"date":"2026-07-18T09:34:42","date_gmt":"2026-07-18T15:34:42","guid":{"rendered":"https:\/\/www.elbeno.com\/blog\/?p=1836"},"modified":"2026-07-18T19:41:13","modified_gmt":"2026-07-19T01:41:13","slug":"a-better-bitset-for-enum-flags","status":"publish","type":"post","link":"https:\/\/www.elbeno.com\/blog\/?p=1836","title":{"rendered":"A better bitset for enum flags"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">TL;DR: using <code>enum<\/code> for bitflags is poor, but it&#8217;s what we&#8217;re likely to end up with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Many electrons have been spent expounding enumeration bitflag types (and not just in C++). As a tech community across multiple languages, we seem to be all in on the idea that the right way for this to look is something like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ I somehow opt in for bitwise operations on this type\nenum struct permissions {\n  NONE  = 0,\n  READ  = 1 &lt;&lt; 0,\n  WRITE = 1 &lt;&lt; 1,\n  EXEC  = 1 &lt;&lt; 2\n};\n\n\/\/ then, I can use \"regular bitwise operators\"\nauto p = permissions::READ | permissions::WRITE;\nif ((p &amp; permissions::READ) == permissions::READ) {\n  \/\/ ...\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Where in C++, opting in variously looks like adding a function declaration found by ADL, or adding a reflection annotation, or using a macro to provide operators, or\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We might well end up here in C++. With successive standards, we get new techniques which enable us to get there a little more easily, and C++26 reflection is the latest example of that. <a href=\"https:\/\/wg21.link\/P4313\">P4313<\/a> is a proposal for <code>enum<\/code> flags in this style.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I think it&#8217;s a workable approach (i.e. we will <em>make<\/em> it work), but ultimately it seems to me that it&#8217;s the wrong direction. I think using a bitset is a much better solution. Unfortunately, <code>std::bitset<\/code> can&#8217;t get us there, which is why we may end up with the <code>enum<\/code> bitflags.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The pros of <code>enum<\/code> bitflags are usually given as:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Type safety (at least when using scoped enumerations). There is no implicit conversion between values of different types. We can&#8217;t accidentally confuse permissions with some other value.<\/li>\n\n\n\n<li>Ergonomics of use. Using these things looks like using plain old integers. (I note that <a href=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2026\/p4313r0.html#design-consideration\" data-type=\"link\" data-id=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2026\/p4313r0.html#design-consideration\">P4313 says<\/a> &#8220;plain C-style <code>int<\/code>&#8220;, notwithstanding clang-tidy&#8217;s <a href=\"https:\/\/clang.llvm.org\/extra\/clang-tidy\/checks\/bugprone\/signed-bitwise.html\">bugprone-signed-bitwise<\/a> check.)<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">I think the downsides are:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Ergonomics of implementation. As I said, problems are lessened with each C++ standard. And this is something the culture of C++ library authorship discounts: who cares how difficult it is to implement under the hood? But it&#8217;s still going to be kind of annoying to apply bit operations to enumeration types you don&#8217;t own.<\/li>\n\n\n\n<li>Aesthetics. I don&#8217;t <em>want<\/em> using these things to look like using plain old integers, because using plain old integers is fraught with peril. Which leads to\u2026<\/li>\n\n\n\n<li>Ergonomics of use. Bitwise operators famously have the &#8220;wrong&#8221; precedence in C and C++, for historical reasons. Yes, please use your compiler warnings. Ironically the <a href=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2026\/p4313r0.html#proposal\">expositional code used in P4313<\/a> exhibits this well-known operator precedence error (which I corrected above)!<\/li>\n\n\n\n<li>(Perhaps most significantly&#8230;) Conflation of ideas. <code>enum<\/code> bitflags are <a href=\"https:\/\/www.youtube.com\/watch?v=SxdOUGdseq4\" data-type=\"link\" data-id=\"https:\/\/www.youtube.com\/watch?v=SxdOUGdseq4\">easy, but not simple<\/a>. They complect <em>naming<\/em> bits (what we actually want) with <em>representing<\/em> them. True, it&#8217;s not our fault; this is the legacy of C that multiple languages double down on at this point. But it&#8217;s also not right. We say &#8220;the type has members <code>A<\/code> and <code>B<\/code>&#8221; then we pretend that <code>A|B<\/code> is also a member of the type. But that&#8217;s immediately subverting type safety: we have confused representation with naming.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">So what&#8217;s the alternative? Well unfortunately, <code>std::bitset<\/code> <em>doesn&#8217;t cut it<\/em>, and in all probability <em>can&#8217;t be made to cut it<\/em>. But a better bitset <em>could<\/em>, and is totally implementable. Here&#8217;s what I&#8217;d like it to look like.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>enum struct permissions {\n  READ, WRITE, EXEC,\n  MAX \/\/ here's the conventional opt-in\n};\n\n\/\/ bitset can take an enum, and preserve it (type safety)\nusing perms_t = bitset&lt;permissions::MAX&gt;;\n\n\/\/ how about a tag constructor?\nauto p = perms_t{place_bits, permissions::READ, permissions::WRITE};\nif (p&#91;permissions::READ]) {\n  \/\/ ...\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As far as I can see, this solves almost (I&#8217;m being conservative in saying almost) all the problems with the enumeration approach.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Type safety. I can&#8217;t assign a <code>perms_t<\/code> here from some other <code>bitset<\/code> or from an integral type. And this is true even for unscoped <code>enum<\/code>s I find in 3rd party libraries.<\/li>\n\n\n\n<li>Ergonomics of use. <code>operator[]<\/code> isn&#8217;t going to have the precedence problems of the bitwise operators. Enumeration values don&#8217;t have to be pre-shifted, avoiding a source of potential error.<\/li>\n\n\n\n<li>Correct separation of concerns. The enumeration type <em>names<\/em> the bits. The <code>bitset<\/code> <em>represents<\/em> them. We aren&#8217;t pretending that <code>READ|WRITE<\/code> is a member of <code>permissions<\/code>.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">It still ticks the other boxes too. Implementation is at least as easy as, if not easier than, the <code>enum<\/code> approach.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I think the one remaining problem is around the ergonomics of taking a 3rd party codebase whose <code>enum<\/code>(s) I don&#8217;t own, and treating the <code>enum<\/code>s as bits. Fair criticism. I think we are always going to find that there are ways this can be annoying. With the <code>enum<\/code> bitflags approach I would use an intrusive opt-in mechanism something like injecting ADL-found functions into a 3rd party namespace. With a bitset approach, it&#8217;s not intrusive, but I still need to provide a &#8220;max&#8221; value (or otherwise specify the number of bits in some other API shape variation not shown here).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Both of these techniques mean that I might have to change code when upstream code changes. I think on balance the bitset approach is a bit easier, especially considering that we can probably use reflection to discover a suitable &#8220;max&#8221; value. (Unless a 3rd party library deliberately hides the <code>enum<\/code> values away, and in that case, what would we use to name the bits anyway?)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But you&#8217;re still thinking this isn&#8217;t as nice as it could be, and you&#8217;re right, because so far you&#8217;re imagining <code>std::bitset<\/code> with just these extras. For nice usage, this <code>bitset<\/code> needs a few more things.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If we&#8217;re using a bitset for representation, <em>well actually<\/em> we want an easy way to specify and get access to the underlying thing (like <code>enum<\/code> can deal with its underlying type). Because ultimately types are all just compile-time fluff that the runtime doesn&#8217;t care about: these things <em>do<\/em> need to be plain integral stuff under the hood.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using B1 = bitset&lt;8&gt;;                \/\/ 8 bits, a std::uint8_t underneath\nusing B2 = bitset&lt;8, std::uint32_t&gt;; \/\/ 8 bits, a std::uint32_t\nusing B3 = bitset&lt;64&gt;;               \/\/ 64 bits, a std::uint64_t\nusing B4 = bitset&lt;64, std::uint8_t&gt;; \/\/ 64 bits, a std::array&lt;std::uint8_t, 8&gt;\n\nenum struct permissions { READ, WRITE, EXEC, MAX };\nusing B = bitset&lt;permissions::MAX, std::uint32_t&gt;;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because we want to be able to make these size\/speed tradeoffs appropriately across platforms from tiny microcontrollers to massive servers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And because this thing <em>is<\/em> just integral stuff that we want to write to some serialization format, we want an easy way to do that.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>auto perms = get_permissions();                \/\/ a bitset over permissions\nauto serial_perms = perms.to&lt;std::uint16_t&gt;(); \/\/ a std::uint16_t for serialization<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">We can of course add several other nice things to a bitset type, for other uses cases. (e.g. Why is it so hard, in a generic way, to construct a <code>std::bitset<\/code> with all the bits set? How do I iterate over set bits? How do I find the lowest unset bit?) But all that&#8217;s a different story.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Well, that&#8217;s about it. I think this bitset approach is much better than the <code>enum<\/code> flags approach. It&#8217;s better even after successive C++ versions iron out more and more of the annoying niceties of <code>enum<\/code> flags implementation. But we&#8217;re going to end up with <code>enum<\/code> flags, aren&#8217;t we? And I guess we&#8217;ll just make it work.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>TL;DR: using enum for bitflags is poor, but it&#8217;s what we&#8217;re likely to end up with. Many electrons have been spent expounding enumeration bitflag types (and not just in C++). As a tech community across multiple languages, we seem to be all in on the idea that the right way&#8230;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22],"tags":[],"class_list":["post-1836","post","type-post","status-publish","format-standard","hentry","category-cpp"],"_links":{"self":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1836","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1836"}],"version-history":[{"count":10,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1836\/revisions"}],"predecessor-version":[{"id":1856,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1836\/revisions\/1856"}],"wp:attachment":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1836"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1836"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1836"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}