Skip to content

Commit c946ce1

Browse files
committed
✨ Add bit_destructure
Problem: - Manual bit-shifting just to split a value into e.g. two pieces is still a thing. e.g. `auto x = val & 0x3; auto y = val >> 2;` Solution: - Introduce `bit_destructure`.
1 parent 59ec5eb commit c946ce1

File tree

3 files changed

+78
-2
lines changed

3 files changed

+78
-2
lines changed

docs/bit.adoc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@ provides an implementation that mirrors
66
https://en.cppreference.com/w/cpp/header/bit[`<bit>`], but is
77
`constexpr` in C++17. It is mostly based on intrinsics.
88

9+
=== `bit_destructure`
10+
11+
`bit_destructure` is a function for unpacking an unsigned integral value into multiple
12+
smaller bit width values. It is a more general case of xref:bit.adoc#_bit_unpack[`bit_unpack`].
13+
14+
[source,cpp]
15+
----
16+
auto const [a, b, c] = stdx::bit_unpack<8, 24>(0x1234'5678u);
17+
assert(a == 0x78u); // bits [0-8)
18+
assert(b == 0x3456u); // bits [8-24)
19+
assert(c == 0x12u); // bits [24-32)
20+
----
21+
22+
The `value_type` of the returned array is the same as the type of the value that
23+
is unpacked.
24+
25+
NOTE: Unlike `bit_unpack`, `bit_destructure` returns the elements in
26+
_increasing_ order of significance, and the template arguments representing the
27+
split boundaries must be in increasing order.
28+
929
=== `bit_mask`
1030

1131
`bit_mask` is a function for constructing a bit mask between most-significant
@@ -71,7 +91,7 @@ static_assert(y == x);
7191
- to pack 4 `std::uint16_t`​s into a `std::uint64_t`
7292
- to pack 8 `std::uint8_t`​s into a `std::uint64_t`
7393

74-
The arguments are listed in order of significance, i.e. for the binary
94+
The arguments are listed in order of _decreasing_ significance, i.e. for the binary
7595
overloads, the first argument is the high bits, and the second argument the low
7696
bits.
7797

@@ -109,7 +129,7 @@ assert(b == 0x5678u);
109129
- to unpack a `std::uint64_t` into 8 `std::uint8_t`​s
110130

111131
The return value of `bit_unpack` is actually a `std::array` with elements in
112-
order of significance. In this way `bit_unpack` followed by `bit_pack` produces
132+
order of _decreasing_ significance. In this way `bit_unpack` followed by `bit_pack` produces
113133
the original value.
114134

115135
[source,cpp]

include/stdx/bit.hpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <array>
99
#include <climits>
1010
#include <cstdint>
11+
#include <iterator>
1112
#include <limits>
1213
#include <type_traits>
1314

@@ -432,6 +433,40 @@ template <std::size_t N> CONSTEVAL auto smallest_uint() {
432433
}
433434

434435
template <std::size_t N> using smallest_uint_t = decltype(smallest_uint<N>());
436+
437+
namespace bit_detail {
438+
template <std::size_t... Offsets>
439+
constexpr auto shifts = [] {
440+
constexpr auto offsets = std::array{std::size_t{}, Offsets...};
441+
auto s = std::array<std::size_t, sizeof...(Offsets) + 1>{};
442+
for (auto i = std::size_t{}; i < sizeof...(Offsets); ++i) {
443+
s[i + 1] = offsets[i + 1] - offsets[i];
444+
}
445+
return s;
446+
}();
447+
448+
template <std::size_t Shift, std::size_t Msb, typename T>
449+
constexpr auto shift_extract(T &t) -> T {
450+
t = static_cast<T>(t >> Shift);
451+
constexpr auto mask = bit_mask<T, Msb>();
452+
return static_cast<T>(t & mask);
453+
}
454+
455+
template <std::size_t... Offsets, typename T, std::size_t... Is>
456+
constexpr auto bit_destructure_impl(T t, std::index_sequence<Is...>) {
457+
return std::array{
458+
shift_extract<shifts<Offsets...>[Is], shifts<Offsets...>[Is + 1] - 1>(
459+
t)...};
460+
}
461+
} // namespace bit_detail
462+
463+
template <std::size_t... Offsets, typename T>
464+
constexpr auto bit_destructure(T t)
465+
-> std::enable_if_t<unsigned_integral<T>,
466+
std::array<T, sizeof...(Offsets) + 1>> {
467+
return bit_detail::bit_destructure_impl<Offsets..., bit_size<T>()>(
468+
t, std::make_index_sequence<sizeof...(Offsets) + 1>{});
469+
}
435470
} // namespace v1
436471
} // namespace stdx
437472

test/bit.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,24 @@ TEST_CASE("smallest_uint", "[bit]") {
432432
STATIC_REQUIRE(std::is_same_v<stdx::smallest_uint_t<64>, std::uint64_t>);
433433
STATIC_REQUIRE(std::is_same_v<stdx::smallest_uint_t<65>, std::uint64_t>);
434434
}
435+
436+
TEST_CASE("bit_destructure (degenerate case)", "[bit]") {
437+
constexpr auto x = std::uint16_t{0b1111'1111'0000'0000u};
438+
auto [a] = stdx::bit_destructure(x);
439+
CHECK(a == x);
440+
}
441+
442+
TEST_CASE("bit_destructure (split in two)", "[bit]") {
443+
constexpr auto x = std::uint16_t{0xa5'5au};
444+
auto [a, b] = stdx::bit_destructure<8>(x);
445+
CHECK(a == 0x5au);
446+
CHECK(b == 0xa5u);
447+
}
448+
449+
TEST_CASE("bit_destructure (split in three)", "[bit]") {
450+
constexpr auto x = std::uint32_t{0x1234'5678u};
451+
auto [a, b, c] = stdx::bit_destructure<8, 24>(x);
452+
CHECK(a == 0x78u);
453+
CHECK(b == 0x3456u);
454+
CHECK(c == 0x12u);
455+
}

0 commit comments

Comments
 (0)