Bitmask operations for enums

Document #: P4313R0 [Latest] [Status]
Date: 2026-07-14
Project: Programming Language C++
Audience: Library Evolution
Reply-to: Iliya Guterman
<>
Anthony Williams
<>

1 Proposal

We propose enabling bitmask operators for enum classes that have a std::bitmask_type annotation, to provide similar behavior to 16.3.3.3.3 [bitmask.types].

enum class [[=std::bitmask_type]] Permission {
  None    = 0,
  Read    = 1 << 0,
  Write   = 1 << 1,
  Execute = 1 << 2,
};

int main () {
    // Combine permissions
    Permission user_perms = Permission::Read | Permission::Write;

    if (user_perms & Permission::Read == Permission::Read)
        std::cout << "Can read";
}

2 Motivations

2.1 Motivation 1: Drawing inspiration from other programming languages.

As a developer coming from languages other than C++, I grew accustomed to using enums to represent flags. However, such behavior is noticeably absent from the C++ language.

2.1.1 Bitmask in: Python

from enum import IntFlag

class Permission(IntFlag):
    READ    = 1 << 0  # 0b001
    WRITE   = 1 << 1  # 0b010
    EXECUTE = 1 << 2  # 0b100

# Combine permissions
user_perms = Permission.READ | Permission.WRITE

# Check permissions
if user_perms & Permission.READ:
    print("Can read")

2.1.2 Bitmask in: C#

using System;

[Flags]
public enum Permission
{
    None    = 0,
    Read    = 1 << 0, // 0b001
    Write   = 1 << 1, // 0b010
    Execute = 1 << 2, // 0b100
}

class Program
{
    static void Main()
    {
        // Combine permissions
        Permission userPerms = Permission.Read | Permission.Write;

        Console.WriteLine(userPerms);
        // Read, Write

        // Check permissions
        if ((userPerms & Permission.Read) != 0) {
            Console.WriteLine("Can read");
        }
    }
}

2.1.3 Bitmask in: Rust

// Cargo.toml: bitflags = "2.4" (or latest)
use bitflags::bitflags;

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct Permissions: u32 {
        const READ    = 0b00000001;
        const WRITE   = 0b00000010;
        const EXECUTE = 0b00000100;
    }
}

fn main() {
    // Combine flags using bitwise OR
    let user_perms = Permissions::READ | Permissions::WRITE;

    // Check permissions
    if user_perms.contains(Permissions::READ) {
        println!("Can read");
    }
}

2.1.4 Bitmask in: JavaScript

const Permission = {
    READ:    1 << 0, // 0b001
    WRITE:   1 << 1, // 0b010
    EXECUTE: 1 << 2, // 0b100
};

// Combine permissions
let userPerms = Permission.READ | Permission.WRITE;

// Check permissions
if (userPerms & Permission.READ) {
    console.log("Can read");
}

2.2 Motivation 2: Reduce boilerplate code

A repeated boilerplate code for bitmask behavior can be found with quite high frequency in the wild

For example, in LLVM project, the same boilerplate code is repeated multiple times. - perms.h - byte.h - perm_options.h

enum class perms : unsigned {
  none = 0,

  owner_read  = 0400,
  owner_write = 0200,
  owner_exec  = 0100,
  owner_all   = 0700,
  ...
}

[[nodiscard]] _LIBCPP_HIDE_FROM_ABI inline constexpr perms operator&(perms __lhs, perms __rhs) {
  return static_cast<perms>(static_cast<unsigned>(__lhs) & static_cast<unsigned>(__rhs));
}

[[nodiscard]] _LIBCPP_HIDE_FROM_ABI inline constexpr perms operator|(perms __lhs, perms __rhs) {
  return static_cast<perms>(static_cast<unsigned>(__lhs) | static_cast<unsigned>(__rhs));
}

[[nodiscard]] _LIBCPP_HIDE_FROM_ABI inline constexpr perms operator^(perms __lhs, perms __rhs) {
  return static_cast<perms>(static_cast<unsigned>(__lhs) ^ static_cast<unsigned>(__rhs));
}

[[nodiscard]] _LIBCPP_HIDE_FROM_ABI inline constexpr perms operator~(perms __lhs) {
  return static_cast<perms>(~static_cast<unsigned>(__lhs));
}

_LIBCPP_HIDE_FROM_ABI inline perms& operator&=(perms& __lhs, perms __rhs) { return __lhs = __lhs & __rhs; }

