Skip to content

Commit 41587bc

Browse files
committed
✨ 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.
1 parent cefe72a commit 41587bc

4 files changed

Lines changed: 329 additions & 0 deletions

File tree

include/stdx/bitset.hpp

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <stdx/type_traits.hpp>
99
#include <stdx/udls.hpp>
1010

11+
#include <boost/mp11/algorithm.hpp>
12+
1113
#include <algorithm>
1214
#include <array>
1315
#include <cstddef>
@@ -478,5 +480,163 @@ template <typename T, typename F, typename R, auto M, typename... S>
478480
#if __cplusplus >= 202002L
479481
template <std::size_t N> bitset(ct_string<N>) -> bitset<N - 1>;
480482
#endif
483+
484+
namespace detail {
485+
template <typename...> constexpr std::size_t index_of = 0;
486+
487+
template <typename T, typename... Us>
488+
constexpr std::size_t index_of<T, type_list<Us...>> =
489+
boost::mp11::mp_find<type_list<Us...>, T>::value;
490+
} // namespace detail
491+
492+
template <typename... Ts> class type_bitset {
493+
using list_t = boost::mp11::mp_unique<type_list<Ts...>>;
494+
constexpr static std::size_t N = boost::mp11::mp_size<list_t>::value;
495+
496+
bitset<N> bs;
497+
498+
[[nodiscard]] friend constexpr auto operator==(type_bitset const &lhs,
499+
type_bitset const &rhs)
500+
-> bool {
501+
return lhs.bs == rhs.bs;
502+
}
503+
504+
friend constexpr auto operator|(type_bitset lhs, type_bitset const &rhs)
505+
-> type_bitset {
506+
lhs |= rhs;
507+
return lhs;
508+
}
509+
510+
friend constexpr auto operator&(type_bitset lhs, type_bitset const &rhs)
511+
-> type_bitset {
512+
lhs &= rhs;
513+
return lhs;
514+
}
515+
516+
friend constexpr auto operator^(type_bitset lhs, type_bitset const &rhs)
517+
-> type_bitset {
518+
lhs ^= rhs;
519+
return lhs;
520+
}
521+
522+
friend constexpr auto operator-(type_bitset const &lhs, type_bitset rhs)
523+
-> type_bitset {
524+
rhs.flip();
525+
return lhs & rhs;
526+
}
527+
528+
template <typename F, std::size_t... Is>
529+
constexpr static auto make_callers(std::index_sequence<Is...>) {
530+
using call_t = auto (*)(F &)->void;
531+
return std::array<call_t, N>{[](F &f) {
532+
f.template operator()<boost::mp11::mp_at_c<list_t, Is>, Is>();
533+
}...};
534+
}
535+
536+
public:
537+
constexpr type_bitset() = default;
538+
constexpr explicit type_bitset(all_bits_t) : bs{all_bits} {}
539+
constexpr explicit type_bitset(std::uint64_t value) : bs{value} {}
540+
541+
template <typename... Us>
542+
constexpr explicit type_bitset(type_list<Us...>)
543+
: bs{place_bits, detail::index_of<Us, list_t>...} {
544+
static_assert((... and (detail::index_of<Us, list_t> < N)),
545+
"Type not found in bitset");
546+
}
547+
548+
template <typename T> [[nodiscard]] constexpr auto to() const -> T {
549+
return bs.template to<T>();
550+
}
551+
[[nodiscard]] constexpr auto to_natural() const { return bs.to_natural(); }
552+
553+
constexpr static std::integral_constant<std::size_t, N> size{};
554+
555+
template <typename T>
556+
[[nodiscard]] constexpr auto operator[](type_identity<T>) const
557+
-> decltype(auto) {
558+
constexpr auto idx = detail::index_of<T, list_t>;
559+
static_assert(idx < sizeof...(Ts), "Type not found in bitset");
560+
return bs[idx];
561+
}
562+
563+
template <typename... Us>
564+
constexpr auto set(bool value = true) LIFETIMEBOUND -> type_bitset & {
565+
static_assert((... and (detail::index_of<Us, type_list<Ts...>> < N)),
566+
"Type not found in bitset");
567+
if constexpr (sizeof...(Us) == 0) {
568+
bs.set();
569+
} else {
570+
(bs.set(detail::index_of<Us, type_list<Ts...>>, value), ...);
571+
}
572+
return *this;
573+
}
574+
575+
template <typename... Us>
576+
constexpr auto reset() LIFETIMEBOUND -> type_bitset & {
577+
static_assert((... and (detail::index_of<Us, type_list<Ts...>> < N)),
578+
"Type not found in bitset");
579+
if constexpr (sizeof...(Us) == 0) {
580+
bs.reset();
581+
} else {
582+
(bs.reset(detail::index_of<Us, type_list<Ts...>>), ...);
583+
}
584+
return *this;
585+
}
586+
587+
template <typename... Us>
588+
constexpr auto flip() LIFETIMEBOUND -> type_bitset & {
589+
static_assert((... and (detail::index_of<Us, type_list<Ts...>> < N)),
590+
"Type not found in bitset");
591+
if constexpr (sizeof...(Us) == 0) {
592+
bs.flip();
593+
} else {
594+
(bs.flip(detail::index_of<Us, type_list<Ts...>>), ...);
595+
}
596+
return *this;
597+
}
598+
599+
[[nodiscard]] constexpr auto count() const -> std::size_t {
600+
return bs.count();
601+
}
602+
603+
[[nodiscard]] constexpr auto all() const -> bool {
604+
return count() == size();
605+
}
606+
607+
[[nodiscard]] constexpr auto any() const -> bool {
608+
return count() != std::size_t{};
609+
}
610+
611+
[[nodiscard]] constexpr auto none() const -> bool { return not any(); }
612+
613+
[[nodiscard]] constexpr auto operator~() const -> type_bitset {
614+
return type_bitset{~bs.template to<std::uint64_t>()};
615+
}
616+
617+
constexpr auto
618+
operator|=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & {
619+
bs |= rhs.bs;
620+
return *this;
621+
}
622+
623+
constexpr auto
624+
operator&=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & {
625+
bs &= rhs.bs;
626+
return *this;
627+
}
628+
629+
constexpr auto
630+
operator^=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & {
631+
bs ^= rhs.bs;
632+
return *this;
633+
}
634+
635+
template <typename F> constexpr auto for_each(F &&f) const -> F {
636+
constexpr auto callers = make_callers<F>(std::make_index_sequence<N>{});
637+
stdx::for_each([&](auto i) { callers[i](f); }, bs);
638+
return f;
639+
}
640+
};
481641
} // namespace v1
482642
} // namespace stdx

