From 6ca363531678841a93cf05361f8230753adbb98c Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Tue, 24 Sep 2024 14:17:30 -0600 Subject: [PATCH] :sparkles: Allow using `bitset` with enum values Problem: - A `bitset` cannot be specified with enumeration values, nor have bits set/reset using them. Solution: - Allow specifying and manipulating a bitset using enumeration values as the bits. --- docs/bitset.adoc | 15 ++++++++++++++ include/stdx/bitset.hpp | 39 ++++++++++++++++++++++-------------- include/stdx/type_traits.hpp | 2 ++ test/bitset.cpp | 32 +++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/docs/bitset.adoc b/docs/bitset.adoc index 5f28aa2..59455fb 100644 --- a/docs/bitset.adoc +++ b/docs/bitset.adoc @@ -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. @@ -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{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). diff --git a/include/stdx/bitset.hpp b/include/stdx/bitset.hpp index 7ff74ab..8b8c6ca 100644 --- a/include/stdx/bitset.hpp +++ b/include/stdx/bitset.hpp @@ -156,20 +156,21 @@ template class bitset { } template [[nodiscard]] constexpr auto to() const -> T { - static_assert(unsigned_integral, - "Conversion must be to an unsigned integral type!"); - static_assert(N <= std::numeric_limits::digits, + using U = underlying_type_t; + static_assert( + unsigned_integral, + "Conversion must be to an unsigned integral type or enum!"); + static_assert(N <= std::numeric_limits::digits, "Bitset too big for conversion to T"); - if constexpr (std::is_same_v) { - return storage[0] & lastmask; + if constexpr (std::is_same_v) { + return static_cast(storage[0] & lastmask); } else { - - T result{highbits()}; + U result{highbits()}; for (auto i = storage_size - 2u; i < storage_size; --i) { result = static_cast(result << storage_elem_size); result |= storage[i]; } - return result; + return static_cast(result); } } @@ -182,13 +183,16 @@ template class bitset { constexpr static std::integral_constant size{}; - [[nodiscard]] constexpr auto operator[](std::size_t pos) const -> bool { + template + [[nodiscard]] constexpr auto operator[](T idx) const -> bool { + auto const pos = static_cast(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 + constexpr auto set(T idx, bool value = true) LIFETIMEBOUND -> bitset & { + auto const pos = static_cast(to_underlying(idx)); auto const [index, offset] = indices(pos); if (value) { storage[index] |= static_cast(bit << offset); @@ -241,7 +245,9 @@ template class bitset { return *this; } - constexpr auto reset(std::size_t pos) LIFETIMEBOUND -> bitset & { + template + constexpr auto reset(T idx) LIFETIMEBOUND -> bitset & { + auto const pos = static_cast(to_underlying(idx)); auto const [index, offset] = indices(pos); storage[index] &= static_cast(~(bit << offset)); return *this; @@ -262,7 +268,8 @@ template class bitset { return set(lsb, len, false); } - constexpr auto flip(std::size_t pos) LIFETIMEBOUND -> bitset & { + template constexpr auto flip(T idx) LIFETIMEBOUND -> bitset & { + auto const pos = static_cast(to_underlying(idx)); auto const [index, offset] = indices(pos); storage[index] ^= static_cast(bit << offset); return *this; @@ -406,8 +413,10 @@ constexpr auto for_each(F &&f, bitset const &...bs) -> F { } } // namespace detail -template -using bitset = detail::bitset())>; +template +using bitset = + detail::bitset())>; } // namespace v1 } // namespace stdx diff --git a/include/stdx/type_traits.hpp b/include/stdx/type_traits.hpp index c9a0444..7b0bc59 100644 --- a/include/stdx/type_traits.hpp +++ b/include/stdx/type_traits.hpp @@ -13,6 +13,8 @@ template constexpr auto to_underlying(E e) noexcept { return e; } } +template +using underlying_type_t = decltype(to_underlying(std::declval())); template struct remove_cvref { using type = std::remove_cv_t>; diff --git a/test/bitset.cpp b/test/bitset.cpp index efa6673..2b54d4f 100644 --- a/test/bitset.cpp +++ b/test/bitset.cpp @@ -422,3 +422,35 @@ TEMPLATE_TEST_CASE("find lowest unset bit (full)", "[bitset]", std::uint8_t, constexpr auto bs = stdx::bitset{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{}; + static_assert(bs.size() == stdx::to_underlying(Bits::MAX)); +} + +TEST_CASE("use bitset with enum struct (to)", "[bitset]") { + constexpr auto bs = stdx::bitset{stdx::all_bits}; + static_assert(bs.to() == static_cast(0b1111)); +} + +TEST_CASE("use bitset with enum struct (set/flip)", "[bitset]") { + auto bs = stdx::bitset{}; + 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{stdx::all_bits}; + static_assert(bs[Bits::ZERO]); + static_assert(bs[Bits::ONE]); + static_assert(bs[Bits::TWO]); + static_assert(bs[Bits::THREE]); +}