From 00848d6a90ec73bbc87af18621305bc53da1a6e8 Mon Sep 17 00:00:00 2001 From: StarQTius Date: Wed, 25 Sep 2024 21:24:42 +0200 Subject: [PATCH] dirty --- example/CMakeLists.txt | 2 +- example/dynamixel-protocol-2.0.cpp | 109 +++++--- include/CMakeLists.txt | 2 +- include/upd/description.hpp | 232 ++++++++++++++-- include/upd/detail/variadic/diff.hpp | 33 --- include/upd/integer.hpp | 57 +++- include/upd/named_tuple.hpp | 42 ++- include/upd/named_value.hpp | 55 ++-- include/upd/tuple.hpp | 379 ++++++++++++++++++++++++--- 9 files changed, 741 insertions(+), 170 deletions(-) delete mode 100644 include/upd/detail/variadic/diff.hpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index a656dce..6e4780b 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,3 +1,3 @@ add_executable(dynamixel_protocol_20 dynamixel-protocol-2.0.cpp) add_dependencies(check_revamp dynamixel_protocol_20) -target_link_libraries(dynamixel_protocol_20 PRIVATE Unpadded) \ No newline at end of file +target_link_libraries(dynamixel_protocol_20 PRIVATE Unpadded) diff --git a/example/dynamixel-protocol-2.0.cpp b/example/dynamixel-protocol-2.0.cpp index a011ac0..0e15cbb 100644 --- a/example/dynamixel-protocol-2.0.cpp +++ b/example/dynamixel-protocol-2.0.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -11,14 +10,57 @@ ostream &operator<<(ostream &os, byte b) { return os << static_cast(b); } } // namespace std +using crc = upd::xuint<16>; + +constexpr auto accumulate_crc(crc acc, upd::xuint<8> byte) noexcept -> crc { + constexpr auto crc_table = std::array { + 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, + 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, + 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, + 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, + 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, + 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, + 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, + 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, + 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, + 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, + 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, + 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, + 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, + 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, + 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, + 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, + 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 + }; + + auto i = ((acc >> 8) ^ byte) & 0xff; + return (acc << 8) ^ crc_table[i]; +} + constexpr auto description = [] { using namespace upd; using namespace upd::literals; using namespace upd::descriptor; - return constant<"header"_h>(0xfdffff, width<32>) | field<"id"_h>(unsigned_int, width<8>) | - field<"length"_h>(unsigned_int, width<16>) | field<"instruction"_h>(unsigned_int, width<8>) | - field<"checksum"_h>(unsigned_int, width<16>); + return + // constant<"header">(0xfdffff, width<32>) | field<"id">(unsigned_int, width<8>) | + // field<"length">(unsigned_int, width<16>) | field<"instruction">(unsigned_int, width<8>) | + checksum<"crc">(accumulate_crc, width<16>, 0, all_fields); }(); constexpr auto answer_description = [] { @@ -26,19 +68,22 @@ constexpr auto answer_description = [] { using namespace upd::literals; using namespace upd::descriptor; - return constant<"header"_h>(0xfdffff, width<32>) | field<"id"_h>(unsigned_int, width<8>) | - field<"length"_h>(unsigned_int, width<16>) | field<"instruction"_h>(unsigned_int, width<8>) | - field<"error"_h>(unsigned_int, width<8>) | field<"model_number"_h>(unsigned_int, width<16>) | - field<"firmware_version"_h>(unsigned_int, width<8>) | field<"checksum"_h>(unsigned_int, width<16>); + return constant<"header">(0xfdffff, width<32>) | field<"id">(unsigned_int, width<8>) | + field<"length">(unsigned_int, width<16>) | field<"instruction">(unsigned_int, width<8>) | + field<"error">(unsigned_int, width<8>) | field<"model_number">(unsigned_int, width<16>) | + field<"firmware_version">(unsigned_int, width<8>) | + checksum<"crc">(accumulate_crc, width<16>, 0, all_fields); }(); struct serializer { + constexpr static auto bytewidth = 8; + template void serialize_unsigned(XInteger value, OutputIt output) { static_assert(upd::is_extended_integer_v, "`value` must be an instance of `extended_integer`"); static_assert(!upd::is_signed_v, "`value` must be unsigned"); - constexpr auto byte_count = value.bitsize / CHAR_BIT; + constexpr auto byte_count = value.bitsize / bytewidth; auto decomposition = value.decompose(upd::width); std::copy(decomposition.begin(), decomposition.end(), output); @@ -56,7 +101,7 @@ struct serializer { abs = ~abs + 1; } - constexpr auto byte_count = abs.bitsize / CHAR_BIT; + constexpr auto byte_count = abs.bitsize / bytewidth; auto decomposition = abs.decompose(upd::width); std::copy(decomposition.begin(), decomposition.end(), output); @@ -64,9 +109,9 @@ struct serializer { template auto deserialize_unsigned(InputIt input, upd::width_t) { - static_assert(Bitsize % CHAR_BIT == 0, "`Bitsize` must be a multiple of `CHAR_BIT`"); + static_assert(Bitsize % bytewidth == 0, "`Bitsize` must be a multiple of `bytewidth`"); - constexpr auto size = Bitsize / CHAR_BIT; + constexpr auto size = Bitsize / bytewidth; auto byteseq = std::array{}; auto last_written = std::copy_n(input, size, byteseq.begin()); @@ -78,9 +123,9 @@ struct serializer { template auto deserialize_signed(InputIt input, upd::width_t) { - static_assert((Bitsize + 1) % CHAR_BIT == 0, "`Bitsize` must be a multiple of `CHAR_BIT`"); + static_assert((Bitsize + 1) % bytewidth == 0, "`Bitsize` must be a multiple of `bytewidth`"); - constexpr auto size = (Bitsize + 1) / CHAR_BIT; + constexpr auto size = (Bitsize + 1) / bytewidth; auto byteseq = std::array{}; auto last_written = std::copy_n(input, size, byteseq.begin()); @@ -88,7 +133,7 @@ struct serializer { UPD_ASSERT(last_written == byteseq.end()); auto raw = upd::recompose_into_xuint(byteseq); - auto sign = (raw & upd::nth_bit != 0); + auto sign = ((raw & upd::nth_bit) != 0); auto abs = (raw & upd::nth_bit) ? ~raw + 1 : raw; return sign ? -abs : abs; @@ -111,8 +156,8 @@ int main() { auto oit = std::ostream_iterator{std::cout, " "}; std::cout << std::hex; - auto ping_ex1 = description.instantiate( - upd::kw<"id"_h> = 1, upd::kw<"length"_h> = 3, upd::kw<"instruction"_h> = 1, upd::kw<"checksum"_h> = 0x4e19); + auto ping_ex1 = description.instantiate(ser, + upd::kw<"id"> = 1, upd::kw<"length"> = 3, upd::kw<"instruction"> = 1); if (!ping_ex1) { return (int)ping_ex1.error(); } @@ -128,14 +173,13 @@ int main() { } std::printf("Answer 1: \n"); - std::printf("- header: %" PRIx32 "\n", (std::uint32_t)(*answer1)[upd::kw<"header"_h>]); - std::printf("- id: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"id"_h>]); - std::printf("- length: %" PRIx16 "\n", (std::uint16_t)(*answer1)[upd::kw<"length"_h>]); - std::printf("- instruction: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"instruction"_h>]); - std::printf("- error: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"error"_h>]); - std::printf("- model number: %" PRIx16 "\n", (std::uint16_t)(*answer1)[upd::kw<"model_number"_h>]); - std::printf("- firmware version: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"firmware_version"_h>]); - std::printf("- checksum: %" PRIx16 "\n", (std::uint16_t)(*answer1)[upd::kw<"checksum"_h>]); + std::printf("- header: %" PRIx32 "\n", (std::uint32_t)(*answer1)[upd::kw<"header">]); + std::printf("- id: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"id">]); + std::printf("- length: %" PRIx16 "\n", (std::uint16_t)(*answer1)[upd::kw<"length">]); + std::printf("- instruction: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"instruction">]); + std::printf("- error: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"error">]); + std::printf("- model number: %" PRIx16 "\n", (std::uint16_t)(*answer1)[upd::kw<"model_number">]); + std::printf("- firmware version: %" PRIx8 "\n", (std::uint8_t)(*answer1)[upd::kw<"firmware_version">]); std::printf("\n\n"); auto answer2_seq = bytearray{0xff, 0xff, 0xfd, 0x00, 0x02, 0x07, 0x00, 0x55, 0x00, 0x06, 0x04, 0x26, 0x6f, 0x6d}; @@ -145,13 +189,12 @@ int main() { } std::printf("Answer 2: \n"); - std::printf("- header: %" PRIx32 "\n", (std::uint32_t)(*answer2)[upd::kw<"header"_h>]); - std::printf("- id: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"id"_h>]); - std::printf("- length: %" PRIx16 "\n", (std::uint16_t)(*answer2)[upd::kw<"length"_h>]); - std::printf("- instruction: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"instruction"_h>]); - std::printf("- error: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"error"_h>]); - std::printf("- model number: %" PRIx16 "\n", (std::uint16_t)(*answer2)[upd::kw<"model_number"_h>]); - std::printf("- firmware version: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"firmware_version"_h>]); - std::printf("- checksum: %" PRIx16 "\n", (std::uint16_t)(*answer2)[upd::kw<"checksum"_h>]); + std::printf("- header: %" PRIx32 "\n", (std::uint32_t)(*answer2)[upd::kw<"header">]); + std::printf("- id: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"id">]); + std::printf("- length: %" PRIx16 "\n", (std::uint16_t)(*answer2)[upd::kw<"length">]); + std::printf("- instruction: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"instruction">]); + std::printf("- error: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"error">]); + std::printf("- model number: %" PRIx16 "\n", (std::uint16_t)(*answer2)[upd::kw<"model_number">]); + std::printf("- firmware version: %" PRIx8 "\n", (std::uint8_t)(*answer2)[upd::kw<"firmware_version">]); std::printf("\n\n"); } diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index c627419..2501446 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(${PROJECT_NAME} INTERFACE) target_include_directories( ${PROJECT_NAME} INTERFACE $ $) -target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) +target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_20) if(${PROJECT_NAME}_PLATFORM_ENDIANESS) target_compile_definitions( diff --git a/include/upd/description.hpp b/include/upd/description.hpp index 7177d2e..72b4e57 100644 --- a/include/upd/description.hpp +++ b/include/upd/description.hpp @@ -11,7 +11,6 @@ #include "detail/always_false.hpp" #include "detail/has_value_member.hpp" #include "detail/integral_constant.hpp" -#include "detail/variadic/diff.hpp" #include "detail/variadic/equals.hpp" #include "detail/variadic/filter.hpp" #include "detail/variadic/map.hpp" @@ -25,10 +24,10 @@ namespace upd::descriptor { -template +template struct field_t; -template +template struct constant_t; } // namespace upd::descriptor @@ -70,16 +69,105 @@ class iterator_reference { namespace upd { +template typename TT> +concept instance_of = detail::is_instance_of_v; + +template +class invoker_iterator; + +template +requires instance_of, invoker_iterator> +class invoker_iterator_proxy; + +template +class invoker_iterator { + template + requires instance_of, invoker_iterator> + friend class invoker_iterator_proxy; + +public: + using invocable_type = F; + + constexpr explicit invoker_iterator(F f) noexcept : m_f{std::move(f)} {} + + constexpr auto operator*() noexcept -> invoker_iterator_proxy { + return invoker_iterator_proxy{this}; + } + + constexpr auto operator*() const noexcept -> invoker_iterator_proxy { + return invoker_iterator_proxy{this}; + } + + constexpr auto operator++() noexcept -> invoker_iterator & { + return *this; + } + + constexpr auto operator++() const noexcept -> const invoker_iterator & { + return *this; + } + +private: + F m_f; +}; + +template +requires instance_of, invoker_iterator> +class invoker_iterator_proxy { + template + friend class invoker_iterator; + +public: + using invocable_type = typename Parent::invocable_type; + + template requires std::invocable + constexpr auto operator=(T &&x) const -> const invoker_iterator_proxy & { + std::invoke(m_parent->m_f, UPD_FWD(x)); + return *this; + } + +private: + constexpr invoker_iterator_proxy(Parent *parent) noexcept: m_parent{parent} {} + + Parent *m_parent; +}; + +template +class transformer_iterator { +public: + constexpr explicit transformer_iterator(Iter iter, F f) noexcept: + m_iter{iter}, + m_f{std::move(f)} + {} + + [[nodiscard]] constexpr auto operator*() -> decltype(auto) { + return m_f(*m_iter); + } + + [[nodiscard]] constexpr auto operator++() -> transformer_iterator & { + ++m_iter; + return *this; + } + +private: + Iter m_iter; + F m_f; +}; + enum class error { none, + checksum_mismatch, }; -template +template class unexpected { public: - constexpr explicit unexpected(E e) noexcept : m_error{std::move(e)} {} + constexpr unexpected() noexcept = default; + + constexpr unexpected(E e) noexcept : m_error{std::move(e)} {} - [[nodiscard]] constexpr auto error() noexcept -> E { return m_error; } + [[nodiscard]] constexpr operator bool() const noexcept { return static_cast(m_error); } + + [[nodiscard]] constexpr auto error() const noexcept -> E { return m_error; } private: E m_error; @@ -160,12 +248,19 @@ class expected { namespace upd::descriptor { -template +struct all_fields_t {}; + +constexpr auto all_fields = all_fields_t{}; + +template constexpr auto field(signedness_t, width_t) noexcept(release); -template +template constexpr auto constant(T, width_t) noexcept(release); +template +constexpr auto checksum(BinaryOp, width_t, Init, all_fields_t) noexcept(release); + } // namespace upd::descriptor namespace upd { @@ -184,6 +279,7 @@ template enum class field_tag { pure_field, constant, + checksum, }; template @@ -191,20 +287,26 @@ class description { template friend constexpr auto operator|(description lhs, description rhs) noexcept; - template + template friend constexpr auto descriptor::field(signedness_t, width_t) noexcept(release); - template + template friend constexpr auto descriptor::constant(T, width_t) noexcept(release); + template + friend constexpr auto descriptor::checksum(BinaryOp, width_t, Init, descriptor::all_fields_t) noexcept(release); + public: - template - [[nodiscard]] constexpr auto instantiate(NamedValues... nvs) const noexcept { - static_assert((detail::is_instance_of_v && ...), + constexpr static auto identifiers = typelist{} + .transform_const([](auto field_type) { return field_type->identifier; }); + + template + [[nodiscard]] constexpr auto instantiate(Serializer &ser, NamedValues... nvs) const noexcept { + static_assert((requires { named_value{nvs}; } && ...), "`nvs` must be a pack of `named_value` instances"); auto named_args = (nvs, ...); - auto impl = [&](const auto &field) { + auto first_pass = [&](const auto &field) { if constexpr (field.tag == field_tag::pure_field) { constexpr auto width = field.width; using representation = std::conditional_t, xuint>; @@ -212,34 +314,85 @@ class description { return kw = representation{value}; } else if constexpr (field.tag == field_tag::constant) { return kw = field.value; + } else if constexpr (field.tag == field_tag::checksum) { + constexpr auto width = field.width; + return kw = xuint{field.init}; } else { static_assert(UPD_ALWAYS_FALSE, "`field` is not a field object"); } }; - auto make_named_tuple = [](auto &&...nvs) { return (nvs, ...); }; + auto make_named_tuple = [](auto &&...nvs) { return name_tuple(std::tuple{UPD_FWD(nvs).value()...}); }; + auto retval = m_fields.transform(first_pass).apply(make_named_tuple); + auto second_pass = [&](const auto &field) { + if constexpr (field.tag == field_tag::checksum) { + constexpr auto identifier = field.identifier; + auto &[op, init, get_range] = field; + auto &acc = retval[kw]; + auto folder = invoker_iterator{ + [&, op=op](auto x) { acc = op(acc, x); } + }; + + get_range(identifiers).for_each([&](auto id) { + return retval[kw].serialize(ser, folder); + }); + } + }; - return expected{m_fields.transform(impl).apply(make_named_tuple)}; + m_fields.for_each(second_pass); + return expected{std::move(retval)}; } template [[nodiscard]] constexpr auto decode(InputIt src, Serializer &ser) const noexcept(release) { - auto impl = [&](const auto &field) { + auto err = unexpected{}; + auto accumulators = m_fields + .filter([](auto field_type) { return field_type->tag == field_tag::checksum; }) + .apply([](auto &&... checksum_fields) { + return name_tuple(std::tuple{checksum_fields.init...}); + } + ); + + auto first_pass = [&](const auto &field) { + if constexpr (field.tag == field_tag::checksum) { + auto &[op, init, get_range] = field; + constexpr auto identifier = field.identifier; + auto range = get_range(identifiers); + auto accumulate_and_pass = [&, &op = op, &acc = accumulators[kw]](auto identifier, const auto &value) mutable { + if constexpr (range.filter([&](auto id) { return id == identifier; }).size()) { + acc = op(acc, value); + } + }; + + return [f = std::move(accumulate_and_pass)](auto iter) { return transformer_iterator{iter, std::move(f)}; }; + } + }; + + auto decorated_src = m_fields.transform(first_pass, filter_void).fold_right(src, invoke); + auto second_pass = [&](const auto &field) { + constexpr auto identifier = field.identifier; if constexpr (field.tag == field_tag::pure_field) { constexpr auto width = field.width; using representation = std::conditional_t, xuint>; - return kw = deserialize_into_xinteger(detail::iterator_reference{src}, ser); + return kw = deserialize_into_xinteger(detail::iterator_reference{src}, ser); } else if constexpr (field.tag == field_tag::constant) { - return kw = field.value; + return kw = field.value; + } else if constexpr (field.tag == field_tag::checksum) { + constexpr auto width = field.width; + using representation = std::conditional_t, xuint>; + auto received = deserialize_into_xinteger(detail::iterator_reference{src}, ser); + if (err && accumulators[kw] != received) { + err = error::checksum_mismatch; + } } else { - static_assert(UPD_ALWAYS_FALSE, "`field` is not a field object"); + static_assert(UPD_ALWAYS_FALSE, "`field.tag` is not a valid `field_tag` enumerator"); } }; - auto make_named_tuple = [](auto &&...nvs) { return (nvs, ...); }; - - auto retval = m_fields.transform(impl).apply(make_named_tuple); - return expected{std::move(retval)}; + auto retval = m_fields + .transform(second_pass, filter_void) + .apply([](auto &&... nvs) { return name_tuple(std::tuple{UPD_FWD(nvs).value()...}); }); + return err ? err : expected{std::move(retval)}; } private: @@ -259,7 +412,7 @@ template auto self_comparison_count = fields.type_only() .transform_const(get_identifier) .square() - .transform(equal_to) + .transform(unpack | equal_to) .fold_const(auto_constant{}, plus); static_assert(self_comparison_count == fields.size(), "Merging these descriptions would result in duplicate IDs"); @@ -271,7 +424,7 @@ template namespace upd::descriptor { -template +template struct field_t { constexpr static auto tag = field_tag::pure_field; constexpr static auto identifier = Identifier; @@ -279,13 +432,13 @@ struct field_t { constexpr static auto width = Width; }; -template +template [[nodiscard]] constexpr auto field(signedness_t, width_t) noexcept(release) { auto retval = field_t{}; return description{tuple{retval}}; } -template +template struct constant_t { constexpr static auto tag = field_tag::constant; constexpr static auto identifier = Identifier; @@ -295,12 +448,33 @@ struct constant_t { xuint value; }; -template +template [[nodiscard]] constexpr auto constant(T n, width_t) noexcept(release) { auto retval = constant_t{}; return description{tuple{retval}}; } +template +struct checksum_t { + constexpr static auto tag = field_tag::checksum; + constexpr static auto identifier = Identifier; + constexpr static auto is_signed = false; + constexpr static auto width = Width; + + BinaryOp op; + Init init; + GetRange get_range; +}; + +template +[[nodiscard]] constexpr auto checksum(BinaryOp op, width_t, Init init, all_fields_t) noexcept(release) { + auto get_range = [](auto identifiers) { return identifiers; }; + auto retval = checksum_t{std::move(op), init, get_range}; + + return description{tuple{retval}}; +} + } // namespace upd::descriptor namespace upd::literals { diff --git a/include/upd/detail/variadic/diff.hpp b/include/upd/detail/variadic/diff.hpp deleted file mode 100644 index d522329..0000000 --- a/include/upd/detail/variadic/diff.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include - -namespace upd::detail::variadic { - -template -struct diff; - -template -struct diff, std::tuple> { - constexpr static auto min_count = std::min(sizeof...(Ts), sizeof...(Us)); - constexpr static auto max_count = std::max(sizeof...(Ts), sizeof...(Us)); - - using lhs = std::tuple; - using clipped_lhs = clip_t; - - using rhs = std::tuple; - using clipped_rhs = clip_t; - - template - [[nodiscard]] static auto impl(std::tuple<_Ts...>, std::tuple<_Us...>, std::index_sequence) noexcept { - return std::tuple..., std::bool_constant...>{}; - } - - using type = - decltype(impl(std::declval(), std::declval(), std::make_index_sequence{})); -}; - -template -using diff_t = typename diff::type; - -} // namespace upd::detail::variadic \ No newline at end of file diff --git a/include/upd/integer.hpp b/include/upd/integer.hpp index d631393..4d20011 100644 --- a/include/upd/integer.hpp +++ b/include/upd/integer.hpp @@ -98,8 +98,15 @@ class extended_integer { constexpr extended_integer() noexcept = default; template - constexpr extended_integer(T n) noexcept(release) : m_value{(underlying)reduce_scalar(n, bitsize)} { - static_assert(std::is_integral_v || std::is_enum_v, "`n` must be an integer or an enumerator"); + constexpr extended_integer(T n) noexcept(release) { + + if constexpr (std::is_integral_v || std::is_enum_v) { + m_value = (underlying) reduce_scalar(n, bitsize); + } else if constexpr (is_extended_integer_v) { + m_value = n.m_value; + } else { + static_assert(UPD_ALWAYS_FALSE, "`n` must be an integer, an enumerator, or an extended integer"); + } } template || std::is_enum_v>> @@ -179,7 +186,7 @@ class extended_integer { template [[nodiscard]] constexpr auto operator+(T rhs) const noexcept { - constexpr auto xrhs = to_extended_integer(rhs); + auto xrhs = to_extended_integer(rhs); constexpr auto retval_bitsize = std::max(bitsize, xrhs.bitsize); auto retval = m_value + xrhs.m_value; @@ -188,6 +195,50 @@ class extended_integer { return xinteger{retval}; } + template + [[nodiscard]] constexpr auto operator&(T rhs) const noexcept { + auto xrhs = to_extended_integer(rhs); + constexpr auto retval_bitsize = std::max(bitsize, xrhs.bitsize); + + auto retval = m_value & xrhs.m_value; + using retval_type = decltype(retval); + + return xinteger{retval}; + } + + template + [[nodiscard]] constexpr auto operator^(T rhs) const noexcept { + auto xrhs = to_extended_integer(rhs); + constexpr auto retval_bitsize = std::max(bitsize, xrhs.bitsize); + + auto retval = m_value ^ xrhs.m_value; + using retval_type = decltype(retval); + + return xinteger{retval}; + } + + template + [[nodiscard]] constexpr auto operator<<(T rhs) const noexcept { + auto xrhs = to_extended_integer(rhs); + constexpr auto retval_bitsize = std::max(bitsize, xrhs.bitsize); + + auto retval = m_value << xrhs.m_value; + using retval_type = decltype(retval); + + return xinteger{retval}; + } + + template + [[nodiscard]] constexpr auto operator>>(T rhs) const noexcept { + auto xrhs = to_extended_integer(rhs); + constexpr auto retval_bitsize = std::max(bitsize, xrhs.bitsize); + + auto retval = m_value >> xrhs.m_value; + using retval_type = decltype(retval); + + return xinteger{retval}; + } + [[nodiscard]] constexpr auto operator~() const noexcept { static_assert(!is_signed, "Bitwise operators only work on unsigned values"); diff --git a/include/upd/named_tuple.hpp b/include/upd/named_tuple.hpp index efcc49b..618f971 100644 --- a/include/upd/named_tuple.hpp +++ b/include/upd/named_tuple.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include #include @@ -18,27 +20,41 @@ namespace upd { -template +template +struct name { + constexpr name(const char (&s)[N]): value{} { + std::ranges::copy(s, value); + } + + template + [[nodiscard]] constexpr auto operator==(const name &rhs) const noexcept -> bool { + return std::string_view{value} == std::string_view{rhs.value}; + }; + + char value[N]; +}; + +template struct kw_t; -template +template [[nodiscard]] constexpr auto name_tuple(std::tuple) noexcept; template [[nodiscard]] constexpr auto concat_named_tuple(Named_Tuple_Ts &&...); -template +template class named_tuple { using id_ts = detail::integral_constant_tuple_t; - template + template friend constexpr auto name_tuple(std::tuple) noexcept; template friend constexpr auto concat_named_tuple(Named_Tuple_Ts &&...); public: - template + template [[nodiscard]] constexpr auto get() noexcept -> auto & { using id_t = detail::integral_constant_t; constexpr auto id_pos = detail::variadic::find_v; @@ -48,7 +64,7 @@ class named_tuple { return std::get(m_content); } - template + template [[nodiscard]] constexpr auto get() const noexcept -> const auto & { using id_t = detail::integral_constant_t; constexpr auto id_pos = detail::variadic::find_v; @@ -58,14 +74,14 @@ class named_tuple { return std::get(m_content); } - template + template [[nodiscard]] constexpr auto operator[](kw_t) noexcept(release) -> auto & { - return get(); + return get(); } - template + template [[nodiscard]] constexpr auto operator[](kw_t) const noexcept(release) -> const auto & { - return get(); + return get(); } template @@ -85,10 +101,10 @@ class named_tuple { template struct is_named_tuple_instance : std::false_type {}; -template +template struct is_named_tuple_instance> : std::true_type {}; -template +template [[nodiscard]] constexpr auto name_tuple(std::tuple tuple) noexcept { using id_ts = detail::integral_constant_tuple_t; @@ -111,4 +127,4 @@ template return std::apply(name_content, id_ts{}); } -} // namespace upd \ No newline at end of file +} // namespace upd diff --git a/include/upd/named_value.hpp b/include/upd/named_value.hpp index 446a4c6..cf1129e 100644 --- a/include/upd/named_value.hpp +++ b/include/upd/named_value.hpp @@ -17,43 +17,53 @@ namespace upd { -template +template struct kw_t; -template -constexpr auto kw = kw_t>{}; +template +constexpr auto kw = kw_t{}; -template -struct named_value { - static_assert(detail::has_value_member_v, "`Identifier` must have a `value` member"); +template +class named_value { +public: + constexpr static auto identifier = Identifier; - constexpr static auto identifier = Identifier::value; + template + constexpr explicit named_value(std::in_place_t, Args &&... args): m_value{UPD_FWD(args)...} {} template [[nodiscard]] constexpr auto map(F &&f) & { - decltype(auto) mapped_value = std::invoke(UPD_FWD(f), value); + decltype(auto) mapped_value = std::invoke(UPD_FWD(f), m_value); return kw = UPD_FWD(mapped_value); } template [[nodiscard]] constexpr auto map(F &&f) const & { - decltype(auto) mapped_value = std::invoke(UPD_FWD(f), value); + decltype(auto) mapped_value = std::invoke(UPD_FWD(f), m_value); return kw = UPD_FWD(mapped_value); } template [[nodiscard]] constexpr auto map(F &&f) && { - decltype(auto) mapped_value = std::invoke(UPD_FWD(f), std::move(value)); + decltype(auto) mapped_value = std::invoke(UPD_FWD(f), std::move(m_value)); return kw = UPD_FWD(mapped_value); } + [[nodiscard]] constexpr auto value() const & noexcept -> const T& { + return m_value; + } + + [[nodiscard]] constexpr auto value() && noexcept -> T { + return std::move(m_value); + } + template [[nodiscard]] constexpr auto operator,(NamedObject nobj) const & { - if constexpr (detail::is_instance_of_v) { - auto values = std::tuple{value, std::move(nobj.value)}; + if constexpr (requires { upd::named_value{nobj}; }) { + auto values = std::tuple{m_value, std::move(nobj).value()}; return name_tuple(std::move(values)); } else if constexpr (is_named_tuple_instance::value) { - auto tail = std::tuple{value}; + auto tail = std::tuple{m_value}; auto named_tail = name_tuple(std::move(tail)); return concat_named_tuple(std::move(named_tail), std::move(nobj)); } else { @@ -63,11 +73,11 @@ struct named_value { template [[nodiscard]] constexpr auto operator,(NamedObject nobj) &&noexcept { - if constexpr (detail::is_instance_of_v) { - auto values = std::tuple{std::move(value), std::move(nobj.value)}; + if constexpr (requires { upd::named_value{nobj}; }) { + auto values = std::tuple{std::move(m_value), std::move(nobj).value()}; return name_tuple(std::move(values)); } else if constexpr (is_named_tuple_instance::value) { - auto tail = std::tuple{std::move(value)}; + auto tail = std::tuple{std::move(m_value)}; auto named_tail = name_tuple(std::move(tail)); return concat_named_tuple(std::move(named_tail), std::move(nobj)); } else { @@ -75,19 +85,18 @@ struct named_value { } } - T value; +private: + T m_value; }; -template +template struct kw_t { - static_assert(detail::has_value_member_v, "`Identifier` must have a `value` member"); - - constexpr static auto identifier = Identifier::value; + constexpr static auto identifier = Identifier; template [[nodiscard]] constexpr auto operator=(T x) const noexcept -> named_value { - return named_value{UPD_FWD(x)}; + return named_value{std::in_place, UPD_FWD(x)}; } }; -} // namespace upd \ No newline at end of file +} // namespace upd diff --git a/include/upd/tuple.hpp b/include/upd/tuple.hpp index 8b44ca0..fda7e78 100644 --- a/include/upd/tuple.hpp +++ b/include/upd/tuple.hpp @@ -1,32 +1,93 @@ #pragma once +#include +#include +#include +#include #include "detail/variadic/at.hpp" #include "detail/variadic/leaf.hpp" +#include "detail/variadic/clean.hpp" +#include "detail/is_instance_of.hpp" + +namespace upd::detail { + +template +[[nodiscard]] constexpr auto apply_on_index_sequence(F &&f, std::index_sequence) -> decltype(auto) { + return UPD_FWD(f)(std::integral_constant{}...); +} + +} // namespace upd::detail namespace upd { +template typename TT> +concept cvref_instance_of = detail::is_instance_of_v, TT>; + +template +class accumulable_t { + template _BinaryOp> + friend constexpr auto operator,(U &&, accumulable_t<_T, _BinaryOp> &&) -> decltype(auto); + + template + friend constexpr auto accumulable(_T &&, _BinaryOp &&) noexcept -> accumulable_t<_T &&, _BinaryOp &&>; + + constexpr accumulable_t(T value, BinaryOp op): m_value{UPD_FWD(value)}, m_op{UPD_FWD(op)} {} + + T m_value; + BinaryOp m_op; +}; + +template BinaryOp> +[[nodiscard]] constexpr auto operator,(U &&x, accumulable_t &&acc) -> decltype(auto) { + return std::invoke(UPD_FWD(acc.m_op), UPD_FWD(x), UPD_FWD(acc.m_value)); +} + +template +[[nodiscard]] constexpr auto accumulable(T &&x, BinaryOp &&op) noexcept -> accumulable_t { + return accumulable_t{UPD_FWD(x), UPD_FWD(op)}; +} + +struct filter_void_t {}; + +constexpr auto filter_void = filter_void_t{}; + +template +[[nodiscard]] constexpr auto apply(Tuple &&, F &&); + struct encapsulate_t {}; constexpr auto encapsulate = encapsulate_t{}; -template -struct operator_invoker { - constexpr static auto op = Operator{}; +constexpr inline auto equal_to = [](auto &&lhs, auto &&rhs) { + return UPD_FWD(lhs) == UPD_FWD(rhs); +}; - template - [[nodiscard]] constexpr auto operator()(const Lhs &lhs, const Rhs &rhs) const -> decltype(auto) { - return op(lhs, rhs); - } +constexpr inline auto plus = [](auto &&lhs, auto &&rhs) { + return UPD_FWD(lhs) + UPD_FWD(rhs); +}; - template - [[nodiscard]] constexpr auto operator()(const std::pair &lhs_rhs) const -> decltype(auto) { - const auto &[lhs, rhs] = lhs_rhs; - return op(lhs, rhs); - } +constexpr inline auto invoke = [](auto &&f, auto && ...args) { + return UPD_FWD(f)(UPD_FWD(args)...); }; -constexpr inline auto equal_to = operator_invoker>{}; -constexpr inline auto plus = operator_invoker>{}; +struct preserve_value_category_t {}; + +constexpr auto preserve_value_category = preserve_value_category_t{}; + +struct unpack_t {}; + + + +template +[[nodiscard]] constexpr auto operator|(unpack_t, F &&f) { + auto impl = [&](auto &&t) { + return upd::apply(UPD_FWD(t), UPD_FWD(f)); + }; + + return impl; +} + +constexpr auto unpack = unpack_t{}; } // namespace upd @@ -141,6 +202,89 @@ struct typebox { } // namespace upd +namespace upd { + +template +constexpr auto sequence = detail::apply_on_index_sequence( + [](auto... is) { return indexlist{}; }, + std::make_index_sequence{}); + +using false_type = auto_constant; +using true_type = auto_constant; + +template +struct has_type_member : false_type {}; + +template +struct has_type_member> : true_type {}; + +template +constexpr auto has_type_member_v = has_type_member::value; + +template +struct has_value_member : false_type {}; + +template +struct has_value_member : true_type {}; + +template +constexpr auto has_value_member_v = has_value_member::value; + +template +constexpr auto constant_value_or = []() { + if constexpr (has_value_member_v) { + return Value::value; + } else { + return Default; + } +}(); + +} // namespace upd + +namespace upd { + +template +[[nodiscard]] constexpr auto get(Tuple &&) noexcept -> auto &&; + +} // namespace upd + +namespace upd::detail { + +template +class is_tuple_like { + constexpr static auto has_tuple_size = has_value_member_v>; + constexpr static auto tuple_size = constant_value_or, 0>; + + template + constexpr static auto has_tuple_element = has_type_member_v>; + + template + constexpr static auto is_nth_gettable(...) noexcept -> bool { + return false; + } + + template(std::declval()))> + constexpr static auto is_nth_gettable(int) noexcept -> bool { + return true; + } + +public: + constexpr static auto value = has_tuple_size && sequence + .all_of([](auto i) { return has_tuple_element && is_nth_gettable(0); }); +}; + +} // namespace upd::detail + +namespace upd { + +template +using is_tuple_like = auto_constant::value>; + +template +constexpr auto is_tuple_like_v = is_tuple_like::value; + +} // namespace upd + template struct std::tuple_size> { constexpr static auto value = sizeof...(Ts); @@ -173,11 +317,6 @@ struct std::tuple_element> { namespace upd::detail { -template -[[nodiscard]] constexpr auto apply_on_index_sequence(F &&f, std::index_sequence) -> decltype(auto) { - return UPD_FWD(f)(std::integral_constant{}...); -} - template struct leaf { [[nodiscard]] constexpr auto at(auto_constant) &noexcept -> T & { return value; } @@ -206,21 +345,21 @@ struct leaves, Ts...> : leaf... { constexpr static auto size = sizeof...(Ts); - explicit constexpr leaves() = default; + constexpr leaves() = default; - explicit constexpr leaves(Ts... xs) : leaf{UPD_FWD(xs)}... {} + template + explicit constexpr leaves(Us &&... xs) : leaf{UPD_FWD(xs)}... {} [[nodiscard]] constexpr auto at(...) const noexcept -> variadic::not_found_t { return variadic::not_found; } }; +template +leaves(Ts...) -> leaves; + } // namespace upd::detail namespace upd { -template -constexpr auto sequence = detail::apply_on_index_sequence([](auto... is) { return indexlist{}; }, - std::make_index_sequence{}); - template [[nodiscard]] constexpr auto get(Tuple &&) noexcept -> auto &&; @@ -228,19 +367,25 @@ template [[nodiscard]] constexpr auto fittest_tuple_like(Ts &&...); template -[[nodiscard]] constexpr auto apply(Tuple &&, F &&); +constexpr void for_each(Tuple &&, F &&); template -constexpr void for_each(Tuple &&, F &&); +[[nodiscard]] constexpr auto transform(Tuple &&, F &&); + +template +[[nodiscard]] constexpr auto transform(Tuple &&, F &&, preserve_value_category_t); + +template +[[nodiscard]] constexpr auto transform(Tuple &&, F &&, filter_void_t); template class tuple_implementation { constexpr static auto normalize = [](auto &&...xs) noexcept { return fittest_tuple_like(UPD_FWD(xs)...); }; - [[nodiscard]] constexpr auto derived() noexcept -> Derived & { return reinterpret_cast(*this); } + [[nodiscard]] constexpr auto derived() noexcept -> Derived & { return static_cast(*this); } [[nodiscard]] constexpr auto derived() const noexcept -> const Derived & { - return reinterpret_cast(*this); + return static_cast(*this); } public: @@ -276,12 +421,66 @@ class tuple_implementation { template [[nodiscard]] constexpr auto at(I i) &&noexcept -> auto && { - return get(std::move(derived())); + return get(std::move(derived())); } template [[nodiscard]] constexpr auto at(I i) const &&noexcept -> const auto && { - return get(std::move(derived())); + return get(std::move(derived())); + } + + template + [[nodiscard]] constexpr auto all_of(UnaryPred &&p) const -> bool { + auto check = [&](auto truth, const auto &x) { return truth && p(x); }; + return fold_left(true, check); + } + + [[nodiscard]] constexpr auto clean() const { + using namespace std::ranges::views; + + constexpr auto truth_table = sequence + .apply([](auto... is) { + return std::array { + !std::same_as< + typename Derived::template raw_type, + detail::variadic::marked_for_cleaning_t + >... + }; + }); + + constexpr auto kept_index_count = std::accumulate(truth_table.begin(), truth_table.end(), 0); + constexpr auto kept_indices = [&] { + auto kept_indices = std::array {}; + auto i = std::size_t{0}; + for (auto j : iota(std::size_t{0}, truth_table.size())) { + if (truth_table.at(j)) { + kept_indices.at(i) = j; + ++i; + } + } + UPD_ASSERT(i == kept_indices.size()); + + return kept_indices; + }(); + + return sequence + .transform([&](auto i) { return auto_constant{}; }) + .transform([&](auto i) -> auto&& { return at(i); }); + } + + template + [[nodiscard]] constexpr auto filter(Pred p) const { + auto impl = [&](auto &&x) -> decltype(auto) { + using unqual_type = std::remove_cvref_t; + + if constexpr (p(typebox{})) { + return UPD_FWD(x); + } else { + return detail::variadic::marked_for_cleaning_t{}; + } + }; + + return transform(impl).clean(); } [[nodiscard]] constexpr auto flatten() const { @@ -341,6 +540,21 @@ class tuple_implementation { return derived().apply(invoke_impl_const); } + template + [[nodiscard]] constexpr auto fold_left(Init &&init, BinaryOp &&op) const noexcept { + auto impl = [&](auto &&... xs) { + return (UPD_FWD(init), ..., accumulable(op, UPD_FWD(xs))); + }; + + return derived().apply(impl); + } + + template + [[nodiscard]] constexpr auto fold_right(Init &&init, BinaryOp &&op) const noexcept { + auto flipped_op = [&](auto &&lhs, auto &&rhs) { return op(UPD_FWD(rhs), UPD_FWD(lhs)); }; + return derived().reverse().fold_left(UPD_FWD(init), flipped_op); + } + template typename TT> [[nodiscard]] constexpr static auto metatransform() noexcept { auto apply_and_box_type = [](auto i) { @@ -353,6 +567,25 @@ class tuple_implementation { return sequence.transform(apply_and_box_type); } + [[nodiscard]] constexpr auto reverse() const { + constexpr auto rindices = []() { + auto rindices = std::array{}; + auto first = rindices.rbegin(); + auto last = rindices.rend(); + auto i = std::size_t{0}; + for (auto &ri : detail::range{first, last}) { + ri = i++; + } + + return rindices; + }(); + + auto get_element = [&](auto i) -> auto && { + return get(derived()); + }; + return sequence.transform(get_element, preserve_value_category); + } + [[nodiscard]] constexpr auto square() { auto seq = sequence; auto pair_up = [&](auto i) { return seq.transform([&](auto j) { return indexlist{}; }); }; @@ -387,6 +620,46 @@ class tuple_implementation { return transform_and_apply(std::move(derived()), UPD_FWD(f), normalize); } + template + [[nodiscard]] constexpr auto transform(F &&f, preserve_value_category_t) & { + return upd::transform(derived(), UPD_FWD(f), preserve_value_category); + } + + template + [[nodiscard]] constexpr auto transform(F &&f, preserve_value_category_t) const & { + return upd::transform(derived(), UPD_FWD(f), preserve_value_category); + } + + template + [[nodiscard]] constexpr auto transform(F &&f, preserve_value_category_t) && { + return upd::transform(std::move(derived()), UPD_FWD(f), preserve_value_category); + } + + template + [[nodiscard]] constexpr auto transform(F &&f, preserve_value_category_t) const && { + return upd::transform(std::move(derived()), UPD_FWD(f), preserve_value_category); + } + + template + [[nodiscard]] constexpr auto transform(F &&f, filter_void_t) & { + return upd::transform(derived(), UPD_FWD(f), filter_void); + } + + template + [[nodiscard]] constexpr auto transform(F &&f, filter_void_t) const & { + return upd::transform(derived(), UPD_FWD(f), filter_void); + } + + template + [[nodiscard]] constexpr auto transform(F &&f, filter_void_t) && { + return upd::transform(std::move(derived()), UPD_FWD(f), filter_void); + } + + template + [[nodiscard]] constexpr auto transform(F &&f, filter_void_t) const && { + return upd::transform(std::move(derived()), UPD_FWD(f), filter_void); + } + template [[nodiscard]] constexpr auto transform_const(F f) const noexcept { auto apply_const = [&](auto x) { return auto_constant{}; }; @@ -439,12 +712,21 @@ class tuple_implementation { [[nodiscard]] constexpr auto apply_const(F f, encapsulate_t) const noexcept { auto impl = [&](auto... xs) { constexpr auto result = f(xs...); - return [&] { return result; }; + return [result] { return result; }; }; return derived().apply(impl); } + template typename TT, typename... Args> + [[nodiscard]] constexpr auto apply_template(Args &&... args) const noexcept { + auto instantiate_and_construct = [&](auto... types) { + return TT{UPD_FWD(args)...}; + }; + + return derived().type_only().apply(instantiate_and_construct); + } + template constexpr void for_each(F &&f) & { upd::for_each(derived(), UPD_FWD(f)); @@ -576,7 +858,6 @@ template constexpr auto seq = std::make_index_sequence{}; auto invoke_f_once = [&](auto i) -> decltype(auto) { return f(get(UPD_FWD(t))); }; - auto invoke_f_on_each = [&](auto... is) -> decltype(auto) { return UPD_FWD(agg)(invoke_f_once(is)...); }; return detail::apply_on_index_sequence(invoke_f_on_each, seq); @@ -596,7 +877,37 @@ template constexpr void for_each(Tuple &&t, F &&f) { auto impl = [&](auto &&...xs) { ((void)f(UPD_FWD(xs)), ...); }; - apply(UPD_FWD(t), impl); + upd::apply(UPD_FWD(t), impl); +} + +template +[[nodiscard]] constexpr auto transform(Tuple &&t, F &&f, preserve_value_category_t) { + auto impl = [&](auto &&...xs) { + return tuple...>{f(UPD_FWD(xs))...}; + }; + + return upd::apply(UPD_FWD(t), impl); +} + +template +[[nodiscard]] constexpr auto transform(Tuple &&t, F &&f, filter_void_t) { + auto invoke_f = [&](auto &&x) -> decltype(auto) { + using type = decltype(x); + using invoke_result = std::invoke_result_t; + + if constexpr (std::is_void_v) { + std::invoke(f, UPD_FWD(x)); + return detail::variadic::marked_for_cleaning_t{}; + } else { + return std::invoke(f, UPD_FWD(x)); + } + }; + + auto impl = [&](auto &&...xs) { + return tuple{invoke_f(UPD_FWD(xs))...}; + }; + + return upd::apply(UPD_FWD(t), impl).clean(); } } // namespace upd