Skip to content

Commit 7e3674d

Browse files
authored
Merge pull request #142 from elbeno/enum-bitset
✨ Allow using `bitset` with enum values
2 parents e71580e + 6ca3635 commit 7e3674d

File tree

4 files changed

+73
-15
lines changed

4 files changed

+73
-15
lines changed

docs/bitset.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ platform.
1515
* Stream input and output operators are not implemented.
1616
* A `std::hash` specialization is not implemented.
1717
* `to_string`, `to_ulong` and `to_ullong` are not implemented
18+
* `operator[]` is read-only: it does not return a proxy reference type
1819

1920
A bitset has two template parameters: the size of the bitset and the storage
2021
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)
7172

7273
Bitsets support all the usual bitwise operators (`and`, `or`, `xor` and `not`)
7374
and also support `operator-` meaning set difference, or `a & ~b`.
75+
76+
A bitset can also be used with an enumeration that represents bits:
77+
[source,cpp]
78+
----
79+
enum struct Bits { ZERO, ONE, TWO, THREE, MAX };
80+
auto bs = stdx::bitset<Bits::MAX>{stdx::all_bits}; // 4 bits, value 0b1111
81+
bs.set(Bits::ZERO);
82+
bs.reset(Bits::ZERO);
83+
bs.flip(Bits::ZERO);
84+
auto bit_zero = bs[Bits::ZERO];
85+
----
86+
87+
NOTE: The enumeration values are the bit positions, not the bits themselves (the
88+
enumeration values are not fixed to powers-of-2).

include/stdx/bitset.hpp

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,20 +156,21 @@ template <std::size_t N, typename StorageElem> class bitset {
156156
}
157157

158158
template <typename T> [[nodiscard]] constexpr auto to() const -> T {
159-
static_assert(unsigned_integral<T>,
160-
"Conversion must be to an unsigned integral type!");
161-
static_assert(N <= std::numeric_limits<T>::digits,
159+
using U = underlying_type_t<T>;
160+
static_assert(
161+
unsigned_integral<U>,
162+
"Conversion must be to an unsigned integral type or enum!");
163+
static_assert(N <= std::numeric_limits<U>::digits,
162164
"Bitset too big for conversion to T");
163-
if constexpr (std::is_same_v<StorageElem, T>) {
164-
return storage[0] & lastmask;
165+
if constexpr (std::is_same_v<StorageElem, U>) {
166+
return static_cast<T>(storage[0] & lastmask);
165167
} else {
166-
167-
T result{highbits()};
168+
U result{highbits()};
168169
for (auto i = storage_size - 2u; i < storage_size; --i) {
169170
result = static_cast<T>(result << storage_elem_size);
170171
result |= storage[i];
171172
}
172-
return result;
173+
return static_cast<T>(result);
173174
}
174175
}
175176

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

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

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

190-
constexpr auto set(std::size_t pos,
191-
bool value = true) LIFETIMEBOUND -> bitset & {
193+
template <typename T>
194+
constexpr auto set(T idx, bool value = true) LIFETIMEBOUND -> bitset & {
195+
auto const pos = static_cast<std::size_t>(to_underlying(idx));
192196
auto const [index, offset] = indices(pos);
193197
if (value) {
194198
storage[index] |= static_cast<StorageElem>(bit << offset);
@@ -241,7 +245,9 @@ template <std::size_t N, typename StorageElem> class bitset {
241245
return *this;
242246
}
243247

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

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

409-
template <std::size_t N, typename StorageElem = void>
410-
using bitset = detail::bitset<N, decltype(smallest_uint<N, StorageElem>())>;
416+
template <auto N, typename StorageElem = void>
417+
using bitset =
418+
detail::bitset<to_underlying(N),
419+
decltype(smallest_uint<to_underlying(N), StorageElem>())>;
411420

412421
} // namespace v1
413422
} // namespace stdx

include/stdx/type_traits.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ template <typename E> constexpr auto to_underlying(E e) noexcept {
1313
return e;
1414
}
1515
}
16+
template <typename E>
17+
using underlying_type_t = decltype(to_underlying(std::declval<E>()));
1618

1719
template <typename T> struct remove_cvref {
1820
using type = std::remove_cv_t<std::remove_reference_t<T>>;

test/bitset.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,35 @@ TEMPLATE_TEST_CASE("find lowest unset bit (full)", "[bitset]", std::uint8_t,
422422
constexpr auto bs = stdx::bitset<sz, TestType>{stdx::all_bits};
423423
static_assert(bs.lowest_unset() == sz);
424424
}
425+
426+
namespace {
427+
enum struct Bits : std::uint8_t { ZERO, ONE, TWO, THREE, MAX };
428+
}
429+
430+
TEST_CASE("use bitset with enum struct (construct)", "[bitset]") {
431+
constexpr auto bs = stdx::bitset<Bits::MAX>{};
432+
static_assert(bs.size() == stdx::to_underlying(Bits::MAX));
433+
}
434+
435+
TEST_CASE("use bitset with enum struct (to)", "[bitset]") {
436+
constexpr auto bs = stdx::bitset<Bits::MAX>{stdx::all_bits};
437+
static_assert(bs.to<Bits>() == static_cast<Bits>(0b1111));
438+
}
439+
440+
TEST_CASE("use bitset with enum struct (set/flip)", "[bitset]") {
441+
auto bs = stdx::bitset<Bits::MAX>{};
442+
bs.set(Bits::ZERO);
443+
CHECK(bs.to_natural() == 1);
444+
bs.reset(Bits::ZERO);
445+
CHECK(bs.to_natural() == 0);
446+
bs.flip(Bits::ZERO);
447+
CHECK(bs.to_natural() == 1);
448+
}
449+
450+
TEST_CASE("use bitset with enum struct (read index)", "[bitset]") {
451+
constexpr auto bs = stdx::bitset<Bits::MAX>{stdx::all_bits};
452+
static_assert(bs[Bits::ZERO]);
453+
static_assert(bs[Bits::ONE]);
454+
static_assert(bs[Bits::TWO]);
455+
static_assert(bs[Bits::THREE]);
456+
}

0 commit comments

Comments
 (0)