_LIBCPP_HIDE_FROM_ABI inline perms& operator|=(perms& __lhs, perms __rhs) { return __lhs = __lhs | __rhs; }

_LIBCPP_HIDE_FROM_ABI inline perms& operator^=(perms& __lhs, perms __rhs) { return __lhs = __lhs ^ __rhs; }

_LIBCPP_END_NAMESPACE_FILESYSTEM

The above can be replaced with the following:

enum class [[=std::bitmask_type]] perms : unsigned {
  none = 0,

  owner_read  = 0400,
  owner_write = 0200,
  owner_exec  = 0100,
  owner_all   = 0700,
  ...
};

2.3 Motivation 3: Standardize solution under C++ standard library

This type of use case is a sought-after feature and has led to the development of many standalone solutions.

  1. A bitmask solution was proposed by Anthony Williams in 2015: Using Enum Classes as Bitfields.
  2. Later on in 2024 Evolution to Anthony’s proposal with C++20 concepts: C++20 Concepts applied - Safe bitmasks using scoped enums
  3. Standalone Header Library for bitmask behavior: Bitmask operators and typesafe comparisons for enum class
  4. Bit Flag Values Enumerations in C++
  5. Stack-overflow questions from developers looking for bitmask behavior from enum class
    1. C++ enum flags vs bitset
    2. How to use bitmask_operators.hpp with namespace and classes

3 Considered alternatives solutions

3.1 Alternatives: Why not use C-style typedef enum instead?

Reverting to using C-style enums means losing the benefits of scoped constants and the type safety provided by C++ enum class.

typedef enum Permission {
    PERMISSION_NONE    = 0,
    PERMISSION_READ    = 1u << 0, // 0b001
    PERMISSION_WRITE   = 1u << 1, // 0b010
    PERMISSION_EXECUTE = 1u << 2, // 0b100
} Permission;

int main(void)
{
    Permission user_perms = PERMISSION_READ | PERMISSION_WRITE;

    // Check permissions
    if (user_perms & PERMISSION_READ) {
        printf("Can read\n");
    }
}

3.2 Alternatives: Can I use std::bitset instead?

While std::bitset supports bitwise operations, it is limited in other respects.

3.2.1 std::bitset does not offer same ergonomics as enum class

std::bitset requires the user to provide meaningful names, for example:

#include <bit>
#include <bitset>
#include <cassert>
#include <iostream>
#include <stdexcept>

class BitSetA : public std::bitset<8> {
public:
    static constexpr std::size_t write_flag = 2;

    // Users must create meaningful function names or additional abstractions to represent the individual bit names
    bool is_write_enabled() {
        return test(write_flag);
    }

    void toggle_write_enabled() {
        flip(write_flag);
    }
};

int main () {
    BitSetA a;
    std::cout << a.is_write_enabled() << std::endl;
    a.toggle_write_enabled();
    std::cout << a.is_write_enabled() << std::endl;
}

3.2.2 std::bitset does not provide the same type safety

std::bitset also does not provide the same type-safety guarantees as enum class. The resulting type remains the generic std::bitset type, so the original semantic type information is lost during operations.

See for example this StackOverflow question: C++ enum flags vs bitset

#include <bit>
#include <bitset>
#include <cassert>
#include <iostream>
#include <stdexcept>

class BitSetA : public std::bitset<8> { };
class BitSetB : public std::bitset<8> { }

int main () {
    BitSetA a;
    BitSetB b;
    
    // a and b decay into std::bitset and lose their type safety
    std::bitset<8> c = a & b;
}

4 Design

The design builds upon the solution proposed by Andreas Fertig C++20 Concepts applied - Safe bitmasks using scoped enums which in itself is an extension to Using Enum Classes as Bitfields initially devised by Anthony Williams

4.1 Design consideration

  1. No implicit conversion: It should not be possible to assign bitmasks of different types to each other or perform operations between them.
  2. User should be able to use bitwise operation normally as they would with plain C-Style int
  3. Enabling bitmask behavior(16.3.3.3.3 [bitmask.types]) on enum class is easy and simple for the user.

5 Reference Implementation

  1. On godbolt Clang reflection https://godbolt.org/z/zebW6hGYY
  2. On godbolt GCC 16.1 https://godbolt.org/z/x43jzhsEM
  3. Trying locally with GCC 16.1 here
