Skip to content

Commit 49c5763

Browse files
authored
Merge pull request #409 from elbeno/for-each-unset-bitset
✨ Allow `bitset` to iterate unset bits
2 parents 9969240 + 75237ca commit 49c5763

4 files changed

Lines changed: 87 additions & 14 deletions

File tree

docs/bitset.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ auto bs = stdx::bitset<8>{0b1010'1010ul};
101101
for_each([&](auto i) { /* i == 1, 3, 5, 7 */ }, bs);
102102
----
103103

104+
To iterate all the unset bits, use:
105+
[source,cpp]
106+
----
107+
auto bs = stdx::bitset<8>{0b1010'1010ul};
108+
for_each<stdx::unset_bit>([&](auto i) { /* i == 0, 2, 4, 6 */ }, bs);
109+
----
110+
104111
To support "external" iteration, or use cases like using a bitset to track used
105112
objects, `lowest_unset` is also provided:
106113
[source,cpp]

include/stdx/bitset.hpp

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@
2121

2222
namespace stdx {
2323
inline namespace v1 {
24+
struct set_bit {
25+
template <auto Bit, typename IterArg, auto Mask>
26+
constexpr static auto fn(auto e, auto idx, auto &f) {
27+
using elem_t = decltype(Bit);
28+
while (e != 0) {
29+
auto const offset = static_cast<std::size_t>(countr_zero(e));
30+
e &= static_cast<elem_t>(~(Bit << offset));
31+
f(static_cast<IterArg>(idx + offset));
32+
}
33+
}
34+
};
35+
struct unset_bit {
36+
template <auto Bit, typename IterArg, auto Mask>
37+
constexpr static auto fn(auto e, auto idx, auto &f) {
38+
using elem_t = decltype(Bit);
39+
while (e != Mask) {
40+
auto const offset = static_cast<std::size_t>(countr_one(e));
41+
e |= static_cast<elem_t>(Bit << offset);
42+
f(static_cast<IterArg>(idx + offset));
43+
}
44+
}
45+
};
46+
struct bit {
47+
template <auto Bit, typename IterArg, auto Mask>
48+
constexpr static auto fn(auto e, auto idx, auto &f) {
49+
using elem_t = decltype(Bit);
50+
for (auto i = std::size_t{}; i < popcount(Mask); ++i) {
51+
bool b = e & static_cast<elem_t>(Bit << i);
52+
f(static_cast<IterArg>(idx + i), b);
53+
}
54+
}
55+
};
56+
57+
namespace detail {
58+
template <typename T>
59+
concept bit_spec = std::same_as<T, set_bit> or std::same_as<T, unset_bit> or
60+
std::same_as<T, bit>;
61+
}
62+
2463
template <auto Size,
2564
typename StorageElem = decltype(smallest_uint<to_underlying(Size)>())>
2665
class bitset {
@@ -106,20 +145,20 @@ class bitset {
106145
return not std::is_enum_v<T> or std::is_same_v<T, decltype(Size)>;
107146
}
108147

109-
template <typename F> constexpr auto for_each(F &&f) const -> F {
110-
std::size_t i = 0;
111-
for (auto e : storage) {
112-
while (e != 0) {
113-
auto const offset = static_cast<std::size_t>(countr_zero(e));
114-
e &= static_cast<elem_t>(~(bit << offset));
115-
f(static_cast<iter_arg_t>(i + offset));
116-
}
117-
i += std::numeric_limits<elem_t>::digits;
148+
template <detail::bit_spec Spec, typename F>
149+
constexpr auto for_each(F &&f) const -> F {
150+
std::size_t idx = 0;
151+
for (auto i = std::size_t{}; i < storage_size - 1; ++i) {
152+
Spec::template fn<bit, iter_arg_t,
153+
std::numeric_limits<elem_t>::max()>(storage[i],
154+
idx, f);
155+
idx += std::numeric_limits<elem_t>::digits;
118156
}
157+
Spec::template fn<bit, iter_arg_t, lastmask>(highbits(), idx, f);
119158
return std::forward<F>(f);
120159
}
121160

122-
template <typename F, auto M, typename... S>
161+
template <detail::bit_spec Spec, typename F, auto M, typename... S>
123162
friend constexpr auto for_each(F &&f, bitset<M, S> const &...bs) -> F;
124163

125164
template <typename T, typename F, typename R>
@@ -457,10 +496,10 @@ class bitset {
457496
}
458497
};
459498

460-
template <typename F, auto M, typename... S>
499+
template <detail::bit_spec Spec = set_bit, typename F, auto M, typename... S>
461500
constexpr auto for_each(F &&f, bitset<M, S> const &...bs) -> F {
462501
if constexpr (sizeof...(bs) == 1) {
463-
return (bs.for_each(std::forward<F>(f)), ...);
502+
return (bs.template for_each<Spec>(std::forward<F>(f)), ...);
464503
} else {
465504
static_assert(stdx::always_false_v<F>, "unimplemented");
466505
return f;
@@ -648,9 +687,10 @@ template <typename... Ts> class type_bitset {
648687
return *this;
649688
}
650689

651-
template <typename F> constexpr auto for_each(F &&f) const -> F {
690+
template <detail::bit_spec Spec = set_bit, typename F>
691+
constexpr auto for_each(F &&f) const -> F {
652692
constexpr auto callers = make_callers<F>(std::make_index_sequence<N>{});
653-
stdx::for_each([&](auto i) { callers[i](f); }, bs);
693+
stdx::for_each<Spec>([&](auto i) { callers[i](f); }, bs);
654694
return f;
655695
}
656696
};

test/bitset.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,23 @@ TEMPLATE_TEST_CASE("for_each", "[bitset]", std::uint8_t, std::uint16_t,
350350
CHECK(result == bs);
351351
}
352352

353+
TEMPLATE_TEST_CASE("for_each (unset bits)", "[bitset]", std::uint8_t,
354+
std::uint16_t, std::uint32_t, std::uint64_t) {
355+
constexpr auto bs = stdx::bitset<64, TestType>{0x01020304'05060708ul};
356+
auto result = decltype(bs){};
357+
for_each<stdx::unset_bit>([&](auto i) { result.set(i); }, bs);
358+
CHECK(result.template to<std::uint64_t>() ==
359+
~bs.template to<std::uint64_t>());
360+
}
361+
362+
TEMPLATE_TEST_CASE("for_each (all bits)", "[bitset]", std::uint8_t,
363+
std::uint16_t, std::uint32_t, std::uint64_t) {
364+
constexpr auto bs = stdx::bitset<64, TestType>{0x01020304'05060708ul};
365+
auto result = decltype(bs){};
366+
for_each<stdx::bit>([&](auto i, bool b) { result.set(i, b); }, bs);
367+
CHECK(result == bs);
368+
}
369+
353370
TEMPLATE_TEST_CASE("for_each iterates in order lsb to msb", "[bitset]",
354371
std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t) {
355372
constexpr auto bs = stdx::bitset<5, TestType>{0b10101ul};

test/type_bitset.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,12 @@ TEST_CASE("for_each", "[type_bitset]") {
200200
});
201201
CHECK(result == "int0float1bool2");
202202
}
203+
204+
TEST_CASE("for_each (unset bits)", "[type_bitset]") {
205+
constexpr auto bs = stdx::type_bitset<int, float, bool>{};
206+
auto result = std::string{};
207+
bs.for_each<stdx::unset_bit>([&]<typename T, std::size_t I>() -> void {
208+
result += std::string{stdx::type_as_string<T>()} + std::to_string(I);
209+
});
210+
CHECK(result == "int0float1bool2");
211+
}

0 commit comments

Comments
 (0)