From 34b3c6d3b8ec8b3ed9265f498af5df8ac6f550c4 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Wed, 9 Sep 2026 21:58:39 -0600 Subject: [PATCH] :sparkles: Add `bit_structure` Problem: - `bit_unpack` is to `bit_pack` as `bit_destructure` is to ???. Solution: - Add `bit_structure`. --- docs/bit.adoc | 26 ++++++++++++++++++++++++++ include/stdx/bit.hpp | 18 ++++++++++++++++++ test/bit.cpp | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/docs/bit.adoc b/docs/bit.adoc index 65cb79f..efe1362 100644 --- a/docs/bit.adoc +++ b/docs/bit.adoc @@ -107,6 +107,32 @@ constexpr std::size_t x = stdx::bit_size(); static_assert(x == 8); ---- +=== `bit_structure` + +`bit_structure` is the opposite of +xref:bit.adoc#_bit_destructure[`bit_destructure`]: a function for packing +several unsigned integral values into a larger bit width value. It is a more +general case of xref:bit.adoc#_bit_pack[`bit_pack`]. + +[source,cpp] +---- +std::uint32_t const x = 0x1234'5678u; +auto const [a, b, c] = stdx::bit_destructure<8, 24>(x); +// a = 0x78u, b = 0x3456u, c = 0x12u +// a, b and c are all std::uint32_t + +auto const x = stdx::bit_structure<8, 24>(a, b, c); +// x = 0x1234'5678u (a std::uint32_t) + +// or: +auto const y = stdx::bit_structure(a, b, c); +// y = 0x1234'5678u (a std::uint64_t) +---- + +NOTE: Unlike `bit_pack`, `bit_structure` takes the arguments in +_increasing_ order of significance, and the template arguments representing the +split boundaries must be in increasing order. + === `bit_unpack` `bit_unpack` is a function for unpacking an unsigned integral values into multiple diff --git a/include/stdx/bit.hpp b/include/stdx/bit.hpp index 5587bfa..61336cc 100644 --- a/include/stdx/bit.hpp +++ b/include/stdx/bit.hpp @@ -462,5 +462,23 @@ constexpr auto bit_destructure(T t) -> std::array { return bit_detail::bit_destructure_impl()>( t, std::make_index_sequence{}); } + +template + requires(sizeof...(Ts) - sizeof...(Offsets) == 1) +constexpr auto bit_structure(Ts... ts) -> T { + constexpr auto shifted = [](auto x) -> T { + return static_cast(static_cast(x) << Offset); + }; + return [&]() -> T { + return (T{} | ... | shifted.template operator()(ts)); + }.template operator()(); +} + +template + requires(sizeof...(Ts) - sizeof...(Offsets) == 1) +constexpr auto bit_structure(Ts... ts) -> std::common_type_t { + using T = std::common_type_t; + return bit_structure(ts...); +} } // namespace v1 } // namespace stdx diff --git a/test/bit.cpp b/test/bit.cpp index d134d32..28beae7 100644 --- a/test/bit.cpp +++ b/test/bit.cpp @@ -459,3 +459,39 @@ TEST_CASE("bit_destructure (split in three)", "[bit]") { CHECK(b == 0x3456u); CHECK(c == 0x12u); } + +TEST_CASE("bit_structure (degenerate case)", "[bit]") { + constexpr auto x = stdx::bit_structure(0b11u); + STATIC_CHECK(x == 0b11u); + STATIC_CHECK(std::same_as); +} + +TEST_CASE("bit_structure (two parts)", "[bit]") { + constexpr auto x = stdx::bit_structure(0b01u, 0b10u); + STATIC_CHECK(x == 0b10'01u); + STATIC_CHECK(std::same_as); +} + +TEST_CASE("bit_structure (three parts)", "[bit]") { + constexpr auto x = + stdx::bit_structure(0b01u, 0b10u, 0b10u); + STATIC_CHECK(x == 0b10'10'001u); + STATIC_CHECK(std::same_as); +} + +TEST_CASE("bit_{de}structure round-trip", "[bit]") { + constexpr auto x = std::uint32_t{0x1234'5678u}; + auto [a, b, c] = stdx::bit_destructure<8, 24>(x); + auto y = stdx::bit_structure(a, b, c); + CHECK(x == y); +} + +TEST_CASE("bit_structure (inferred type)", "[bit]") { + constexpr auto x = std::uint32_t{0x1234'5678u}; + auto [a, b, c] = stdx::bit_destructure<8, 24>(x); + STATIC_CHECK(std::same_as); + STATIC_CHECK(std::same_as); + STATIC_CHECK(std::same_as); + auto y = stdx::bit_structure<8, 24>(a, b, c); + CHECK(x == y); +}