Skip to content

Commit 6e30094

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 6e30094

4 files changed

Lines changed: 326 additions & 0 deletions

File tree

include/stdx/bitset.hpp

Lines changed: 159 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,162 @@ 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+
public:
529+
constexpr type_bitset() = default;
530+
constexpr explicit type_bitset(all_bits_t) : bs{all_bits} {}
531+
constexpr explicit type_bitset(std::uint64_t value) : bs{value} {}
532+
533+
template <typename... Us>
534+
constexpr explicit type_bitset(type_list<Us...>)
535+
: bs{place_bits, detail::index_of<Us, list_t>...} {
536+
static_assert((... and (detail::index_of<Us, list_t> < N)),
537+
"Type not found in bitset");
538+
}
539+
540+
template <typename T> [[nodiscard]] constexpr auto to() const -> T {
541+
return bs.template to<T>();
542+
}
543+
[[nodiscard]] constexpr auto to_natural() const { return bs.to_natural(); }
544+
545+
constexpr static std::integral_constant<std::size_t, N> size{};
546+
547+
template <typename T>
548+
[[nodiscard]] constexpr auto operator[](type_identity<T>) const
549+
-> decltype(auto) {
550+
constexpr auto idx = detail::index_of<T, list_t>;
551+
static_assert(idx < sizeof...(Ts), "Type not found in bitset");
552+
return bs[idx];
553+
}
554+
555+
template <typename... Us>
556+
constexpr auto set(bool value = true) LIFETIMEBOUND -> type_bitset & {
557+
static_assert((... and (detail::index_of<Us, type_list<Ts...>> < N)),
558+
"Type not found in bitset");
559+
if constexpr (sizeof...(Us) == 0) {
560+
bs.set();
561+
} else {
562+
(bs.set(detail::index_of<Us, type_list<Ts...>>, value), ...);
563+
}
564+
return *this;
565+
}
566+
567+
template <typename... Us>
568+
constexpr auto reset() LIFETIMEBOUND -> type_bitset & {
569+
static_assert((... and (detail::index_of<Us, type_list<Ts...>> < N)),
570+
"Type not found in bitset");
571+
if constexpr (sizeof...(Us) == 0) {
572+
bs.reset();
573+
} else {
574+
(bs.reset(detail::index_of<Us, type_list<Ts...>>), ...);
575+
}
576+
return *this;
577+
}
578+
579+
template <typename... Us>
580+
constexpr auto flip() LIFETIMEBOUND -> type_bitset & {
581+
static_assert((... and (detail::index_of<Us, type_list<Ts...>> < N)),
582+
"Type not found in bitset");
583+
if constexpr (sizeof...(Us) == 0) {
584+
bs.flip();
585+
} else {
586+
(bs.flip(detail::index_of<Us, type_list<Ts...>>), ...);
587+
}
588+
return *this;
589+
}
590+
591+
[[nodiscard]] constexpr auto count() const -> std::size_t {
592+
return bs.count();
593+
}
594+
595+
[[nodiscard]] constexpr auto all() const -> bool {
596+
return count() == size();
597+
}
598+
599+
[[nodiscard]] constexpr auto any() const -> bool {
600+
return count() != std::size_t{};
601+
}
602+
603+
[[nodiscard]] constexpr auto none() const -> bool { return not any(); }
604+
605+
[[nodiscard]] constexpr auto operator~() const -> type_bitset {
606+
return type_bitset{~bs.template to<std::uint64_t>()};
607+
}
608+
609+
constexpr auto
610+
operator|=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & {
611+
bs |= rhs.bs;
612+
return *this;
613+
}
614+
615+
constexpr auto
616+
operator&=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & {
617+
bs &= rhs.bs;
618+
return *this;
619+
}
620+
621+
constexpr auto
622+
operator^=(type_bitset const &rhs) LIFETIMEBOUND->type_bitset & {
623+
bs ^= rhs.bs;
624+
return *this;
625+
}
626+
627+
template <typename F> constexpr auto for_each(F &&f) const -> F {
628+
using call_t = auto (*)(F &)->void;
629+
constexpr auto callers = apply_sequence<std::make_index_sequence<N>>(
630+
[]<std::size_t... Is>() {
631+
return std::array<call_t, N>{[](F &f) {
632+
f.template
633+
operator()<boost::mp11::mp_at_c<list_t, Is>, Is>();
634+
}...};
635+
});
636+
stdx::for_each([&](auto i) { callers[i](f); }, bs);
637+
return f;
638+
}
639+
};
481640
} // namespace v1
482641
} // 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: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
TEST_CASE("for_each", "[type_bitset]") {
158+
constexpr auto bs = stdx::type_bitset<int, float, bool>{stdx::all_bits};
159+
auto result = std::string{};
160+
bs.for_each([&]<typename T, std::size_t I>() -> void {
161+
result += std::string{stdx::type_as_string<T>()} + std::to_string(I);
162+
});
163+
CHECK(result == "int0float1bool2");
164+
}

0 commit comments

Comments
 (0)