Skip to content

Commit a1c783d

Browse files
authored
Merge pull request #211 from elbeno/bit-mask-array
🎨 Enable `bit_mask` with array types
2 parents ea55684 + 204b648 commit a1c783d

File tree

3 files changed

+116
-22
lines changed

3 files changed

+116
-22
lines changed

docs/bit.adoc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ static_assert(z == std::uint8_t{0b1111'1111});
2626
----
2727

2828
`Msb` and `Lsb` denote a closed (inclusive) range where `Msb >= Lsb`. The first
29-
template argument must be an unsigned integral type.
29+
template argument must be an unsigned integral type or a `std::array` of
30+
unsigned integral types. In the case of an array, the elements are considered to
31+
be in order least significant to most significant.
32+
33+
[source,cpp]
34+
----
35+
constexpr auto x = stdx::bit_mask<std::array<std::uint8_t, 3>, 19>>();
36+
// x == { 0xff, 0xff, 0x0f }
37+
----
3038

3139
`bit_mask` is also available for use with "normal" value arguments rather than
3240
template arguments:

include/stdx/bit.hpp

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <stdx/compiler.hpp>
34
#include <stdx/concepts.hpp>
45
#include <stdx/type_traits.hpp>
56
#include <stdx/utility.hpp>
@@ -340,37 +341,78 @@ template <typename To, typename From> constexpr auto bit_unpack(From arg) {
340341
}
341342

342343
namespace detail {
343-
template <typename T, std::size_t Bit>
344-
constexpr auto mask_bits()
345-
-> std::enable_if_t<Bit <= std::numeric_limits<T>::digits, T> {
346-
if constexpr (Bit == std::numeric_limits<T>::digits) {
347-
return std::numeric_limits<T>::max();
348-
} else {
349-
return static_cast<T>(T{1} << Bit) - T{1};
344+
template <typename T> struct num_digits_t {
345+
constexpr static std::size_t value = std::numeric_limits<T>::digits;
346+
};
347+
template <typename T> constexpr auto num_digits_v = num_digits_t<T>::value;
348+
template <typename T, std::size_t N> struct num_digits_t<std::array<T, N>> {
349+
constexpr static std::size_t value = (N * num_digits_v<T>);
350+
};
351+
352+
template <typename T> struct mask_bits_t {
353+
static_assert(std::is_unsigned_v<T>,
354+
"bit_mask must be used with unsigned types");
355+
356+
constexpr auto operator()(std::size_t bit) const -> T {
357+
if (bit == num_digits_v<T>) {
358+
return std::numeric_limits<T>::max();
359+
}
360+
return static_cast<T>(T{1} << bit) - T{1};
350361
}
351-
}
362+
};
352363

353-
template <typename T> constexpr auto mask_bits(std::size_t Bit) -> T {
354-
if (Bit == std::numeric_limits<T>::digits) {
355-
return std::numeric_limits<T>::max();
364+
template <typename T, std::size_t N> struct mask_bits_t<std::array<T, N>> {
365+
constexpr auto operator()(std::size_t bit) const -> std::array<T, N> {
366+
constexpr auto t_bits = num_digits_v<T>;
367+
auto const quot = bit / t_bits;
368+
auto const rem = bit % t_bits;
369+
370+
std::array<T, N> r{};
371+
T *p = std::data(r);
372+
for (auto i = std::size_t{}; i < quot; ++i) {
373+
*p++ = mask_bits_t<T>{}(t_bits);
374+
}
375+
if (rem != 0) {
376+
*p = mask_bits_t<T>{}(rem);
377+
}
378+
return r;
356379
}
357-
return static_cast<T>(T{1} << Bit) - T{1};
358-
}
380+
};
381+
382+
template <typename T> struct bitmask_subtract {
383+
static_assert(std::is_unsigned_v<T>,
384+
"bit_mask must be used with unsigned types");
385+
constexpr auto operator()(T x, T y) const -> T { return x ^ y; }
386+
};
387+
388+
template <typename T, std::size_t N> struct bitmask_subtract<std::array<T, N>> {
389+
constexpr auto operator()(std::array<T, N> const &x,
390+
std::array<T, N> const &y) const
391+
-> std::array<T, N> {
392+
std::array<T, N> r{};
393+
for (auto i = std::size_t{}; i < N; ++i) {
394+
r[i] = bitmask_subtract<T>{}(x[i], y[i]);
395+
}
396+
return r;
397+
}
398+
};
359399
} // namespace detail
360400

361-
template <typename T, std::size_t Msb = std::numeric_limits<T>::digits - 1,
401+
template <typename T, std::size_t Msb = detail::num_digits_v<T> - 1,
362402
std::size_t Lsb = 0>
363-
[[nodiscard]] constexpr auto bit_mask() noexcept
364-
-> std::enable_if_t<std::is_unsigned_v<T> and Msb >= Lsb, T> {
365-
static_assert(Msb < std::numeric_limits<T>::digits);
366-
return detail::mask_bits<T, Msb + 1>() - detail::mask_bits<T, Lsb>();
403+
[[nodiscard]] CONSTEVAL auto bit_mask() noexcept -> T {
404+
static_assert(Msb < detail::num_digits_v<T>,
405+
"bit_mask requested exceeds the range of the type");
406+
static_assert(Msb >= Lsb, "bit_mask range is invalid");
407+
return detail::bitmask_subtract<T>{}(detail::mask_bits_t<T>{}(Msb + 1),
408+
detail::mask_bits_t<T>{}(Lsb));
367409
}
368410

369411
template <typename T>
370412
[[nodiscard]] constexpr auto bit_mask(std::size_t Msb,
371-
std::size_t Lsb = 0) noexcept
372-
-> std::enable_if_t<std::is_unsigned_v<T>, T> {
373-
return detail::mask_bits<T>(Msb + 1) - detail::mask_bits<T>(Lsb);
413+
std::size_t Lsb = 0) noexcept -> T {
414+
return detail::bitmask_subtract<T>{}(detail::mask_bits_t<T>{}(Msb + 1),
415+
detail::mask_bits_t<T>{}(Lsb));
374416
}
375417

376418
template <typename T> constexpr auto bit_size() -> std::size_t {

test/bit.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <catch2/catch_template_test_macros.hpp>
44
#include <catch2/catch_test_macros.hpp>
55

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

253+
TEST_CASE("template bit_mask (array type whole range)", "[bit]") {
254+
using A = std::array<std::uint8_t, 3>;
255+
constexpr auto m = stdx::bit_mask<A>();
256+
CHECK(m == A{0xff, 0xff, 0xff});
257+
}
258+
259+
TEST_CASE("template bit_mask (array type low bits)", "[bit]") {
260+
using A = std::array<std::uint8_t, 3>;
261+
constexpr auto m = stdx::bit_mask<A, 1, 0>();
262+
CHECK(m == A{0b0000'0011, 0, 0});
263+
}
264+
265+
TEST_CASE("template bit_mask (array type mid bits)", "[bit]") {
266+
using A = std::array<std::uint8_t, 3>;
267+
constexpr auto m = stdx::bit_mask<A, 19, 4>();
268+
CHECK(m == A{0b1111'0000, 0xff, 0b0000'1111});
269+
}
270+
271+
TEST_CASE("template bit_mask (array type high bits)", "[bit]") {
272+
using A = std::array<std::uint8_t, 3>;
273+
constexpr auto m = stdx::bit_mask<A, 23, 20>();
274+
CHECK(m == A{0, 0, 0b1111'0000});
275+
}
276+
277+
TEST_CASE("template bit_mask (array type single bit)", "[bit]") {
278+
using A = std::array<std::uint8_t, 3>;
279+
constexpr auto m = stdx::bit_mask<A, 5, 5>();
280+
CHECK(m == A{0b0010'0000, 0, 0});
281+
}
282+
283+
TEST_CASE("template bit_mask (array of array type)", "[bit]") {
284+
using A = std::array<std::uint8_t, 1>;
285+
using B = std::array<A, 3>;
286+
constexpr auto m = stdx::bit_mask<B, 19, 4>();
287+
CHECK(m == B{A{0b1111'0000}, A{0xff}, A{0b0000'1111}});
288+
}
289+
290+
TEST_CASE("template bit_mask (large array type)", "[bit]") {
291+
using A = std::array<std::uint64_t, 4>;
292+
constexpr auto m = stdx::bit_mask<A, 192, 192>();
293+
CHECK(m == A{0, 0, 0, 1});
294+
}
295+
252296
TEST_CASE("arg bit_mask (whole range)", "[bit]") {
253297
constexpr auto m = stdx::bit_mask<std::uint64_t>(63);
254298
static_assert(m == std::numeric_limits<std::uint64_t>::max());

0 commit comments

Comments
 (0)