From 204b6485cca2eb55b2da5fb0d0f5c0aac3177979 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Fri, 21 Mar 2025 17:02:21 -0600 Subject: [PATCH] :art: Enable `bit_mask` with array types Problem: - There is no way to ask for a bit mask in a `std::array`, such that e.g. `bit_mask>()` has all 24 bits set. Solution: - Make `bit_mask` able to return a `std::array`. Note: - This is a building block for array fields in messages among other things. --- docs/bit.adoc | 10 +++++- include/stdx/bit.hpp | 84 +++++++++++++++++++++++++++++++++----------- test/bit.cpp | 44 +++++++++++++++++++++++ 3 files changed, 116 insertions(+), 22 deletions(-) diff --git a/docs/bit.adoc b/docs/bit.adoc index 42dcbd7..2b7205e 100644 --- a/docs/bit.adoc +++ b/docs/bit.adoc @@ -26,7 +26,15 @@ static_assert(z == std::uint8_t{0b1111'1111}); ---- `Msb` and `Lsb` denote a closed (inclusive) range where `Msb >= Lsb`. The first -template argument must be an unsigned integral type. +template argument must be an unsigned integral type or a `std::array` of +unsigned integral types. In the case of an array, the elements are considered to +be in order least significant to most significant. + +[source,cpp] +---- +constexpr auto x = stdx::bit_mask, 19>>(); +// x == { 0xff, 0xff, 0x0f } +---- `bit_mask` is also available for use with "normal" value arguments rather than template arguments: diff --git a/include/stdx/bit.hpp b/include/stdx/bit.hpp index 9e8af51..657f555 100644 --- a/include/stdx/bit.hpp +++ b/include/stdx/bit.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -340,37 +341,78 @@ template constexpr auto bit_unpack(From arg) { } namespace detail { -template -constexpr auto mask_bits() - -> std::enable_if_t::digits, T> { - if constexpr (Bit == std::numeric_limits::digits) { - return std::numeric_limits::max(); - } else { - return static_cast(T{1} << Bit) - T{1}; +template struct num_digits_t { + constexpr static std::size_t value = std::numeric_limits::digits; +}; +template constexpr auto num_digits_v = num_digits_t::value; +template struct num_digits_t> { + constexpr static std::size_t value = (N * num_digits_v); +}; + +template struct mask_bits_t { + static_assert(std::is_unsigned_v, + "bit_mask must be used with unsigned types"); + + constexpr auto operator()(std::size_t bit) const -> T { + if (bit == num_digits_v) { + return std::numeric_limits::max(); + } + return static_cast(T{1} << bit) - T{1}; } -} +}; -template constexpr auto mask_bits(std::size_t Bit) -> T { - if (Bit == std::numeric_limits::digits) { - return std::numeric_limits::max(); +template struct mask_bits_t> { + constexpr auto operator()(std::size_t bit) const -> std::array { + constexpr auto t_bits = num_digits_v; + auto const quot = bit / t_bits; + auto const rem = bit % t_bits; + + std::array r{}; + T *p = std::data(r); + for (auto i = std::size_t{}; i < quot; ++i) { + *p++ = mask_bits_t{}(t_bits); + } + if (rem != 0) { + *p = mask_bits_t{}(rem); + } + return r; } - return static_cast(T{1} << Bit) - T{1}; -} +}; + +template struct bitmask_subtract { + static_assert(std::is_unsigned_v, + "bit_mask must be used with unsigned types"); + constexpr auto operator()(T x, T y) const -> T { return x ^ y; } +}; + +template struct bitmask_subtract> { + constexpr auto operator()(std::array const &x, + std::array const &y) const + -> std::array { + std::array r{}; + for (auto i = std::size_t{}; i < N; ++i) { + r[i] = bitmask_subtract{}(x[i], y[i]); + } + return r; + } +}; } // namespace detail -template ::digits - 1, +template - 1, std::size_t Lsb = 0> -[[nodiscard]] constexpr auto bit_mask() noexcept - -> std::enable_if_t and Msb >= Lsb, T> { - static_assert(Msb < std::numeric_limits::digits); - return detail::mask_bits() - detail::mask_bits(); +[[nodiscard]] CONSTEVAL auto bit_mask() noexcept -> T { + static_assert(Msb < detail::num_digits_v, + "bit_mask requested exceeds the range of the type"); + static_assert(Msb >= Lsb, "bit_mask range is invalid"); + return detail::bitmask_subtract{}(detail::mask_bits_t{}(Msb + 1), + detail::mask_bits_t{}(Lsb)); } template [[nodiscard]] constexpr auto bit_mask(std::size_t Msb, - std::size_t Lsb = 0) noexcept - -> std::enable_if_t, T> { - return detail::mask_bits(Msb + 1) - detail::mask_bits(Lsb); + std::size_t Lsb = 0) noexcept -> T { + return detail::bitmask_subtract{}(detail::mask_bits_t{}(Msb + 1), + detail::mask_bits_t{}(Lsb)); } template constexpr auto bit_size() -> std::size_t { diff --git a/test/bit.cpp b/test/bit.cpp index 4680e39..53d14ea 100644 --- a/test/bit.cpp +++ b/test/bit.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -249,6 +250,49 @@ TEST_CASE("template bit_mask (single bit)", "[bit]") { static_assert(std::is_same_v); } +TEST_CASE("template bit_mask (array type whole range)", "[bit]") { + using A = std::array; + constexpr auto m = stdx::bit_mask(); + CHECK(m == A{0xff, 0xff, 0xff}); +} + +TEST_CASE("template bit_mask (array type low bits)", "[bit]") { + using A = std::array; + constexpr auto m = stdx::bit_mask(); + CHECK(m == A{0b0000'0011, 0, 0}); +} + +TEST_CASE("template bit_mask (array type mid bits)", "[bit]") { + using A = std::array; + constexpr auto m = stdx::bit_mask(); + CHECK(m == A{0b1111'0000, 0xff, 0b0000'1111}); +} + +TEST_CASE("template bit_mask (array type high bits)", "[bit]") { + using A = std::array; + constexpr auto m = stdx::bit_mask(); + CHECK(m == A{0, 0, 0b1111'0000}); +} + +TEST_CASE("template bit_mask (array type single bit)", "[bit]") { + using A = std::array; + constexpr auto m = stdx::bit_mask(); + CHECK(m == A{0b0010'0000, 0, 0}); +} + +TEST_CASE("template bit_mask (array of array type)", "[bit]") { + using A = std::array; + using B = std::array; + constexpr auto m = stdx::bit_mask(); + CHECK(m == B{A{0b1111'0000}, A{0xff}, A{0b0000'1111}}); +} + +TEST_CASE("template bit_mask (large array type)", "[bit]") { + using A = std::array; + constexpr auto m = stdx::bit_mask(); + CHECK(m == A{0, 0, 0, 1}); +} + TEST_CASE("arg bit_mask (whole range)", "[bit]") { constexpr auto m = stdx::bit_mask(63); static_assert(m == std::numeric_limits::max());