From 651f86e957595f5a19db161a773688bb089ac1f3 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Thu, 3 Sep 2026 16:03:52 -0600 Subject: [PATCH] :art: Expose `for_each` and `transform_reduce` as `bitset` memfns Problem: - Having `for_each` in particular as a free function on `bitset` increases the work the compiler has to do in overload resolution, concept checking etc. Solution: - Provide `for_each` and `transform_reduce` as member functions on `bitset`. Note: - `for_each` is already a member function on `type_bitset`. --- docs/bitset.adoc | 3 +++ include/stdx/bitset.hpp | 56 ++++++++++++++++++++--------------------- test/bitset.cpp | 22 ++++++++++++++++ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/docs/bitset.adoc b/docs/bitset.adoc index de28fe0..d3ae08f 100644 --- a/docs/bitset.adoc +++ b/docs/bitset.adoc @@ -125,6 +125,9 @@ auto result = transform_reduce([](auto i) { return i * 2 }, // result is 1*2 + 3*2 + 5*2 + 7*2 ---- +NOTE: Both `for_each` and `transform_reduce` are also member functions on +`bitset`, as well as free functions. + === `type_bitset` A `type_bitset` is much the same as a `bitset`, except that it is indexed by types. diff --git a/include/stdx/bitset.hpp b/include/stdx/bitset.hpp index a48b7d9..0fc78c0 100644 --- a/include/stdx/bitset.hpp +++ b/include/stdx/bitset.hpp @@ -145,37 +145,9 @@ class bitset { return not std::is_enum_v or std::is_same_v; } - template - constexpr auto for_each(F &&f) const -> F { - std::size_t idx = 0; - for (auto i = std::size_t{}; i < storage_size - 1; ++i) { - Spec::template fn::max()>(storage[i], - idx, f); - idx += std::numeric_limits::digits; - } - Spec::template fn(highbits(), idx, f); - return std::forward(f); - } - template friend constexpr auto for_each(F &&f, bitset const &...bs) -> F; - template - constexpr auto transform_reduce(F &&f, R &&r, T init) const -> T { - std::size_t i = 0; - for (auto e : storage) { - while (e != 0) { - auto const offset = static_cast(countr_zero(e)); - e &= static_cast(~(bit << offset)); - init = - r(std::move(init), f(static_cast(i + offset))); - } - i += std::numeric_limits::digits; - } - return init; - } - template friend constexpr auto transform_reduce(F &&f, R &&r, T init, bitset const &...bs) -> T; @@ -494,6 +466,34 @@ class bitset { } return *this; } + + template + constexpr auto for_each(F &&f) const -> F { + std::size_t idx = 0; + for (auto i = std::size_t{}; i < storage_size - 1; ++i) { + Spec::template fn::max()>(storage[i], + idx, f); + idx += std::numeric_limits::digits; + } + Spec::template fn(highbits(), idx, f); + return std::forward(f); + } + + template + constexpr auto transform_reduce(F &&f, R &&r, T init) const -> T { + std::size_t i = 0; + for (auto e : storage) { + while (e != 0) { + auto const offset = static_cast(countr_zero(e)); + e &= static_cast(~(bit << offset)); + init = + r(std::move(init), f(static_cast(i + offset))); + } + i += std::numeric_limits::digits; + } + return init; + } }; template diff --git a/test/bitset.cpp b/test/bitset.cpp index ebe13c2..86175d0 100644 --- a/test/bitset.cpp +++ b/test/bitset.cpp @@ -350,6 +350,14 @@ TEMPLATE_TEST_CASE("for_each", "[bitset]", std::uint8_t, std::uint16_t, CHECK(result == bs); } +TEMPLATE_TEST_CASE("member for_each", "[bitset]", std::uint8_t, std::uint16_t, + std::uint32_t, std::uint64_t) { + constexpr auto bs = stdx::bitset<64, TestType>{0x01020304'05060708ul}; + auto result = decltype(bs){}; + bs.for_each([&](auto i) { result.set(i); }); + CHECK(result == bs); +} + TEMPLATE_TEST_CASE("for_each (unset bits)", "[bitset]", std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t) { constexpr auto bs = stdx::bitset<64, TestType>{0x01020304'05060708ul}; @@ -396,6 +404,20 @@ TEMPLATE_TEST_CASE("transform_reduce", "[bitset]", std::uint8_t, std::uint16_t, CHECK(calls == bs.count()); } +TEMPLATE_TEST_CASE("member transform_reduce", "[bitset]", std::uint8_t, + std::uint16_t, std::uint32_t, std::uint64_t) { + constexpr auto bs = stdx::bitset<8, TestType>{0b10101010ul}; + int calls{}; + auto const result = bs.transform_reduce( + [&](auto i) { + ++calls; + return i == 3 and calls == 2; + }, + std::logical_or{}, false); + CHECK(result); + CHECK(calls == bs.count()); +} + TEMPLATE_TEST_CASE("set range of bits (lsb, length)", "[bitset]", std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t) { using namespace stdx::literals;