Skip to content

Commit

Permalink
Merge pull request #142 from elbeno/enum-bitset
Browse files Browse the repository at this point in the history
✨ Allow using `bitset` with enum values
  • Loading branch information
elbeno authored Sep 24, 2024
2 parents e71580e + 6ca3635 commit 7e3674d
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 15 deletions.
15 changes: 15 additions & 0 deletions docs/bitset.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ platform.
* Stream input and output operators are not implemented.
* A `std::hash` specialization is not implemented.
* `to_string`, `to_ulong` and `to_ullong` are not implemented
* `operator[]` is read-only: it does not return a proxy reference type

A bitset has two template parameters: the size of the bitset and the storage
element type to use. The storage element type must be unsigned.
Expand Down Expand Up @@ -71,3 +72,17 @@ auto j = bs.to_natural(); // 5 (a std::uint16_t)

Bitsets support all the usual bitwise operators (`and`, `or`, `xor` and `not`)
and also support `operator-` meaning set difference, or `a & ~b`.

A bitset can also be used with an enumeration that represents bits:
[source,cpp]
----
enum struct Bits { ZERO, ONE, TWO, THREE, MAX };
auto bs = stdx::bitset<Bits::MAX>{stdx::all_bits}; // 4 bits, value 0b1111
bs.set(Bits::ZERO);
bs.reset(Bits::ZERO);
bs.flip(Bits::ZERO);
auto bit_zero = bs[Bits::ZERO];
----

NOTE: The enumeration values are the bit positions, not the bits themselves (the
enumeration values are not fixed to powers-of-2).
39 changes: 24 additions & 15 deletions include/stdx/bitset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,20 +156,21 @@ template <std::size_t N, typename StorageElem> class bitset {
}

template <typename T> [[nodiscard]] constexpr auto to() const -> T {
static_assert(unsigned_integral<T>,
"Conversion must be to an unsigned integral type!");
static_assert(N <= std::numeric_limits<T>::digits,
using U = underlying_type_t<T>;
static_assert(
unsigned_integral<U>,
"Conversion must be to an unsigned integral type or enum!");
static_assert(N <= std::numeric_limits<U>::digits,
"Bitset too big for conversion to T");
if constexpr (std::is_same_v<StorageElem, T>) {
return storage[0] & lastmask;
if constexpr (std::is_same_v<StorageElem, U>) {
return static_cast<T>(storage[0] & lastmask);
} else {

T result{highbits()};
U result{highbits()};
for (auto i = storage_size - 2u; i < storage_size; --i) {
result = static_cast<T>(result << storage_elem_size);
result |= storage[i];
}
return result;
return static_cast<T>(result);
}
}

Expand All @@ -182,13 +183,16 @@ template <std::size_t N, typename StorageElem> class bitset {

constexpr static std::integral_constant<std::size_t, N> size{};

[[nodiscard]] constexpr auto operator[](std::size_t pos) const -> bool {
template <typename T>
[[nodiscard]] constexpr auto operator[](T idx) const -> bool {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
auto const [index, offset] = indices(pos);
return (storage[index] & (bit << offset)) != 0;
}

constexpr auto set(std::size_t pos,
bool value = true) LIFETIMEBOUND -> bitset & {
template <typename T>
constexpr auto set(T idx, bool value = true) LIFETIMEBOUND -> bitset & {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
auto const [index, offset] = indices(pos);
if (value) {
storage[index] |= static_cast<StorageElem>(bit << offset);
Expand Down Expand Up @@ -241,7 +245,9 @@ template <std::size_t N, typename StorageElem> class bitset {
return *this;
}

constexpr auto reset(std::size_t pos) LIFETIMEBOUND -> bitset & {
template <typename T>
constexpr auto reset(T idx) LIFETIMEBOUND -> bitset & {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
auto const [index, offset] = indices(pos);
storage[index] &= static_cast<StorageElem>(~(bit << offset));
return *this;
Expand All @@ -262,7 +268,8 @@ template <std::size_t N, typename StorageElem> class bitset {
return set(lsb, len, false);
}

constexpr auto flip(std::size_t pos) LIFETIMEBOUND -> bitset & {
template <typename T> constexpr auto flip(T idx) LIFETIMEBOUND -> bitset & {
auto const pos = static_cast<std::size_t>(to_underlying(idx));
auto const [index, offset] = indices(pos);
storage[index] ^= static_cast<StorageElem>(bit << offset);
return *this;
Expand Down Expand Up @@ -406,8 +413,10 @@ constexpr auto for_each(F &&f, bitset<M, S> const &...bs) -> F {
}
} // namespace detail

template <std::size_t N, typename StorageElem = void>
using bitset = detail::bitset<N, decltype(smallest_uint<N, StorageElem>())>;
template <auto N, typename StorageElem = void>
using bitset =
detail::bitset<to_underlying(N),
decltype(smallest_uint<to_underlying(N), StorageElem>())>;

} // namespace v1
} // namespace stdx
2 changes: 2 additions & 0 deletions include/stdx/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ template <typename E> constexpr auto to_underlying(E e) noexcept {
return e;
}
}
template <typename E>
using underlying_type_t = decltype(to_underlying(std::declval<E>()));

template <typename T> struct remove_cvref {
using type = std::remove_cv_t<std::remove_reference_t<T>>;
Expand Down
32 changes: 32 additions & 0 deletions test/bitset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,35 @@ TEMPLATE_TEST_CASE("find lowest unset bit (full)", "[bitset]", std::uint8_t,
constexpr auto bs = stdx::bitset<sz, TestType>{stdx::all_bits};
static_assert(bs.lowest_unset() == sz);
}

namespace {
enum struct Bits : std::uint8_t { ZERO, ONE, TWO, THREE, MAX };
}

TEST_CASE("use bitset with enum struct (construct)", "[bitset]") {
constexpr auto bs = stdx::bitset<Bits::MAX>{};
static_assert(bs.size() == stdx::to_underlying(Bits::MAX));
}

TEST_CASE("use bitset with enum struct (to)", "[bitset]") {
constexpr auto bs = stdx::bitset<Bits::MAX>{stdx::all_bits};
static_assert(bs.to<Bits>() == static_cast<Bits>(0b1111));
}

TEST_CASE("use bitset with enum struct (set/flip)", "[bitset]") {
auto bs = stdx::bitset<Bits::MAX>{};
bs.set(Bits::ZERO);
CHECK(bs.to_natural() == 1);
bs.reset(Bits::ZERO);
CHECK(bs.to_natural() == 0);
bs.flip(Bits::ZERO);
CHECK(bs.to_natural() == 1);
}

TEST_CASE("use bitset with enum struct (read index)", "[bitset]") {
constexpr auto bs = stdx::bitset<Bits::MAX>{stdx::all_bits};
static_assert(bs[Bits::ZERO]);
static_assert(bs[Bits::ONE]);
static_assert(bs[Bits::TWO]);
static_assert(bs[Bits::THREE]);
}

0 comments on commit 7e3674d

Please sign in to comment.