Skip to content

🎨 Enable bit_mask with array types #211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/bit.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::array<std::uint8_t, 3>, 19>>();
// x == { 0xff, 0xff, 0x0f }
----

`bit_mask` is also available for use with "normal" value arguments rather than
template arguments:
Expand Down
84 changes: 63 additions & 21 deletions include/stdx/bit.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <stdx/compiler.hpp>
#include <stdx/concepts.hpp>
#include <stdx/type_traits.hpp>
#include <stdx/utility.hpp>
Expand Down Expand Up @@ -340,37 +341,78 @@ template <typename To, typename From> constexpr auto bit_unpack(From arg) {
}

namespace detail {
template <typename T, std::size_t Bit>
constexpr auto mask_bits()
-> std::enable_if_t<Bit <= std::numeric_limits<T>::digits, T> {
if constexpr (Bit == std::numeric_limits<T>::digits) {
return std::numeric_limits<T>::max();
} else {
return static_cast<T>(T{1} << Bit) - T{1};
template <typename T> struct num_digits_t {
constexpr static std::size_t value = std::numeric_limits<T>::digits;
};
template <typename T> constexpr auto num_digits_v = num_digits_t<T>::value;
template <typename T, std::size_t N> struct num_digits_t<std::array<T, N>> {
constexpr static std::size_t value = (N * num_digits_v<T>);
};

template <typename T> struct mask_bits_t {
static_assert(std::is_unsigned_v<T>,
"bit_mask must be used with unsigned types");

constexpr auto operator()(std::size_t bit) const -> T {
if (bit == num_digits_v<T>) {
return std::numeric_limits<T>::max();
}
return static_cast<T>(T{1} << bit) - T{1};
}
}
};

template <typename T> constexpr auto mask_bits(std::size_t Bit) -> T {
if (Bit == std::numeric_limits<T>::digits) {
return std::numeric_limits<T>::max();
template <typename T, std::size_t N> struct mask_bits_t<std::array<T, N>> {
constexpr auto operator()(std::size_t bit) const -> std::array<T, N> {
constexpr auto t_bits = num_digits_v<T>;
auto const quot = bit / t_bits;
auto const rem = bit % t_bits;

std::array<T, N> r{};
T *p = std::data(r);
for (auto i = std::size_t{}; i < quot; ++i) {
*p++ = mask_bits_t<T>{}(t_bits);
}
if (rem != 0) {
*p = mask_bits_t<T>{}(rem);
}
return r;
}
return static_cast<T>(T{1} << Bit) - T{1};
}
};

template <typename T> struct bitmask_subtract {
static_assert(std::is_unsigned_v<T>,
"bit_mask must be used with unsigned types");
constexpr auto operator()(T x, T y) const -> T { return x ^ y; }
};

template <typename T, std::size_t N> struct bitmask_subtract<std::array<T, N>> {
constexpr auto operator()(std::array<T, N> const &x,
std::array<T, N> const &y) const
-> std::array<T, N> {
std::array<T, N> r{};
for (auto i = std::size_t{}; i < N; ++i) {
r[i] = bitmask_subtract<T>{}(x[i], y[i]);
}
return r;
}
};
} // namespace detail

template <typename T, std::size_t Msb = std::numeric_limits<T>::digits - 1,
template <typename T, std::size_t Msb = detail::num_digits_v<T> - 1,
std::size_t Lsb = 0>
[[nodiscard]] constexpr auto bit_mask() noexcept
-> std::enable_if_t<std::is_unsigned_v<T> and Msb >= Lsb, T> {
static_assert(Msb < std::numeric_limits<T>::digits);
return detail::mask_bits<T, Msb + 1>() - detail::mask_bits<T, Lsb>();
[[nodiscard]] CONSTEVAL auto bit_mask() noexcept -> T {
static_assert(Msb < detail::num_digits_v<T>,
"bit_mask requested exceeds the range of the type");
static_assert(Msb >= Lsb, "bit_mask range is invalid");
return detail::bitmask_subtract<T>{}(detail::mask_bits_t<T>{}(Msb + 1),
detail::mask_bits_t<T>{}(Lsb));
}

template <typename T>
[[nodiscard]] constexpr auto bit_mask(std::size_t Msb,
std::size_t Lsb = 0) noexcept
-> std::enable_if_t<std::is_unsigned_v<T>, T> {
return detail::mask_bits<T>(Msb + 1) - detail::mask_bits<T>(Lsb);
std::size_t Lsb = 0) noexcept -> T {
return detail::bitmask_subtract<T>{}(detail::mask_bits_t<T>{}(Msb + 1),
detail::mask_bits_t<T>{}(Lsb));
}

template <typename T> constexpr auto bit_size() -> std::size_t {
Expand Down
44 changes: 44 additions & 0 deletions test/bit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>

#include <array>
#include <cstdint>
#include <limits>
#include <type_traits>
Expand Down Expand Up @@ -249,6 +250,49 @@ TEST_CASE("template bit_mask (single bit)", "[bit]") {
static_assert(std::is_same_v<decltype(m), std::uint8_t const>);
}

TEST_CASE("template bit_mask (array type whole range)", "[bit]") {
using A = std::array<std::uint8_t, 3>;
constexpr auto m = stdx::bit_mask<A>();
CHECK(m == A{0xff, 0xff, 0xff});
}

TEST_CASE("template bit_mask (array type low bits)", "[bit]") {
using A = std::array<std::uint8_t, 3>;
constexpr auto m = stdx::bit_mask<A, 1, 0>();
CHECK(m == A{0b0000'0011, 0, 0});
}

TEST_CASE("template bit_mask (array type mid bits)", "[bit]") {
using A = std::array<std::uint8_t, 3>;
constexpr auto m = stdx::bit_mask<A, 19, 4>();
CHECK(m == A{0b1111'0000, 0xff, 0b0000'1111});
}

TEST_CASE("template bit_mask (array type high bits)", "[bit]") {
using A = std::array<std::uint8_t, 3>;
constexpr auto m = stdx::bit_mask<A, 23, 20>();
CHECK(m == A{0, 0, 0b1111'0000});
}

TEST_CASE("template bit_mask (array type single bit)", "[bit]") {
using A = std::array<std::uint8_t, 3>;
constexpr auto m = stdx::bit_mask<A, 5, 5>();
CHECK(m == A{0b0010'0000, 0, 0});
}

TEST_CASE("template bit_mask (array of array type)", "[bit]") {
using A = std::array<std::uint8_t, 1>;
using B = std::array<A, 3>;
constexpr auto m = stdx::bit_mask<B, 19, 4>();
CHECK(m == B{A{0b1111'0000}, A{0xff}, A{0b0000'1111}});
}

TEST_CASE("template bit_mask (large array type)", "[bit]") {
using A = std::array<std::uint64_t, 4>;
constexpr auto m = stdx::bit_mask<A, 192, 192>();
CHECK(m == A{0, 0, 0, 1});
}

TEST_CASE("arg bit_mask (whole range)", "[bit]") {
constexpr auto m = stdx::bit_mask<std::uint64_t>(63);
static_assert(m == std::numeric_limits<std::uint64_t>::max());
Expand Down