I want to enable bitmask-like behavior (ie. overloaded operator|
, operator&
and operator^
) for some enum class
es. This is what I came up with:
#pragma once
#include <type_traits>
/// \brief Marks an enum class as enabled for bitmask behavior.
/// \param type The name of the enum for which to enable the bitmask.
/// \details This macro works by specializing \see{is_bitmask}.
#define ENABLE_BITMASK(type) template<> \
struct is_bitmask<type> { \
constexpr static bool value = true; \
private: \
is_bitmask() = delete; \
};
/// This is a marker struct to enable bitmask behavior for enum classes. Should be
/// automatically specialized by using \see ENABLE_BITMASK.
/// \tparam T The enum class for which to enable this behavior.
template <typename T>
struct is_bitmask final {
constexpr static bool value = false;
private:
is_bitmask() = delete;
};
template <typename T>
concept is_enum_bitmask = std::is_enum_v<T> && (is_bitmask<T>::value == std::true_type::value);
/// Enables bitmask behavior for enum classes by overloading operator|, operator&, operator^ and operator~.
/// \tparam Bits The enum to turn into a bitmask. This template parameter is constrained by \see is_enum_bitmask.
template <is_enum_bitmask Bits>
struct EnumBitset {
private:
using Type = std::underlying_type_t<Bits>;
Type bits_ = 0;
constexpr EnumBitset(Type b) noexcept {
bits_ = b;
}
public:
constexpr EnumBitset(Bits bit) noexcept {
bits_ = static_cast<Type>(bit);
}
constexpr EnumBitset() noexcept {
bits_ = 0;
}
constexpr EnumBitset(const EnumBitset& other) = default;
constexpr EnumBitset(EnumBitset&& other) noexcept = default;
constexpr EnumBitset& operator=(const EnumBitset& other) = default;
constexpr EnumBitset& operator=(EnumBitset&& other) noexcept = default;
constexpr ~EnumBitset() noexcept = default;
[[nodiscard]] constexpr inline EnumBitset<Bits> operator|(const EnumBitset<Bits>& b) const noexcept {
return EnumBitset{this->bits_ | b.bits_};
}
[[nodiscard]] constexpr inline EnumBitset<Bits> operator&(const EnumBitset<Bits>& b) const noexcept {
return EnumBitset{this->bits_ & b.bits_};
}
[[nodiscard]] constexpr inline EnumBitset<Bits> operator^(const EnumBitset<Bits>& b) const noexcept {
return EnumBitset{this->bits_ ^ b.bits_};
}
constexpr inline void operator|=(const EnumBitset<Bits>& b) noexcept {
this->bits_ |= b.bits_;
}
constexpr inline void operator&=(const EnumBitset<Bits>& b) noexcept {
this->bits_ &= b.bits_;
}
constexpr inline void operator^=(const EnumBitset<Bits>& b) noexcept {
this->bits_ ^= b.bits_;
}
[[nodiscard]] constexpr inline bool operator==(const EnumBitset<Bits>& b) const noexcept {
return this->bits_ == b.bits_;
}
[[nodiscard]] constexpr inline bool operator!=(const EnumBitset<Bits>& b) const noexcept {
return this->bits_ != b.bits_;
}
constexpr inline EnumBitset<Bits> operator~() const noexcept {
return EnumBitset{~this->bits_};
}
constexpr inline operator bool() const noexcept {
return bits_ != 0;
}
constexpr inline explicit operator Type() const noexcept {
return bits_;
}
[[nodiscard]] constexpr inline Type getBits() const { return bits_; }
};
template <typename T, typename U>
requires (is_enum_bitmask<T> && std::is_constructible_v<EnumBitset<T>, U>)
constexpr auto operator|(T left, U right) -> EnumBitset<T> {
return EnumBitset<T>(left) | right;
}
template <typename T, typename U>
requires (is_enum_bitmask<T> && std::is_constructible_v<EnumBitset<T>, U>)
constexpr auto operator&(T left, U right) -> EnumBitset<T> {
return EnumBitset<T>(left) & right;
}
template <typename T, typename U>
requires (is_enum_bitmask<T> && std::is_constructible_v<EnumBitset<T>, U>)
constexpr auto operator^(T left, U right) -> EnumBitset<T> {
return EnumBitset<T>(left) ^ right;
}
Here's how it's supposed to work:
- One can enable bitmask behavior by using
ENABLE_BITMASK(MyEnum)
. I'm really unsure if this is a good way of doing this or if there are better ways to tag an enum as "bitmask enabled". I don't want to enable it for all enum classes since this would makeenum class
es pointless. - All enum class constants can be used with each other, but we can't mix two enums.
- All operations should be
constexpr
so they can be optimized by the compiler. - (Known Limitation):
std::is_enum_v<T>
works for both enum classes and enums, I'll fix this with C++23'sstd::is_scoped_enum<T>
as soon as it's available.
Here's some example code:
enum class BitmaskableEnum {
kFirst = 1,
kSecond = 2,
kThird = 4,
kFirstAndSecond = 3
};
ENABLE_BITMASK(BitmaskableEnum);
enum class SecondBitmaskableEnum {
kFirst = 1
};
ENABLE_BITMASK(SecondBitmaskableEnum);
enum class RegularEnum {
kFirstBit = 1,
kSecondBit = 2,
kBoth = 3
};
void tests() {
// Should compile
auto a = BitmaskableEnum::kFirst
| BitmaskableEnum::kSecond
^ BitmaskableEnum::kThird;
a ^= BitmaskableEnum::kThird;
// Should be true
assert(a == BitmaskableEnum::kFirstAndSecond);
// Those should not compile:
const auto b = RegularEnum::kFirstBit | RegularEnum::kSecondBit;
const auto c = BitmaskableEnum::kFirst | SecondBitmaskableEnum::kFirst;
}
I also have a minor question: I'll be using this in a larger project of mine and would like to put this in the util
namespace. Would this work with ADL? If not: Which parts need to be kept in the global namespace?