[util] Remove enum_bitfields

The enum_bitfields system never worked quite right, and always had edge cases where name
resolution for the SFINAE would fail. Move everything over to use util::bitset, which can
be constexpr and boils down to inline integer bitops in release mode.

Improved util::bitset itself, moving the array-backed base implementation into a new
util::sized_bitset, and making the single-inttype backed implementation the base case.
Also added a distinction between | or |= (which work with real bit values) and + or +=
(which work with bit indexes).
This commit is contained in:
Justin C. Miller
2024-02-25 23:40:14 -08:00
parent f7ea46e49e
commit 9f54927a82
36 changed files with 352 additions and 622 deletions

View File

@@ -7,34 +7,148 @@
namespace util {
/// A statically-sized templated bitset
template <unsigned N>
template <typename I>
class bitset
{
public:
using storage_type = I;
private:
template <typename T>
static constexpr storage_type bit_or(T b) { return 1ull << uint64_t(b); }
template <typename T, typename ...Args>
static constexpr storage_type bit_or(T b, Args... bs) { return (1ull << storage_type(b)) | bit_or(bs...); }
public:
constexpr bitset(storage_type v = 0) : m_bits {v} {}
constexpr bitset(const bitset<I> &o) : m_bits {o.m_bits} {}
template <typename ...Args>
__attribute__ ((always_inline))
static constexpr bitset of(Args... args) { return {bit_or(args...)}; }
template <typename T>
__attribute__ ((always_inline))
static constexpr bitset from(T i) { return {storage_type(i)}; }
__attribute__ ((always_inline))
inline constexpr bitset & operator=(bitset b) { m_bits = b.m_bits; return *this; }
inline constexpr operator storage_type () const { return m_bits; }
template <typename T>
__attribute__ ((always_inline))
inline constexpr bool get(T i) const {
return m_bits & bit(i);
}
template <typename T>
__attribute__ ((always_inline))
inline bitset & set(T i) {
m_bits |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((always_inline))
inline bitset & clear(T i) {
m_bits &= ~bit(i);
return *this;
}
__attribute__ ((always_inline))
inline storage_type range(unsigned start, unsigned count) {
return (m_bits >> start) & ~((1 << count) - 1);
}
__attribute__ ((always_inline))
inline bitset & set_range(unsigned start, unsigned count, storage_type val) {
const storage_type mask = ~((1 << count) - 1) << start;
m_bits = (m_bits & ~mask) | ((val << start) & mask);
return *this;
}
template <typename T>
__attribute__ ((always_inline))
inline constexpr bool operator[](T i) const { return get(i); }
__attribute__ ((always_inline))
inline constexpr bitset operator|(bitset b) const { return {storage_type(m_bits | b.m_bits)}; }
template <typename T>
__attribute__ ((always_inline))
inline constexpr bitset operator+(T i) const { return {storage_type(m_bits | bit(i))}; }
__attribute__ ((always_inline))
inline constexpr bitset operator|=(bitset b) { *this = *this|b; return *this; }
template <typename T>
__attribute__ ((always_inline))
inline constexpr bitset operator+=(T i) { set(i); return *this; }
__attribute__ ((always_inline))
inline constexpr bitset operator&(const bitset &b) const { return {storage_type(m_bits & b.m_bits)}; }
template <typename T>
__attribute__ ((always_inline))
inline constexpr bitset operator&(T i) const { return {m_bits & bit(i)}; }
__attribute__ ((always_inline))
inline constexpr bitset & operator&=(const bitset &b) { m_bits &= b.m_bits; return *this; }
__attribute__ ((always_inline))
inline constexpr bool operator==(const bitset &b) const { return m_bits == b.m_bits; }
template <typename T>
__attribute__ ((always_inline))
inline constexpr bool operator==(T i) const { return m_bits == storage_type(i); }
inline constexpr bool empty() const { return m_bits == 0; }
inline constexpr uint64_t value() const { return m_bits; }
private:
template <typename T>
inline constexpr storage_type bit(T i) const { return (storage_type(1) << int(i)); }
storage_type m_bits;
};
using bitset64 = bitset<uint64_t>;
using bitset32 = bitset<uint32_t>;
using bitset16 = bitset<uint16_t>;
using bitset8 = bitset<uint8_t>;
template <unsigned N>
class sized_bitset
{
static constexpr unsigned num_elems = (N + 63) / 64;
public:
template <typename T>
__attribute__ ((force_inline))
__attribute__ ((always_inline))
inline bool get(T i) const {
return bits(i) & bit(i);
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & set(T i) {
__attribute__ ((always_inline))
inline sized_bitset & set(T i) {
bits(i) |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & clear(T i) {
__attribute__ ((always_inline))
inline sized_bitset & clear(T i) {
bits(i) &= ~bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
__attribute__ ((always_inline))
inline bool operator[](T i) const { return get(i); }
inline bool empty() const {
@@ -45,260 +159,18 @@ public:
private:
template <typename T>
__attribute__ ((force_inline))
__attribute__ ((always_inline))
inline uint64_t bit(T i) const { return (1ull << (static_cast<uint64_t>(i) & 63)); }
template <typename T>
__attribute__ ((force_inline))
__attribute__ ((always_inline))
inline uint64_t &bits(T i) { return m_bits[static_cast<uint64_t>(i) >> 6]; }
template <typename T>
__attribute__ ((force_inline))
__attribute__ ((always_inline))
inline uint64_t bits(T i) const { return m_bits[static_cast<uint64_t>(i) >> 6]; }
uint64_t m_bits[num_elems] = {0};
};
namespace {
}
/// A statically-sized templated bitset
template <>
class bitset<64>
{
template <typename T>
static constexpr uint64_t bit_or(T b) { return 1ull << uint64_t(b); }
template <typename T, typename ...Args>
static constexpr uint64_t bit_or(T b, Args... bs) { return (1ull << uint64_t(b)) | bit_or(bs...); }
public:
bitset(uint64_t v = 0) : m_bits {v} {}
bitset(const bitset<64> &o) : m_bits {o.m_bits} {}
template <typename ...Args>
constexpr explicit bitset(Args... args) : m_bits(bit_or(args...)) {}
template <typename T>
__attribute__ ((force_inline))
inline bitset & operator=(T v) { m_bits = static_cast<uint64_t>(v); return *this; }
inline constexpr operator const uint64_t () const { return m_bits; }
template <typename T>
__attribute__ ((force_inline))
inline constexpr bool get(T i) const {
return m_bits & bit(i);
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & set(T i) {
m_bits |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & clear(T i) {
m_bits &= ~bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline constexpr bool operator[](T i) const { return get(i); }
inline constexpr bool empty() const { return m_bits == 0; }
inline constexpr uint64_t value() const { return m_bits; }
private:
template <typename T>
inline constexpr uint64_t bit(T i) const { return (1ull << static_cast<uint64_t>(i)); }
uint64_t m_bits;
};
/// A statically-sized templated bitset
template <>
class bitset<32>
{
template <typename T>
static constexpr uint32_t bit_or(T b) { return 1u << uint32_t(b); }
template <typename T, typename ...Args>
static constexpr uint32_t bit_or(T b, Args... bs) { return (1u << uint32_t(b)) | bit_or(bs...); }
public:
bitset(uint32_t v = 0) : m_bits {v} {}
bitset(const bitset<32> &o) : m_bits {o.m_bits} {}
template <typename ...Args>
constexpr bitset(Args... args) : m_bits(bit_or(args...)) {}
template <typename T>
inline bitset & operator=(T v) { m_bits = static_cast<uint32_t>(v); return *this; }
inline constexpr operator uint32_t () const { return m_bits; }
template <typename T>
__attribute__ ((force_inline))
inline constexpr bool get(T i) const {
return m_bits & bit(i);
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & set(T i) {
m_bits |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & clear(T i) {
m_bits &= ~bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bool operator[](T i) const { return get(i); }
inline bool empty() const { return m_bits == 0; }
inline constexpr uint32_t value() const { return m_bits; }
private:
template <typename T>
inline uint32_t bit(T i) const { return (1u << static_cast<uint32_t>(i)); }
uint32_t m_bits;
};
/// A statically-sized templated bitset
template <>
class bitset<16>
{
template <typename T>
static constexpr uint16_t bit_or(T b) { return 1u << uint16_t(b); }
template <typename T, typename ...Args>
static constexpr uint16_t bit_or(T b, Args... bs) { return (1u << uint16_t(b)) | bit_or(bs...); }
public:
bitset(uint16_t v = 0) : m_bits {v} {}
bitset(const bitset<16> &o) : m_bits {o.m_bits} {}
template <typename ...Args>
constexpr bitset(Args... args) : m_bits(bit_or(args...)) {}
template <typename T>
inline bitset & operator=(T v) { m_bits = static_cast<uint16_t>(v); return *this; }
inline constexpr operator uint16_t () const { return m_bits; }
template <typename T>
__attribute__ ((force_inline))
inline constexpr bool get(T i) const {
return m_bits & bit(i);
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & set(T i) {
m_bits |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & clear(T i) {
m_bits &= ~bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bool operator[](T i) const { return get(i); }
inline bool empty() const { return m_bits == 0; }
inline constexpr uint16_t value() const { return m_bits; }
private:
template <typename T>
inline uint16_t bit(T i) const { return (1u << static_cast<uint16_t>(i)); }
uint16_t m_bits;
};
/// A statically-sized templated bitset
template <>
class bitset<8>
{
template <typename T>
static constexpr uint8_t bit_or(T b) { return 1u << uint8_t(b); }
template <typename T, typename ...Args>
static constexpr uint8_t bit_or(T b, Args... bs) { return (1u << uint8_t(b)) | bit_or(bs...); }
public:
bitset(uint8_t v = 0) : m_bits {v} {}
bitset(const bitset<8> &o) : m_bits {o.m_bits} {}
template <typename ...Args>
constexpr bitset(Args... args) : m_bits(bit_or(args...)) {}
template <typename T>
inline bitset & operator=(T v) { m_bits = static_cast<uint8_t>(v); return *this; }
inline constexpr operator uint8_t () const { return m_bits; }
template <typename T>
__attribute__ ((force_inline))
inline constexpr bool get(T i) const {
return m_bits & bit(i);
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & set(T i) {
m_bits |= bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bitset & clear(T i) {
m_bits &= ~bit(i);
return *this;
}
template <typename T>
__attribute__ ((force_inline))
inline bool operator[](T i) const { return get(i); }
inline bool empty() const { return m_bits == 0; }
inline constexpr uint8_t value() const { return m_bits; }
private:
template <typename T>
inline uint8_t bit(T i) const { return (1u << static_cast<uint8_t>(i)); }
uint8_t m_bits;
};
using bitset64 = bitset<64>;
using bitset32 = bitset<32>;
using bitset16 = bitset<16>;
using bitset8 = bitset<8>;
} // namespace util

View File

@@ -1,99 +0,0 @@
#pragma once
#include <util/basic_types.h>
namespace util {
namespace bits {
template <typename E>
constexpr bool is_enum_bitfield(E) { return false; }
template <typename E>
struct enum_or_int {
static constexpr bool value =
is_enum_bitfield(typename types::non_const<E>::type{})
|| types::is_integral<E>::value;
};
template <typename E, typename F>
struct both_enum_or_int {
static constexpr bool value =
types::conjunction< enum_or_int<E>, enum_or_int<F> >::value;
};
template <typename E, typename R>
struct enable_if_bitfield {
using enum_t = typename types::non_const<E>::type;
using type = typename
types::enable_if< is_enum_bitfield(enum_t{}), R >::type;
};
template <typename E, typename F>
constexpr typename types::enable_if<both_enum_or_int<E, F>::value,E>::type&
operator |= (E &lhs, F rhs)
{
return lhs = static_cast<E>(
static_cast<typename types::integral<E>::type>(lhs) |
static_cast<typename types::integral<F>::type>(rhs));
}
template <typename E, typename F>
constexpr typename types::enable_if<both_enum_or_int<E, F>::value,E>::type&
operator &= (E &lhs, F rhs)
{
return lhs = static_cast<E>(
static_cast<typename types::integral<E>::type>(lhs) &
static_cast<typename types::integral<F>::type>(rhs));
}
template <typename E, typename F>
constexpr typename types::enable_if<both_enum_or_int<E, F>::value,E>::type&
operator ^= (E &lhs, F rhs)
{
return lhs = static_cast<E>(
static_cast<typename types::integral<E>::type>(lhs) ^
static_cast<typename types::integral<F>::type>(rhs));
}
template <typename E, typename F>
constexpr typename types::enable_if<both_enum_or_int<E, F>::value,E>::type
operator & (E lhs, F rhs) { return lhs &= rhs; }
template <typename E, typename F>
constexpr typename types::enable_if<both_enum_or_int<E, F>::value,E>::type
operator | (E lhs, F rhs) { return lhs |= rhs; }
template <typename E, typename F>
constexpr typename types::enable_if<both_enum_or_int<E, F>::value,E>::type
operator ^ (E lhs, F rhs) { return lhs ^= rhs; }
template <typename E>
constexpr typename enable_if_bitfield<E,E>::type
operator ~ (E rhs) { return static_cast<E>(~static_cast<typename types::integral<E>::type>(rhs)); }
template <typename E>
constexpr typename enable_if_bitfield<E,bool>::type
operator ! (E rhs) { return static_cast<typename types::integral<E>::type>(rhs) == 0; }
/// Override logical-and to mean 'rhs contains all bits in lhs'
template <typename E>
constexpr typename enable_if_bitfield<E,bool>::type
operator && (E rhs, E lhs) { return (rhs & lhs) == lhs; }
/// Generic 'has' for non-marked bitfields
template <typename E, typename F>
constexpr bool has(E set, F flags)
{
return
(static_cast<typename types::integral<E>::type>(set) &
static_cast<typename types::integral<F>::type>(flags)) ==
static_cast<typename types::integral<F>::type>(flags);
}
} // namespace bits
} // namespace util
#define is_bitfield(name) \
constexpr bool is_enum_bitfield(name) { return true; } \
using namespace ::util::bits;

View File

@@ -20,7 +20,6 @@ module("util",
"util/cdb.h",
"util/counted.h",
"util/deque.h",
"util/enum_bitfields.h",
"util/format.h",
"util/hash.h",
"util/linked_list.h",