#include <type_traits>
#include <utility>
#include <meta>

namespace std {
inline constexpr struct BitmaskType {} bitmask_type;

template <typename T>
concept BitmaskTypeLike =
    std::is_enum_v<T> and (!std::meta::annotations_of_with_type(^^T,std::meta::type_of(^^bitmask_type)).empty());

} // std

template <std::BitmaskTypeLike T>
constexpr T operator|(T lhs, T rhs) {
  return static_cast<T>(std::to_underlying(lhs) | std::to_underlying(rhs));
}

template <std::BitmaskTypeLike T>
constexpr T operator&(T lhs, T rhs) {
  return static_cast<T>(std::to_underlying(lhs) & std::to_underlying(rhs));
}

template <std::BitmaskTypeLike T>
constexpr T operator^(T lhs, T rhs) {
  return static_cast<T>(std::to_underlying(lhs) ^ std::to_underlying(rhs));
}

template <std::BitmaskTypeLike T> constexpr T operator~(T lhs) {
  return static_cast<T>(~std::to_underlying(lhs));
}

template <std::BitmaskTypeLike T>
constexpr T& operator|=(T &lhs, T rhs) {
  lhs = lhs | rhs;
  return lhs;
}

template <std::BitmaskTypeLike T>
constexpr T& operator&=(T &lhs, T rhs) {
  lhs = lhs & rhs;
  return lhs;
}

template <std::BitmaskTypeLike T> constexpr T& operator^=(T &lhs, T rhs) {
  lhs = lhs ^ rhs;
  return lhs;
}

6 Wording

6.1 Add <bitmask> header

Add new header <bitmask> to table Table 24: C++ library 16.4.2.3 [headers]

6.2 Add a new sections to chapter 22 [utilities] after 22.9 [bitset]

With the content:

22.x.1 Header <bitmask> synopsis

The header <bitmask> defines all function needed for 16.3.3.3.3 [bitmask.types]

namespace std {
// 22.x.2, annotation type
inline constexpr unspecified bitmask_type = unspecified;

// 22.x.3, concept
template <typename T>
  concept bitmask-like = see below;         // exposition only
}

// 22.x.4, goes into global namespace 
template <bitmask-like T> constexpr T operator|(T lhs, T rhs);
template <bitmask-like T> constexpr T operator&(T lhs, T rhs);
template <bitmask-like T> constexpr T operator^(T lhs, T rhs);
template <bitmask-like T> constexpr T operator~(T lhs);
template <bitmask-like T> constexpr T& operator|=(T &lhs, T rhs);
template <bitmask-like T> constexpr T& operator&=(T &lhs, T rhs);
template <bitmask-like T> constexpr T& operator^=(T &lhs, T rhs);

22.x.2 Annotation bitmask_type

namespace std {
  inline constexpr unspecified bitmask_type;
}

1 bitmask_type is an instance of a unique unspecified type that is suitable for use as an annotation (9.13.12 [dcl.attr.annotation]).

22.x.3 Concept bitmask-like

namespace std {
  template <typename T>
  concept bitmask-like =
      std::is_enum_v<T> and (!std::meta::annotations_of_with_type(^^T,std::meta::type_of(^^bitmask_type)).empty()); 
}

[ Note: bitwise operators matching those required for Bitmask types [bitmask.types] are defined for types that conform to the bitmask-like concept.end note ]

1

[ Example:
enum class [[=std::bitmask_type]] Permission {
  None    = 0,
  read    = 1 << 0,
  Write   = 1 << 1,
  Execute = 1 << 2,
};

The enum Permission conforms to the concept bitmask-likeend example ]

22.x.4 bitmask operators

1 For each operator @ in (|, ^, &) there is a definition in the global namespace of the form:

template<bitmask-like T> constexpr T operator@ (T lhs,T rhs) noexcept {
  return static_cast<T>(to_underlying(lhs) @ to_underlying(rhs));
}

2 And the corresponding compound assignment operators have definitions in the global namespace of the form:

template<bitmask-like T> constexpr T& operator@= (T &lhs,T rhs) noexcept {
  lhs = lhs @ rhs; return lhs;
}

3 Additionally the complement operator is defined in the global namespace as:

template<bitmask-like T> constexpr T operator~ (T lhs) noexcept {
  return static_cast<T>(~to_underlying(lhs));
}