From 94b72914e7682b41d04323689ac0e8c51baf4cd8 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Thu, 29 Jan 2026 11:35:27 -0700 Subject: [PATCH] :sparkles: Add type-indexed bitset Problem: - Sometimes we have a need to map types to on/off status. The way we do this right now is clunky, typically using `bool` variable templates. Solution: - Add a `type_bitset` which holds one bit per type, and supports set/reset/read on a type basis, as well as some of the usual bitset operations. --- include/stdx/bitset.hpp | 160 ++++++++++++++++++++++++++++++++ include/stdx/type_traits.hpp | 2 + test/CMakeLists.txt | 1 + test/type_bitset.cpp | 172 +++++++++++++++++++++++++++++++++++ 4 files changed, 335 insertions(+) create mode 100644 test/type_bitset.cpp diff --git a/include/stdx/bitset.hpp b/include/stdx/bitset.hpp index f69d00b..3d05ac9 100644 --- a/include/stdx/bitset.hpp +++ b/include/stdx/bitset.hpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include #include @@ -478,5 +480,163 @@ template #if __cplusplus >= 202002L template bitset(ct_string) -> bitset; #endif + +namespace detail { +template constexpr std::size_t index_of = 0; + +template +constexpr std::size_t index_of> = + boost::mp11::mp_find, T>::value; +} // namespace detail + +template class type_bitset { + using list_t = boost::mp11::mp_unique>; + constexpr static std::size_t N = boost::mp11::mp_size::value; + + bitset bs; + + [[nodiscard]] friend constexpr auto operator==(type_bitset const &lhs, + type_bitset const &rhs) + -> bool { + return lhs.bs == rhs.bs; + } + + friend constexpr auto operator|(type_bitset lhs, type_bitset const &rhs) + -> type_bitset { + lhs |= rhs; + return lhs; + } + + friend constexpr auto operator&(type_bitset lhs, type_bitset const &rhs) + -> type_bitset { + lhs &= rhs; + return lhs; + } + + friend constexpr auto operator^(type_bitset lhs, type_bitset const &rhs) + -> type_bitset { + lhs ^= rhs; + return lhs; + } + + friend constexpr auto operator-(type_bitset const &lhs, type_bitset rhs) + -> type_bitset { + rhs.flip(); + return lhs & rhs; + } + + template + constexpr static auto make_callers(std::index_sequence) { + using call_t = auto (*)(F &)->void; + return std::array{[](F &f) { + f.template operator(), Is>(); + }...}; + } + + public: + constexpr type_bitset() = default; + constexpr explicit type_bitset(all_bits_t) : bs{all_bits} {} + constexpr explicit type_bitset(std::uint64_t value) : bs{value} {} + + template + constexpr explicit type_bitset(type_list) + : bs{place_bits, detail::index_of...} { + static_assert((... and (detail::index_of < N)), + "Type not found in bitset"); + } + + template [[nodiscard]] constexpr auto to() const -> T { + return bs.template to(); + } + [[nodiscard]] constexpr auto to_natural() const { return bs.to_natural(); } + + constexpr static std::integral_constant size{}; + + template + [[nodiscard]] constexpr auto operator[](type_identity) const + -> decltype(auto) { + constexpr auto idx = detail::index_of; + static_assert(idx < sizeof...(Ts), "Type not found in bitset"); + return bs[idx]; + } + + template + constexpr auto set(bool value = true) LIFETIMEBOUND -> type_bitset & { + static_assert((... and (detail::index_of> < N)), + "Type not found in bitset"); + if constexpr (sizeof...(Us) == 0) { + bs.set(); + } else { + (bs.set(detail::index_of>, value), ...); + } + return *this; + } + + template + constexpr auto reset() LIFETIMEBOUND -> type_bitset & { + static_assert((... and (detail::index_of> < N)), + "Type not found in bitset"); + if constexpr (sizeof...(Us) == 0) { + bs.reset(); + } else { + (bs.reset(detail::index_of>), ...); + } + return *this; + } + + template + constexpr auto flip() LIFETIMEBOUND -> type_bitset & { + static_assert((... and (detail::index_of> < N)), + "Type not found in bitset"); + if constexpr (sizeof...(Us) == 0) { + bs.flip(); + } else { + (bs.flip(detail::index_of>), ...); + } + return *this; + } + + [[nodiscard]] constexpr auto count() const -> std::size_t { + return bs.count(); + } + + [[nodiscard]] constexpr auto all() const -> bool { + return count() == size(); + } + + [[nodiscard]] constexpr auto any() const -> bool { + return count() != std::size_t{}; + } + + [[nodiscard]] constexpr auto none() const -> bool { return not any(); } + + [[nodiscard]] constexpr auto operator~() const -> type_bitset { + return type_bitset{~bs.template to()}; + } + + constexpr auto + operator|=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & { + bs |= rhs.bs; + return *this; + } + + constexpr auto + operator&=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & { + bs &= rhs.bs; + return *this; + } + + constexpr auto + operator^=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & { + bs ^= rhs.bs; + return *this; + } + + template constexpr auto for_each(F &&f) const -> F { + constexpr auto callers = make_callers(std::make_index_sequence{}); + stdx::for_each([&](auto i) { callers[i](f); }, bs); + return f; + } +}; } // namespace v1 } // namespace stdx diff --git a/include/stdx/type_traits.hpp b/include/stdx/type_traits.hpp index 70e79ad..11d2ce0 100644 --- a/include/stdx/type_traits.hpp +++ b/include/stdx/type_traits.hpp @@ -77,6 +77,8 @@ template struct type_identity { using type = T; }; template using type_identity_t = typename type_identity::type; +template +constexpr static auto type_identity_v = type_identity{}; namespace detail { template typename U> diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index da67474..1ecbafd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,6 +63,7 @@ add_tests( rollover span to_underlying + type_bitset type_map type_traits with_result_of diff --git a/test/type_bitset.cpp b/test/type_bitset.cpp new file mode 100644 index 0000000..1b52453 --- /dev/null +++ b/test/type_bitset.cpp @@ -0,0 +1,172 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include + +TEST_CASE("bitset size", "[type_bitset]") { + STATIC_CHECK(stdx::type_bitset{}.size() == 1u); + STATIC_CHECK(stdx::type_bitset{}.size() == 2u); +} + +TEST_CASE("index operation", "[type_bitset]") { + STATIC_CHECK(not stdx::type_bitset{}[stdx::type_identity_v]); +} + +TEST_CASE("set single bit", "[type_bitset]") { + auto bs = stdx::type_bitset{}; + CHECK(not bs[stdx::type_identity_v]); + bs.set(); + CHECK(bs[stdx::type_identity_v]); + bs.set(false); + CHECK(not bs[stdx::type_identity_v]); +} + +TEST_CASE("reset single bit", "[type_bitset]") { + auto bs = stdx::type_bitset{stdx::all_bits}; + CHECK(bs[stdx::type_identity_v]); + bs.reset(); + CHECK(not bs[stdx::type_identity_v]); +} + +TEST_CASE("flip single bit", "[type_bitset]") { + auto bs = stdx::type_bitset{stdx::all_bits}; + CHECK(bs[stdx::type_identity_v]); + bs.flip(); + CHECK(not bs[stdx::type_identity_v]); +} + +TEST_CASE("construct with a value", "[type bitset]") { + constexpr auto bs1 = stdx::type_bitset{1ul}; + STATIC_CHECK(bs1[stdx::type_identity_v]); + + constexpr auto bs2 = stdx::type_bitset{255ul}; + STATIC_CHECK(bs2[stdx::type_identity_v]); + STATIC_CHECK(bs2[stdx::type_identity_v]); +} + +TEST_CASE("construct with values for bits", "[type_bitset]") { + constexpr auto bs = + stdx::type_bitset{stdx::type_list{}}; + STATIC_CHECK(bs[stdx::type_identity_v]); + STATIC_CHECK(not bs[stdx::type_identity_v]); + STATIC_CHECK(bs[stdx::type_identity_v]); +} + +TEMPLATE_TEST_CASE("convert to unsigned integral type", "[type_bitset]", + std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t) { + constexpr auto bs = stdx::type_bitset{255ul}; + constexpr auto val = bs.template to(); + STATIC_CHECK(std::is_same_v); + STATIC_CHECK(val == 7u); +} + +TEST_CASE("convert to type that fits", "[type_bitset]") { + constexpr auto bs = stdx::type_bitset{stdx::all_bits}; + constexpr auto val = bs.to_natural(); + STATIC_CHECK(std::is_same_v); + STATIC_CHECK(val == 7u); +} + +TEST_CASE("all", "[type bitset]") { + constexpr auto bs1 = stdx::type_bitset{stdx::all_bits}; + STATIC_CHECK(bs1.all()); + + constexpr auto bs2 = stdx::type_bitset{}; + STATIC_CHECK(not bs2.all()); +} + +TEST_CASE("any", "[type bitset]") { + constexpr auto bs1 = stdx::type_bitset{stdx::all_bits}; + STATIC_CHECK(bs1.any()); + + constexpr auto bs2 = stdx::type_bitset{}; + STATIC_CHECK(not bs2.any()); +} + +TEST_CASE("none", "[type bitset]") { + constexpr auto bs1 = stdx::type_bitset{stdx::all_bits}; + STATIC_CHECK(not bs1.none()); + + constexpr auto bs2 = stdx::type_bitset{}; + STATIC_CHECK(bs2.none()); +} + +TEST_CASE("count", "[type_bitset]") { + constexpr auto bs1 = stdx::type_bitset{}; + STATIC_CHECK(bs1.count() == 0u); + + constexpr auto bs2 = stdx::type_bitset{stdx::all_bits}; + STATIC_CHECK(bs2.count() == 3u); +} + +TEST_CASE("set all bits", "[type_bitset]") { + auto bs = stdx::type_bitset{}; + bs.set(); + CHECK(bs == stdx::type_bitset{stdx::all_bits}); + bs.set(); + CHECK(bs == stdx::type_bitset{stdx::all_bits}); +} + +TEST_CASE("reset all bits", "[type_bitset]") { + auto bs = stdx::type_bitset{stdx::all_bits}; + bs.reset(); + CHECK(bs == stdx::type_bitset{}); + bs.reset(); + CHECK(bs == stdx::type_bitset{}); +} + +TEST_CASE("flip all bits", "[type_bitset]") { + auto bs = stdx::type_bitset{stdx::all_bits}; + bs.flip(); + CHECK(bs == stdx::type_bitset{}); + bs.flip(); + CHECK(bs == stdx::type_bitset{stdx::all_bits}); +} + +TEST_CASE("or", "[type_bitset]") { + constexpr auto bs1 = stdx::type_bitset{0b101ul}; + constexpr auto bs2 = stdx::type_bitset{0b010ul}; + STATIC_CHECK((bs1 | bs2) == + stdx::type_bitset{stdx::all_bits}); +} + +TEST_CASE("and", "[type_bitset]") { + constexpr auto bs1 = stdx::type_bitset{0b101ul}; + constexpr auto bs2 = stdx::type_bitset{0b100ul}; + STATIC_CHECK((bs1 & bs2) == stdx::type_bitset{0b100ul}); +} + +TEST_CASE("xor", "[type_bitset]") { + constexpr auto bs1 = stdx::type_bitset{0b101ul}; + constexpr auto bs2 = stdx::type_bitset{0b010ul}; + STATIC_CHECK((bs1 ^ bs2) == + stdx::type_bitset{stdx::all_bits}); +} + +TEST_CASE("not", "[type_bitset]") { + constexpr auto bs = stdx::type_bitset{0b101ul}; + STATIC_CHECK(~bs == stdx::type_bitset{0b10ul}); +} + +TEST_CASE("difference", "[type_bitset]") { + constexpr auto bs1 = stdx::type_bitset{0b101ul}; + constexpr auto bs2 = stdx::type_bitset{0b011ul}; + STATIC_CHECK((bs1 - bs2) == stdx::type_bitset{0b100ul}); +} + +#if __cplusplus >= 202002L +TEST_CASE("for_each", "[type_bitset]") { + constexpr auto bs = stdx::type_bitset{stdx::all_bits}; + auto result = std::string{}; + bs.for_each([&]() -> void { + result += std::string{stdx::type_as_string()} + std::to_string(I); + }); + CHECK(result == "int0float1bool2"); +} +#endif