include/stdx/type_traits.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ template <typename T> struct type_identity {
7777
using type = T;
7878
};
7979
template <typename T> using type_identity_t = typename type_identity<T>::type;
80+
template <typename T>
81+
constexpr static auto type_identity_v = type_identity<T>{};
8082

8183
namespace detail {
8284
template <typename T, template <typename...> typename U>

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ add_tests(
6262
rollover
6363
span
6464
to_underlying
65+
type_bitset
6566
type_map
6667
type_traits
6768
with_result_of

test/type_bitset.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#include <stdx/bitset.hpp>
2+
#include <stdx/ct_conversions.hpp>
3+
4+
#include <catch2/catch_template_test_macros.hpp>
5+
#include <catch2/catch_test_macros.hpp>
6+
7+
#include <cstddef>
8+
#include <cstdint>
9+
#include <string>
10+
#include <type_traits>
11+
12+
TEST_CASE("bitset size", "[type_bitset]") {
13+
STATIC_CHECK(stdx::type_bitset<int>{}.size() == 1u);
14+
STATIC_CHECK(stdx::type_bitset<int, float>{}.size() == 2u);
15+
}
16+
17+
TEST_CASE("index operation", "[type_bitset]") {
18+
STATIC_CHECK(not stdx::type_bitset<int>{}[stdx::type_identity_v<int>]);
19+
}
20+
21+
TEST_CASE("set single bit", "[type_bitset]") {
22+
auto bs = stdx::type_bitset<int, float, bool>{};
23+
CHECK(not bs[stdx::type_identity_v<int>]);
24+
bs.set<int>();
25+
CHECK(bs[stdx::type_identity_v<int>]);
26+
bs.set<int>(false);
27+
CHECK(not bs[stdx::type_identity_v<int>]);
28+
}
29+
30+
TEST_CASE("reset single bit", "[type_bitset]") {
31+
auto bs = stdx::type_bitset<int, float, bool>{stdx::all_bits};
32+
CHECK(bs[stdx::type_identity_v<int>]);
33+
bs.reset<int>();
34+
CHECK(not bs[stdx::type_identity_v<int>]);
35+
}
36+
37+
TEST_CASE("flip single bit", "[type_bitset]") {
38+
auto bs = stdx::type_bitset<int, float, bool>{stdx::all_bits};
39+
CHECK(bs[stdx::type_identity_v<int>]);
40+
bs.flip<int>();
41+
CHECK(not bs[stdx::type_identity_v<int>]);
42+
}
43+
44+
TEST_CASE("construct with a value", "[type bitset]") {
45+
constexpr auto bs1 = stdx::type_bitset<int, float>{1ul};
46+
STATIC_CHECK(bs1[stdx::type_identity_v<int>]);
47+
48+
constexpr auto bs2 = stdx::type_bitset<int, float>{255ul};
49+
STATIC_CHECK(bs2[stdx::type_identity_v<int>]);
50+
STATIC_CHECK(bs2[stdx::type_identity_v<float>]);
51+
}
52+
53+
TEST_CASE("construct with values for bits", "[type_bitset]") {
54+
constexpr auto bs =
55+
stdx::type_bitset<int, float, bool>{stdx::type_list<int, bool>{}};
56+
STATIC_CHECK(bs[stdx::type_identity_v<int>]);
57+
STATIC_CHECK(not bs[stdx::type_identity_v<float>]);
58+
STATIC_CHECK(bs[stdx::type_identity_v<bool>]);
59+
}
60+
61+
TEMPLATE_TEST_CASE("convert to unsigned integral type", "[type_bitset]",
62+
std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t) {
63+
constexpr auto bs = stdx::type_bitset<int, float, bool>{255ul};
64+
constexpr auto val = bs.template to<TestType>();
65+
STATIC_REQUIRE(std::is_same_v<decltype(val), TestType const>);
66+
STATIC_REQUIRE(val == 7u);
67+
}
68+
69+
TEST_CASE("convert to type that fits", "[type_bitset]") {
70+
constexpr auto bs = stdx::type_bitset<int, float, bool>{stdx::all_bits};
71+
constexpr auto val = bs.to_natural();
72+
STATIC_REQUIRE(std::is_same_v<decltype(val), std::uint8_t const>);
73+
STATIC_REQUIRE(val == 7u);
74+
}
75+
76+
TEST_CASE("all", "[type bitset]") {
77+
constexpr auto bs1 = stdx::type_bitset<int, float, bool>{stdx::all_bits};
78+
STATIC_CHECK(bs1.all());
79+
80+
constexpr auto bs2 = stdx::type_bitset<int, float, bool>{};
81+
STATIC_CHECK(not bs2.all());
82+
}
83+
84+
TEST_CASE("any", "[type bitset]") {
85+
constexpr auto bs1 = stdx::type_bitset<int, float, bool>{stdx::all_bits};
86+
STATIC_CHECK(bs1.any());
87+
88+
constexpr auto bs2 = stdx::type_bitset<int, float, bool>{};
89+
STATIC_CHECK(not bs2.any());
90+
}
91+
92+
TEST_CASE("none", "[type bitset]") {
93+
constexpr auto bs1 = stdx::type_bitset<int, float, bool>{stdx::all_bits};
94+
STATIC_CHECK(not bs1.none());
95+
96+
constexpr auto bs2 = stdx::type_bitset<int, float, bool>{};
97+
STATIC_CHECK(bs2.none());
98+
}
99+
100+
TEST_CASE("count", "[type_bitset]") {
101+
constexpr auto bs1 = stdx::type_bitset<int, float, bool>{};
102+
STATIC_CHECK(bs1.count() == 0u);
103+
104+
constexpr auto bs2 = stdx::type_bitset<int, float, bool>{stdx::all_bits};
105+
STATIC_CHECK(bs2.count() == 3u);
106+
}
107+
108+
TEST_CASE("set all bits", "[type_bitset]") {
109+
auto bs = stdx::type_bitset<int, float, bool>{};
110+
bs.set();
111+
CHECK(bs == stdx::type_bitset<int, float, bool>{stdx::all_bits});
112+
bs.set();
113+
CHECK(bs == stdx::type_bitset<int, float, bool>{stdx::all_bits});
114+
}
115+
116+
TEST_CASE("reset all bits", "[type_bitset]") {
117+
auto bs = stdx::type_bitset<int, float, bool>{stdx::all_bits};
118+
bs.reset();
119+
CHECK(bs == stdx::type_bitset<int, float, bool>{});
120+
bs.reset();
121+
CHECK(bs == stdx::type_bitset<int, float, bool>{});
122+
}
123+
124+
TEST_CASE("flip all bits", "[type_bitset]") {
125+
auto bs = stdx::type_bitset<int, float, bool>{stdx::all_bits};
126+
bs.flip();
127+
CHECK(bs == stdx::type_bitset<int, float, bool>{});
128+
bs.flip();
129+
CHECK(bs == stdx::type_bitset<int, float, bool>{stdx::all_bits});
130+
}
131+
132+
TEST_CASE("or", "[type_bitset]") {
133+
constexpr auto bs1 = stdx::type_bitset<int, float, bool>{0b101ul};
134+
constexpr auto bs2 = stdx::type_bitset<int, float, bool>{0b010ul};
135+
STATIC_REQUIRE((bs1 | bs2) ==
136+
stdx::type_bitset<int, float, bool>{stdx::all_bits});
137+
}
138+
139+
TEST_CASE("and", "[type_bitset]") {
140+
constexpr auto bs1 = stdx::type_bitset<int, float, bool>{0b101ul};
141+
constexpr auto bs2 = stdx::type_bitset<int, float, bool>{0b100ul};
142+
STATIC_REQUIRE((bs1 & bs2) == stdx::type_bitset<int, float, bool>{0b100ul});
143+
}
144+
145+
TEST_CASE("xor", "[type_bitset]") {
146+
constexpr auto bs1 = stdx::type_bitset<int, float, bool>{0b101ul};
147+
constexpr auto bs2 = stdx::type_bitset<int, float, bool>{0b010ul};
148+
STATIC_REQUIRE((bs1 ^ bs2) ==
149+
stdx::type_bitset<int, float, bool>{stdx::all_bits});
150+
}
151+
152+
TEST_CASE("not", "[type_bitset]") {
153+
constexpr auto bs = stdx::type_bitset<int, float, bool>{0b101ul};
154+
STATIC_REQUIRE(~bs == stdx::type_bitset<int, float, bool>{0b10ul});
155+
}
156+
157+
#if __cplusplus >= 202002L
158+
TEST_CASE("for_each", "[type_bitset]") {
159+
constexpr auto bs = stdx::type_bitset<int, float, bool>{stdx::all_bits};
160+
auto result = std::string{};
161+
bs.for_each([&]<typename T, std::size_t I>() -> void {
162+
result += std::string{stdx::type_as_string<T>()} + std::to_string(I);
163+
});
164+
CHECK(result == "int0float1bool2");
165+
}
166+
#endif

0 commit comments

Comments
 (0)