diff --git a/docs/bit.adoc b/docs/bit.adoc index 189f419..65cb79f 100644 --- a/docs/bit.adoc +++ b/docs/bit.adoc @@ -6,6 +6,26 @@ provides an implementation that mirrors https://en.cppreference.com/w/cpp/header/bit[``], but is `constexpr` in C++17. It is mostly based on intrinsics. +=== `bit_destructure` + +`bit_destructure` is a function for unpacking an unsigned integral value into multiple +smaller bit width values. It is a more general case of xref:bit.adoc#_bit_unpack[`bit_unpack`]. + +[source,cpp] +---- +auto const [a, b, c] = stdx::bit_destructure<8, 24>(0x1234'5678u); +assert(a == 0x78u); // bits [0-8) +assert(b == 0x3456u); // bits [8-24) +assert(c == 0x12u); // bits [24-32) +---- + +The `value_type` of the returned array is the same as the type of the value that +is pass to `bit_destructure`. + +NOTE: Unlike `bit_unpack`, `bit_destructure` returns the elements in +_increasing_ order of significance, and the template arguments representing the +split boundaries must be in increasing order. + === `bit_mask` `bit_mask` is a function for constructing a bit mask between most-significant @@ -71,7 +91,7 @@ static_assert(y == x); - to pack 4 `std::uint16_t`​s into a `std::uint64_t` - to pack 8 `std::uint8_t`​s into a `std::uint64_t` -The arguments are listed in order of significance, i.e. for the binary +The arguments are listed in order of _decreasing_ significance, i.e. for the binary overloads, the first argument is the high bits, and the second argument the low bits. @@ -109,7 +129,7 @@ assert(b == 0x5678u); - to unpack a `std::uint64_t` into 8 `std::uint8_t`​s The return value of `bit_unpack` is actually a `std::array` with elements in -order of significance. In this way `bit_unpack` followed by `bit_pack` produces +order of _decreasing_ significance. In this way `bit_unpack` followed by `bit_pack` produces the original value. [source,cpp] diff --git a/include/stdx/bit.hpp b/include/stdx/bit.hpp index 657f555..3706177 100644 --- a/include/stdx/bit.hpp +++ b/include/stdx/bit.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -432,6 +433,40 @@ template CONSTEVAL auto smallest_uint() { } template using smallest_uint_t = decltype(smallest_uint()); + +namespace bit_detail { +template +constexpr auto shifts = [] { + constexpr auto offsets = std::array{std::size_t{}, Offsets...}; + auto s = std::array{}; + for (auto i = std::size_t{}; i < sizeof...(Offsets); ++i) { + s[i + 1] = offsets[i + 1] - offsets[i]; + } + return s; +}(); + +template +constexpr auto shift_extract(T &t) -> T { + t = static_cast(t >> Shift); + constexpr auto mask = bit_mask(); + return static_cast(t & mask); +} + +template +constexpr auto bit_destructure_impl(T t, std::index_sequence) { + return std::array{ + shift_extract[Is], shifts[Is + 1] - 1>( + t)...}; +} +} // namespace bit_detail + +template +constexpr auto bit_destructure(T t) + -> std::enable_if_t, + std::array> { + return bit_detail::bit_destructure_impl()>( + t, std::make_index_sequence{}); +} } // namespace v1 } // namespace stdx diff --git a/test/bit.cpp b/test/bit.cpp index 8a8e1ad..982b5d5 100644 --- a/test/bit.cpp +++ b/test/bit.cpp @@ -432,3 +432,24 @@ TEST_CASE("smallest_uint", "[bit]") { STATIC_REQUIRE(std::is_same_v, std::uint64_t>); STATIC_REQUIRE(std::is_same_v, std::uint64_t>); } + +TEST_CASE("bit_destructure (degenerate case)", "[bit]") { + constexpr auto x = std::uint16_t{0b1111'1111'0000'0000u}; + auto [a] = stdx::bit_destructure(x); + CHECK(a == x); +} + +TEST_CASE("bit_destructure (split in two)", "[bit]") { + constexpr auto x = std::uint16_t{0xa5'5au}; + auto [a, b] = stdx::bit_destructure<8>(x); + CHECK(a == 0x5au); + CHECK(b == 0xa5u); +} + +TEST_CASE("bit_destructure (split in three)", "[bit]") { + constexpr auto x = std::uint32_t{0x1234'5678u}; + auto [a, b, c] = stdx::bit_destructure<8, 24>(x); + CHECK(a == 0x78u); + CHECK(b == 0x3456u); + CHECK(c == 0x12u); +}