Skip to content

Commit d4f808b

Browse files
committed
🎨 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<std::array<std::uint8_t, 3>>()` 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.
1 parent ea55684 commit d4f808b

File tree

3 files changed

+111
-22
lines changed

3 files changed

+111
-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: 64 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,79 @@ 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+
} else {
360+
return static_cast<T>(T{1} << bit) - T{1};
361+
}
350362
}
351-
}
363+
};
352364

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

361-
template <typename T, std::size_t Msb = std::numeric_limits<T>::digits - 1,
402+
template <typename T, std::size_t Msb = detail::num_digits_v<T> - 1,
362403
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>();
404+
[[nodiscard]] CONSTEVAL auto bit_mask() noexcept -> T {
405+
static_assert(Msb < detail::num_digits_v<T>,
406+
"bit_mask requested exceeds the range of the type");
407+
static_assert(Msb >= Lsb, "bit_mask range is invalid");
408+
return detail::bitmask_subtract<T>{}(detail::mask_bits_t<T>{}(Msb + 1),
409+
detail::mask_bits_t<T>{}(Lsb));
367410
}
368411

369412
template <typename T>
370413
[[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);
414+
std::size_t Lsb = 0) noexcept -> T {
415+
return detail::bitmask_subtract<T>{}(detail::mask_bits_t<T>{}(Msb + 1),
416+
detail::mask_bits_t<T>{}(Lsb));
374417
}
375418

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

test/bit.cpp

Lines changed: 38 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,43 @@ 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+
252290
TEST_CASE("arg bit_mask (whole range)", "[bit]") {
253291
constexpr auto m = stdx::bit_mask<std::uint64_t>(63);
254292
static_assert(m == std::numeric_limits<std::uint64_t>::max());

0 commit comments

Comments
 (0)