From 45262dc9375d8e7e9d9034ee9937b3783285af20 Mon Sep 17 00:00:00 2001 From: StarQTius Date: Sun, 15 Sep 2024 04:33:06 +0200 Subject: [PATCH] Add tuple classes --- example/dynamixel-protocol-2.0.cpp | 95 ++- include/upd/basic_ibytestream.hpp | 3 +- include/upd/description.hpp | 260 ++++--- include/upd/detail/is_instance_of.hpp | 6 + include/upd/detail/variadic/diff.hpp | 23 +- include/upd/detail/variadic/equals.hpp | 10 +- include/upd/detail/variadic/filter.hpp | 33 + include/upd/detail/variadic/find.hpp | 3 +- include/upd/detail/variadic/leaf.hpp | 2 +- include/upd/detail/variadic/map.hpp | 4 +- include/upd/detail/variadic/to_array.hpp | 2 +- include/upd/integer.hpp | 109 ++- include/upd/named_tuple.hpp | 12 +- include/upd/named_value.hpp | 7 +- include/upd/tuple.hpp | 895 +++++++++++++---------- test/CMakeLists.txt | 2 +- test/basic_ibytestream.cpp | 5 +- test/integer.cpp | 11 +- 18 files changed, 856 insertions(+), 626 deletions(-) create mode 100644 include/upd/detail/variadic/filter.hpp diff --git a/example/dynamixel-protocol-2.0.cpp b/example/dynamixel-protocol-2.0.cpp index 1318612..a011ac0 100644 --- a/example/dynamixel-protocol-2.0.cpp +++ b/example/dynamixel-protocol-2.0.cpp @@ -1,13 +1,13 @@ -#include -#include -#include #include +#include +#include + +#include +#include namespace std { -ostream &operator<<(ostream &os, byte b) { - return os << static_cast(b); -} +ostream &operator<<(ostream &os, byte b) { return os << static_cast(b); } } // namespace std @@ -16,12 +16,9 @@ constexpr auto 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<"checksum"_h>(unsigned_int, width<16>); + 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>); }(); constexpr auto answer_description = [] { @@ -29,15 +26,10 @@ 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"_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>); }(); struct serializer { @@ -56,7 +48,7 @@ struct serializer { void serialize_signed(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 signed"); - + auto sign = value.signbit(); auto abs = value.abs().enlarge(upd::width<1>); @@ -94,10 +86,10 @@ struct serializer { auto last_written = std::copy_n(input, size, byteseq.begin()); UPD_ASSERT(last_written == byteseq.end()); - + auto raw = upd::recompose_into_xuint(byteseq); auto sign = (raw & upd::nth_bit != 0); - auto abs = (raw & upd::nth_bit) ? ~raw + 1: raw; + auto abs = (raw & upd::nth_bit) ? ~raw + 1 : raw; return sign ? -abs : abs; } @@ -106,12 +98,11 @@ struct serializer { template struct bytearray : std::array { template - constexpr explicit bytearray(Bytes... bytes) noexcept: std::array{static_cast(bytes)...} { - } + constexpr explicit bytearray(Bytes... bytes) noexcept : std::array{static_cast(bytes)...} {} }; template -explicit bytearray(Bytes...) noexcept -> bytearray; +explicit bytearray(Bytes...) noexcept->bytearray; int main() { using namespace upd::literals; @@ -121,50 +112,46 @@ int main() { std::cout << std::hex; auto ping_ex1 = description.instantiate( - upd::kw<"header"_h> = 0xfdffff, - upd::kw<"id"_h> = 1, - upd::kw<"length"_h> = 3, - upd::kw<"instruction"_h> = 1, - upd::kw<"checksum"_h> = 0x4e19); + upd::kw<"id"_h> = 1, upd::kw<"length"_h> = 3, upd::kw<"instruction"_h> = 1, upd::kw<"checksum"_h> = 0x4e19); if (!ping_ex1) { - return (int) ping_ex1.error(); + return (int)ping_ex1.error(); } std::printf("Ping: example 1\n"); ping_ex1->serialize(ser, oit); std::printf("\n\n"); - auto answer1_seq = bytearray {0xff, 0xff, 0xfd, 0x00, 0x01, 0x07, 0x00, 0x55, 0x00, 0x06, 0x04, 0x26, 0x65, 0x5d}; + auto answer1_seq = bytearray{0xff, 0xff, 0xfd, 0x00, 0x01, 0x07, 0x00, 0x55, 0x00, 0x06, 0x04, 0x26, 0x65, 0x5d}; auto answer1 = answer_description.decode(answer1_seq.begin(), ser); if (!answer1) { - return (int) answer1.error(); + return (int)answer1.error(); } 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"_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("\n\n"); - auto answer2_seq = bytearray {0xff, 0xff, 0xfd, 0x00, 0x02, 0x07, 0x00, 0x55, 0x00, 0x06, 0x04, 0x26, 0x6f, 0x6d}; + auto answer2_seq = bytearray{0xff, 0xff, 0xfd, 0x00, 0x02, 0x07, 0x00, 0x55, 0x00, 0x06, 0x04, 0x26, 0x6f, 0x6d}; auto answer2 = answer_description.decode(answer2_seq.begin(), ser); if (!answer2) { - return (int) answer2.error(); + return (int)answer2.error(); } 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"_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("\n\n"); -} \ No newline at end of file +} diff --git a/include/upd/basic_ibytestream.hpp b/include/upd/basic_ibytestream.hpp index 2397afa..355acb1 100644 --- a/include/upd/basic_ibytestream.hpp +++ b/include/upd/basic_ibytestream.hpp @@ -34,7 +34,8 @@ class basic_ibytestream { } else if constexpr (std::is_enum_v) { return std::numeric_limits>::digits; } else { - static_assert(UPD_ALWAYS_FALSE, "Producers can only produce `extended_integer` values, integral values or enumerators"); + static_assert(UPD_ALWAYS_FALSE, + "Producers can only produce `extended_integer` values, integral values or enumerators"); } }(); diff --git a/include/upd/description.hpp b/include/upd/description.hpp index ee8e563..7177d2e 100644 --- a/include/upd/description.hpp +++ b/include/upd/description.hpp @@ -5,21 +5,33 @@ #include #include #include -#include #include +#include -#include "detail/has_value_member.hpp" #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" #include "detail/variadic/product.hpp" -#include "detail/variadic/diff.hpp" #include "detail/variadic/to_array.hpp" -#include "detail/variadic/equals.hpp" -#include "upd.hpp" -#include "token.hpp" -#include "named_value.hpp" #include "integer.hpp" +#include "named_value.hpp" +#include "token.hpp" +#include "tuple.hpp" +#include "upd.hpp" + +namespace upd::descriptor { + +template +struct field_t; + +template +struct constant_t; + +} // namespace upd::descriptor namespace upd::detail { @@ -33,24 +45,17 @@ class iterator_reference { using pointer = typename std::iterator_traits::pointer; using reference = typename std::iterator_traits::reference; - constexpr explicit iterator_reference(iterator_type &iter): - m_cache{*iter}, - m_iter{iter} - { - ++iter; - } + constexpr explicit iterator_reference(iterator_type &iter) : m_cache{*iter}, m_iter{iter} { ++iter; } - [[nodiscard]] constexpr auto operator*() const -> value_type { - return m_cache; - } + [[nodiscard]] constexpr auto operator*() const -> value_type { return m_cache; } - [[nodiscard]] constexpr auto operator++() -> iterator_reference& { + [[nodiscard]] constexpr auto operator++() -> iterator_reference & { m_cache = *m_iter; ++m_iter.get(); return *this; } - [[nodiscard]] constexpr auto operator++() const -> const iterator_reference& { + [[nodiscard]] constexpr auto operator++() const -> const iterator_reference & { m_cache = *m_iter; ++m_iter.get(); return *this; @@ -59,7 +64,6 @@ class iterator_reference { private: std::reference_wrapper m_iter; value_type m_cache; - }; } // namespace upd::detail @@ -73,13 +77,9 @@ enum class error { template class unexpected { public: - constexpr explicit unexpected(E e) noexcept: - m_error{std::move(e)} - {} - - [[nodiscard]] constexpr auto error() noexcept -> E { - return m_error; - } + constexpr explicit unexpected(E e) noexcept : m_error{std::move(e)} {} + + [[nodiscard]] constexpr auto error() noexcept -> E { return m_error; } private: E m_error; @@ -88,95 +88,72 @@ class unexpected { template class expected { public: - constexpr explicit expected(T x) noexcept: - m_value_or_error{std::in_place_index<0>, std::move(x)} - {} - - constexpr expected(unexpected e) noexcept: - m_value_or_error{std::in_place_index<1>, e.error()} - {} - - [[nodiscard]] constexpr operator bool() const noexcept { - return value_if() != nullptr; - } + constexpr explicit expected(T x) noexcept : m_value_or_error{std::in_place_index<0>, std::move(x)} {} - [[nodiscard]] constexpr auto operator*() noexcept -> T& { - return value(); - } + constexpr expected(unexpected e) noexcept : m_value_or_error{std::in_place_index<1>, e.error()} {} - [[nodiscard]] constexpr auto operator*() const noexcept -> const T& { - return value(); - } + [[nodiscard]] constexpr operator bool() const noexcept { return value_if() != nullptr; } - [[nodiscard]] constexpr auto operator->() noexcept -> T* { - return &value(); - } + [[nodiscard]] constexpr auto operator*() noexcept -> T & { return value(); } - [[nodiscard]] constexpr auto operator->() const noexcept -> const T* { - return &value(); - } + [[nodiscard]] constexpr auto operator*() const noexcept -> const T & { return value(); } + + [[nodiscard]] constexpr auto operator->() noexcept -> T * { return &value(); } - [[nodiscard]] constexpr auto value() & noexcept -> T& { + [[nodiscard]] constexpr auto operator->() const noexcept -> const T * { return &value(); } + + [[nodiscard]] constexpr auto value() &noexcept -> T & { auto *v = value_if(); UPD_ASSERT(v != nullptr); - + return *v; } - [[nodiscard]] constexpr auto value() const & noexcept -> const T& { + [[nodiscard]] constexpr auto value() const &noexcept -> const T & { const auto *v = value_if(); UPD_ASSERT(v != nullptr); - + return *v; } - [[nodiscard]] constexpr auto value() && noexcept -> T&& { + [[nodiscard]] constexpr auto value() &&noexcept -> T && { auto *v = value_if(); UPD_ASSERT(v != nullptr); - + return std::move(*v); } - [[nodiscard]] constexpr auto error() & noexcept -> E& { + [[nodiscard]] constexpr auto error() &noexcept -> E & { auto *e = error_if(); UPD_ASSERT(e != nullptr); - + return *e; } - [[nodiscard]] constexpr auto error() const & noexcept -> const E& { + [[nodiscard]] constexpr auto error() const &noexcept -> const E & { const auto *e = error_if(); UPD_ASSERT(e != nullptr); - + return *e; } - [[nodiscard]] constexpr auto error() && noexcept -> E&& { + [[nodiscard]] constexpr auto error() &&noexcept -> E && { auto *e = error_if(); UPD_ASSERT(e != nullptr); - + return std::move(*e); } - [[nodiscard]] constexpr auto value_if() noexcept -> T* { - return std::get_if<0>(&m_value_or_error); - } + [[nodiscard]] constexpr auto value_if() noexcept -> T * { return std::get_if<0>(&m_value_or_error); } - [[nodiscard]] constexpr auto value_if() const noexcept -> const T* { - return std::get_if<0>(&m_value_or_error); - } + [[nodiscard]] constexpr auto value_if() const noexcept -> const T * { return std::get_if<0>(&m_value_or_error); } - [[nodiscard]] constexpr auto error_if() noexcept -> E* { - return std::get_if<1>(&m_value_or_error); - } + [[nodiscard]] constexpr auto error_if() noexcept -> E * { return std::get_if<1>(&m_value_or_error); } - [[nodiscard]] constexpr auto error_if() const noexcept -> const E* { - return std::get_if<1>(&m_value_or_error); - } + [[nodiscard]] constexpr auto error_if() const noexcept -> const E * { return std::get_if<1>(&m_value_or_error); } private: std::variant m_value_or_error; - }; } // namespace upd @@ -184,7 +161,10 @@ class expected { namespace upd::descriptor { template -constexpr auto field(signedness_t, width_t) noexcept; +constexpr auto field(signedness_t, width_t) noexcept(release); + +template +constexpr auto constant(T, width_t) noexcept(release); } // namespace upd::descriptor @@ -192,7 +172,7 @@ namespace upd { template [[nodiscard]] constexpr auto all_of(const Range &range) noexcept -> bool { - for (const auto &e: range) { + for (const auto &e : range) { if (!e) { return false; } @@ -201,88 +181,124 @@ template return true; } +enum class field_tag { + pure_field, + constant, +}; + template class description { template friend constexpr auto operator|(description lhs, description rhs) noexcept; template - friend constexpr auto descriptor::field(signedness_t, width_t) noexcept; + friend constexpr auto descriptor::field(signedness_t, width_t) noexcept(release); -public: - constexpr static auto ids = [] { return std::tuple_cat(Ts::ids...); }(); - - [[nodiscard]] constexpr auto fields() const & -> std::tuple { return m_fields; } - - [[nodiscard]] constexpr auto fields() && -> std::tuple { return std::move(m_fields); } + template + friend constexpr auto descriptor::constant(T, width_t) noexcept(release); +public: template [[nodiscard]] constexpr auto instantiate(NamedValues... nvs) const noexcept { - using identifier_types = std::decay_t; - using value_name_types = std::tuple...>; + static_assert((detail::is_instance_of_v && ...), + "`nvs` must be a pack of `named_value` instances"); + + auto named_args = (nvs, ...); + auto impl = [&](const auto &field) { + if constexpr (field.tag == field_tag::pure_field) { + constexpr auto width = field.width; + using representation = std::conditional_t, xuint>; + auto value = named_args[kw]; + return kw = representation{value}; + } else if constexpr (field.tag == field_tag::constant) { + return kw = field.value; + } else { + static_assert(UPD_ALWAYS_FALSE, "`field` is not a field object"); + } + }; - static_assert((detail::is_instance_of_v && ...), "`nvs` must be a pack of `named_value` instances"); - static_assert(detail::variadic::equals_v, "`nvs.identifier...` must match the content of `ids` in order"); + auto make_named_tuple = [](auto &&...nvs) { return (nvs, ...); }; - return expected{(std::move(nvs).map([](auto n){ return typename Ts::underlying{n}; }), ...)}; + return expected{m_fields.transform(impl).apply(make_named_tuple)}; } template - [[nodiscard]] constexpr auto decode(InputIt src, Serializer &ser) const { - auto impl = [&](auto... identifiers) { - return ((kw = deserialize_into_xinteger(detail::iterator_reference{src}, ser)), ...); + [[nodiscard]] constexpr auto decode(InputIt src, Serializer &ser) const noexcept(release) { + auto impl = [&](const auto &field) { + 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); + } else if constexpr (field.tag == field_tag::constant) { + return kw = field.value; + } else { + static_assert(UPD_ALWAYS_FALSE, "`field` is not a field object"); + } }; - auto retval = std::apply(impl, ids); + auto make_named_tuple = [](auto &&...nvs) { return (nvs, ...); }; + + auto retval = m_fields.transform(impl).apply(make_named_tuple); return expected{std::move(retval)}; } private: - explicit constexpr description(Ts... xs) : m_fields{UPD_FWD(xs)...} {} + explicit constexpr description(tuple fields) : m_fields{std::move(fields)} {} - std::tuple m_fields; + tuple m_fields; }; template [[nodiscard]] constexpr inline auto operator|(description lhs, description rhs) noexcept { - auto fields = std::tuple_cat(std::move(lhs.m_fields), std::move(rhs.m_fields)); - auto get_ids = [](auto x) { return x.ids; }; - - using id_ts = detail::variadic::flatmapf_t; - using id_squared_ts = detail::variadic::product_t; - using is_same_ts = detail::variadic::binmap_t; - constexpr auto are_unique = detail::variadic::sum_v == std::tuple_size_v; - - if constexpr (are_unique) { - auto make_description = [](auto... fields) { return description{std::move(fields)...}; }; - return std::apply(make_description, std::move(fields)); - } else { - static_assert(UPD_ALWAYS_FALSE, "Merging these descriptions would result in duplicate IDs"); - } + auto fields = std::move(lhs.m_fields) + std::move(rhs.m_fields); + auto get_identifier = [](auto field_type) { return field_type->identifier; }; + + // For each identifier of the merged description, we count how often it + // appears. If the total is not equal to the number of fields, we know that + // there are duplicate identifiers. + auto self_comparison_count = fields.type_only() + .transform_const(get_identifier) + .square() + .transform(equal_to) + .fold_const(auto_constant{}, plus); + + static_assert(self_comparison_count == fields.size(), "Merging these descriptions would result in duplicate IDs"); + + return description{std::move(fields)}; } } // namespace upd namespace upd::descriptor { -template +template struct field_t { - constexpr static auto id = Id::value; - constexpr static auto is_signed = Is_Signed::value; - constexpr static auto width = Width::value; - constexpr static auto ids = std::tuple{}; - - using underlying = xinteger; + constexpr static auto tag = field_tag::pure_field; + constexpr static auto identifier = Identifier; + constexpr static auto is_signed = Signedness; + constexpr static auto width = Width; }; -template -[[nodiscard]] constexpr auto field(signedness_t, width_t) noexcept { - using id = detail::integral_constant_t; - using is_signed = detail::integral_constant_t; - using width = detail::integral_constant_t; +template +[[nodiscard]] constexpr auto field(signedness_t, width_t) noexcept(release) { + auto retval = field_t{}; + return description{tuple{retval}}; +} + +template +struct constant_t { + constexpr static auto tag = field_tag::constant; + constexpr static auto identifier = Identifier; + constexpr static auto is_signed = false; + constexpr static auto width = Width; + + xuint value; +}; - auto retval = field_t{}; - return description{retval}; +template +[[nodiscard]] constexpr auto constant(T n, width_t) noexcept(release) { + auto retval = constant_t{}; + return description{tuple{retval}}; } } // namespace upd::descriptor diff --git a/include/upd/detail/is_instance_of.hpp b/include/upd/detail/is_instance_of.hpp index 869a315..f39810c 100644 --- a/include/upd/detail/is_instance_of.hpp +++ b/include/upd/detail/is_instance_of.hpp @@ -13,4 +13,10 @@ struct is_instance_of, TT> : std::true_type {}; template typename TT> constexpr auto is_instance_of_v = is_instance_of::value; +template typename TT> +struct is_instance_of_partial { + template + using type = is_instance_of; +}; + } // namespace upd::detail diff --git a/include/upd/detail/variadic/diff.hpp b/include/upd/detail/variadic/diff.hpp index 18d665d..d522329 100644 --- a/include/upd/detail/variadic/diff.hpp +++ b/include/upd/detail/variadic/diff.hpp @@ -9,21 +9,22 @@ 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)); + 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 lhs = std::tuple; + using clipped_lhs = clip_t; - using rhs = std::tuple; - using clipped_rhs = 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...>{}; - } + 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{})); + using type = + decltype(impl(std::declval(), std::declval(), std::make_index_sequence{})); }; template diff --git a/include/upd/detail/variadic/equals.hpp b/include/upd/detail/variadic/equals.hpp index fca6a99..587244a 100644 --- a/include/upd/detail/variadic/equals.hpp +++ b/include/upd/detail/variadic/equals.hpp @@ -6,7 +6,15 @@ template struct equals; template -struct equals, std::tuple>: std::bool_constant<((Ts::value == Us::value) && ...)> {}; +struct equals, std::tuple> { + constexpr static auto value = [] { + if constexpr (sizeof...(Ts) == sizeof...(Us)) { + return ((Ts::value == Us::value) && ...); + } else { + return false; + } + }(); +}; template constexpr auto equals_v = equals::value; diff --git a/include/upd/detail/variadic/filter.hpp b/include/upd/detail/variadic/filter.hpp new file mode 100644 index 0000000..a773906 --- /dev/null +++ b/include/upd/detail/variadic/filter.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "clean.hpp" + +namespace upd::detail::variadic { + +template typename> +struct filter; + +template typename F> +struct filter, F> { + using marked = std::tuple::value, Ts, marked_for_cleaning_t>...>; + using type = clean_t; +}; + +template typename F> +using filter_t = typename filter::type; + +template +struct filterf; + +template +struct filterf, F> { + using marked = std::tuple::value, Ts, marked_for_cleaning_t>...>; + using type = clean_t; +}; + +template +using filterf_t = typename filterf::type; + +} // namespace upd::detail::variadic \ No newline at end of file diff --git a/include/upd/detail/variadic/find.hpp b/include/upd/detail/variadic/find.hpp index 1b06c37..973d8eb 100644 --- a/include/upd/detail/variadic/find.hpp +++ b/include/upd/detail/variadic/find.hpp @@ -11,7 +11,8 @@ template struct find; template -struct find, U> : decltype(aggregated_leaves(std::declval>(), std::make_index_sequence{}) +struct find, U> + : decltype(aggregated_leaves(std::declval>(), std::make_index_sequence{}) .find((U *)nullptr)) {}; template diff --git a/include/upd/detail/variadic/leaf.hpp b/include/upd/detail/variadic/leaf.hpp index 43e4991..d20f99c 100644 --- a/include/upd/detail/variadic/leaf.hpp +++ b/include/upd/detail/variadic/leaf.hpp @@ -4,8 +4,8 @@ #include #include "../../index_type.hpp" -#include "../integral_constant.hpp" #include "../../literals.hpp" +#include "../integral_constant.hpp" #include "../range.hpp" namespace upd::detail::variadic { diff --git a/include/upd/detail/variadic/map.hpp b/include/upd/detail/variadic/map.hpp index 7599cdc..dc1df5d 100644 --- a/include/upd/detail/variadic/map.hpp +++ b/include/upd/detail/variadic/map.hpp @@ -4,8 +4,8 @@ #include #include -#include "flatten.hpp" #include "all_of.hpp" +#include "flatten.hpp" namespace upd::detail::variadic { @@ -58,7 +58,7 @@ using flatmapf_t = typename mapf::type; template constexpr void for_each(Tuple, F) { using are_voids = map_t; - + static_assert(all_of_v, "`F` should always return `void`"); } diff --git a/include/upd/detail/variadic/to_array.hpp b/include/upd/detail/variadic/to_array.hpp index b72af2b..aa1cbda 100644 --- a/include/upd/detail/variadic/to_array.hpp +++ b/include/upd/detail/variadic/to_array.hpp @@ -7,7 +7,7 @@ struct to_array; template struct to_array> { - constexpr static auto value = std::array{Ts::value...}; + constexpr static auto value = std::array{Ts::value...}; }; template diff --git a/include/upd/integer.hpp b/include/upd/integer.hpp index c60b3dd..d631393 100644 --- a/include/upd/integer.hpp +++ b/include/upd/integer.hpp @@ -1,25 +1,25 @@ #pragma once +#include +#include #include #include #include -#include -#include #include -#include +#include -#include "detail/integral_constant.hpp" +#include "detail/always_false.hpp" #include "detail/has_value_member.hpp" -#include "literals.hpp" +#include "detail/integral_constant.hpp" #include "detail/is_instance_of.hpp" +#include "literals.hpp" #include "token.hpp" -#include "detail/always_false.hpp" #include "upd.hpp" namespace upd::detail { template -constexpr auto bitmask_v = ((T) 1 << Bitsize) - 1; +constexpr auto bitmask_v = ((T)1 << Bitsize) - 1; } // namespace upd::detail @@ -41,6 +41,15 @@ constexpr auto to_extended_integer(T) noexcept; template constexpr auto decompose_into_xuint(XInteger) UPD_NOEXCEPT; +template +[[nodiscard]] constexpr auto reduce_scalar(Scalar, std::size_t) noexcept; + +template +[[nodiscard]] constexpr auto reduce_integer(Integer n, std::size_t bitsize) noexcept; + +template +[[nodiscard]] constexpr auto reduce_enum(Enum e, std::size_t bitsize) noexcept; + template using xinteger = extended_integer, Underlying>; @@ -75,10 +84,10 @@ class extended_integer { static_assert(detail::has_value_member_v, "`Bitsize` must has a `value` member"); static_assert(std::is_integral_v, "`Bitsize::value` must be an integer"); - static_assert(Bitsize::value >= 0, - "`Bitsize::value` must be a positive integer"); + static_assert(Bitsize::value >= 0, "`Bitsize::value` must be a positive integer"); static_assert(std::is_integral_v, "`Underlying_T` must be an integral type"); - static_assert(Bitsize::value <= std::numeric_limits::digits, "`Bitsize::value` should be lesser or equal to the number of digits in `Underlying_T`"); + static_assert(Bitsize::value <= std::numeric_limits::digits, + "`Bitsize::value` should be lesser or equal to the number of digits in `Underlying_T`"); public: using underlying = Underlying_T; @@ -87,22 +96,10 @@ class extended_integer { constexpr static auto is_signed = std::is_signed_v; constexpr extended_integer() noexcept = default; - + template - constexpr extended_integer(T n) noexcept(release) { - if constexpr (std::is_integral_v) { - using pow2_type = std::conditional_t, std::intmax_t, std::uintmax_t>; - auto pow2 = (pow2_type) 1 << bitsize; - m_value = static_cast(n % pow2); - } else if constexpr (std::is_enum_v) { - using enum_underlying = std::underlying_type_t; - using pow2_type = std::conditional_t, std::intmax_t, std::uintmax_t>; - auto value = static_cast(n); - auto pow2 = (pow2_type) 1 << bitsize; - m_value = static_cast(value % pow2); - } else { - static_assert(UPD_ALWAYS_FALSE, "`n` must be an integer or an enumerator"); - } + 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"); } template || std::is_enum_v>> @@ -112,14 +109,13 @@ class extended_integer { return static_cast(m_value); } else { using underlying = std::underlying_type_t; - static_assert(std::numeric_limits::digits >= bitsize, "`T` cannot hold an integer with `Bitsize` bits"); + static_assert(std::numeric_limits::digits >= bitsize, + "`T` cannot hold an integer with `Bitsize` bits"); return static_cast(m_value); } } - [[nodiscard]] constexpr auto value() const noexcept -> underlying { - return m_value; - } + [[nodiscard]] constexpr auto value() const noexcept -> underlying { return m_value; } template [[nodiscard]] constexpr auto decompose(std::integral_constant) const noexcept { @@ -131,7 +127,7 @@ class extended_integer { template [[nodiscard]] constexpr auto decompose(width_t) const UPD_NOEXCEPT { using byte = xinteger; - + return decompose_into_xuint(*this); } @@ -148,9 +144,7 @@ class extended_integer { } } - [[nodiscard]] constexpr auto signbit() const noexcept -> bool { - return std::signbit(m_value); - } + [[nodiscard]] constexpr auto signbit() const noexcept -> bool { return std::signbit(m_value); } template [[nodiscard]] constexpr auto resize(width_t) const noexcept { @@ -196,25 +190,25 @@ class extended_integer { [[nodiscard]] constexpr auto operator~() const noexcept { static_assert(!is_signed, "Bitwise operators only work on unsigned values"); - + return xinteger{~m_value}; } - constexpr auto operator<<=(std::size_t offset) noexcept -> extended_integer& { + constexpr auto operator<<=(std::size_t offset) noexcept -> extended_integer & { static_assert(!is_signed, "Bitwise operators only work on unsigned values"); - + m_value <<= offset; return *this; } template - constexpr auto operator|=(T rhs) noexcept -> extended_integer& { + constexpr auto operator|=(T rhs) noexcept -> extended_integer & { auto xrhs = to_extended_integer(rhs); - + static_assert(!is_signed && !xrhs.is_signed, "Bitwise operators only work on unsigned values"); static_assert(xrhs.bitsize <= bitsize, "Such an operation would cause a narrowing conversion"); - + m_value |= xrhs.m_value; return *this; @@ -285,7 +279,7 @@ template template [[nodiscard]] constexpr auto recompose_into_xuint(const std::array &byteseq) noexcept { using namespace upd::literals; - + auto retval = xuint * N>{0}; auto first = byteseq.rbegin(); auto last = byteseq.rend(); @@ -299,4 +293,39 @@ template return retval; } +template +[[nodiscard]] constexpr auto reduce_scalar(Scalar s, std::size_t bitsize) noexcept { + static_assert(std::is_scalar_v, "`s` must be a scalar type"); + + if constexpr (std::is_integral_v) { + return reduce_integer(s, bitsize); + } else if constexpr (std::is_enum_v) { + return reduce_enum(s, bitsize); + } else { + static_assert(UPD_ALWAYS_FALSE, "`s` has an unsupported type"); + } +} + +template +[[nodiscard]] constexpr auto reduce_integer(Integer n, std::size_t bitsize) noexcept { + static_assert(std::is_integral_v, "`n` must be an integer"); + + using pow2_type = std::conditional_t, std::intmax_t, std::uintmax_t>; + + auto pow2 = (pow2_type)1 << bitsize; + + return n % pow2; +} + +template +[[nodiscard]] constexpr auto reduce_enum(Enum e, std::size_t bitsize) noexcept { + static_assert(std::is_enum_v, "`e` must be an enumerator"); + + using underlying = std::underlying_type_t; + + auto value = static_cast(e); + + return reduce_integer(value, bitsize); +} + } // namespace upd diff --git a/include/upd/named_tuple.hpp b/include/upd/named_tuple.hpp index 0788baa..efcc49b 100644 --- a/include/upd/named_tuple.hpp +++ b/include/upd/named_tuple.hpp @@ -11,8 +11,8 @@ #include "detail/integral_constant.hpp" #include "detail/tuple_operations.hpp" #include "detail/variadic/concat.hpp" -#include "detail/variadic/find.hpp" #include "detail/variadic/count.hpp" +#include "detail/variadic/find.hpp" #include "integer.hpp" #include "upd.hpp" @@ -69,10 +69,8 @@ class named_tuple { } template - constexpr void serialize(Serializer &ser, OutputIt output) { - auto serialize_pack = [&] (const auto &... xs) { - (xs.serialize(ser, output), ...); - }; + constexpr void serialize(Serializer &ser, OutputIt output) { + auto serialize_pack = [&](const auto &...xs) { (xs.serialize(ser, output), ...); }; std::apply(serialize_pack, m_content); } @@ -85,10 +83,10 @@ class named_tuple { }; template -struct is_named_tuple_instance: std::false_type {}; +struct is_named_tuple_instance : std::false_type {}; template -struct is_named_tuple_instance>: std::true_type {}; +struct is_named_tuple_instance> : std::true_type {}; template [[nodiscard]] constexpr auto name_tuple(std::tuple tuple) noexcept { diff --git a/include/upd/named_value.hpp b/include/upd/named_value.hpp index c0ce701..446a4c6 100644 --- a/include/upd/named_value.hpp +++ b/include/upd/named_value.hpp @@ -12,12 +12,13 @@ #include "detail/tuple_operations.hpp" #include "detail/variadic/concat.hpp" #include "integer.hpp" -#include "upd.hpp" #include "named_tuple.hpp" +#include "upd.hpp" namespace upd { -template struct kw_t; +template +struct kw_t; template constexpr auto kw = kw_t>{}; @@ -61,7 +62,7 @@ struct named_value { } template - [[nodiscard]] constexpr auto operator,(NamedObject nobj) && noexcept { + [[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)}; return name_tuple(std::move(values)); diff --git a/include/upd/tuple.hpp b/include/upd/tuple.hpp index 18ed54f..8b44ca0 100644 --- a/include/upd/tuple.hpp +++ b/include/upd/tuple.hpp @@ -1,449 +1,602 @@ -//! \file - #pragma once -#include -#include -#include - -#include "detail/serialization.hpp" -#include "detail/type_traits/conjunction.hpp" -#include "detail/type_traits/index_sequence.hpp" -#include "detail/type_traits/require.hpp" -#include "detail/type_traits/signature.hpp" -#include "detail/type_traits/typelist.hpp" -#include "format.hpp" -#include "type.hpp" -#include "typelist.hpp" -#include "upd.hpp" +#include "detail/variadic/at.hpp" +#include "detail/variadic/leaf.hpp" namespace upd { -template -class tuple_view; +struct encapsulate_t {}; + +constexpr auto encapsulate = encapsulate_t{}; + +template +struct operator_invoker { + constexpr static auto op = Operator{}; + + template + [[nodiscard]] constexpr auto operator()(const Lhs &lhs, const Rhs &rhs) const -> decltype(auto) { + return op(lhs, 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 equal_to = operator_invoker>{}; +constexpr inline auto plus = operator_invoker>{}; + +} // namespace upd + +namespace upd { -namespace detail { +template +struct auto_constant { + using type = auto_constant; + using value_type = std::remove_cv_t>; + + constexpr static auto value = [] { + if constexpr (std::is_invocable_v) { + return Value(); + } else { + return Value; + } + }(); + + template + [[nodiscard]] constexpr operator T() { + return static_cast(value); + } +}; + +template +[[nodiscard]] constexpr auto operator==(auto_constant, auto_constant) noexcept -> decltype(auto) { + return auto_constant{}; +} + +template +[[nodiscard]] constexpr auto operator==(auto_constant, Rhs &&rhs) -> decltype(auto) { + return Lhs == UPD_FWD(rhs); +} + +template +[[nodiscard]] constexpr auto operator==(Lhs &&lhs, auto_constant) -> decltype(auto) { + return UPD_FWD(lhs) == Rhs; +} + +template +[[nodiscard]] constexpr auto operator<(auto_constant, auto_constant) noexcept -> decltype(auto) { + return auto_constant < Lhs{}; +} + +template +[[nodiscard]] constexpr auto operator<(auto_constant, Rhs &&rhs) -> decltype(auto) { + return Lhs < UPD_FWD(rhs); +} -template -class tuple_base; // IWYU pragma: keep +template +[[nodiscard]] constexpr auto operator<(Lhs &&lhs, auto_constant) -> decltype(auto) { + return UPD_FWD(lhs) < Rhs; +} -//! \name -//! \brief Normalize some type before being passed to a function -//! -//! For now, the only types to normalize are `std::array` instances (to their corresponding plain array-type) -// \warning Be careful when using these functions as they do not extent the lifetime of their parameter. -//! -//! @{ +template +[[nodiscard]] constexpr auto operator+(auto_constant, auto_constant) noexcept -> decltype(auto) { + return auto_constant{}; +} -template::value)> -auto normalize(std::array &&array) -> T (&&)[N] { - return reinterpret_cast(*array.data()); +template +[[nodiscard]] constexpr auto operator+(auto_constant, Rhs &&rhs) -> decltype(auto) { + return Lhs + UPD_FWD(rhs); } -template(nullptr)), - Target_T>::value> = 0> -T &&normalize(T &&x) { - return UPD_FWD(x); + +template +[[nodiscard]] constexpr auto operator+(Lhs &&lhs, auto_constant) -> decltype(auto) { + return UPD_FWD(lhs) + Rhs; } -//! @} +template +[[nodiscard]] constexpr auto operator-(auto_constant, auto_constant) noexcept -> decltype(auto) { + return auto_constant{}; +} -//! \brief Return the size in bytes occupied by the serialization of instances of the provided type (if serializable) -template = 0> -constexpr std::size_t serialization_size_impl(...) { - return sizeof(T); +template +[[nodiscard]] constexpr auto operator-(auto_constant, Rhs &&rhs) -> decltype(auto) { + return Lhs - UPD_FWD(rhs); } -template = 0> -constexpr std::size_t serialization_size_impl(int) { - return decltype(make_view_for( - (byte_t *)nullptr, examine_invocable::unserialize)>{}))::size; + +template +[[nodiscard]] constexpr auto operator-(Lhs &&lhs, auto_constant) -> decltype(auto) { + return UPD_FWD(lhs) - Rhs; } -//! \brief Return the size in bytes occupied by the serialization of instances of the provided type (if serializable) +template +struct is_auto_constant : std::false_type {}; + +template +struct is_auto_constant> : std::true_type {}; + +template +constexpr auto is_auto_constant_v = is_auto_constant::value; + +template +class tuple; + +template +class typelist; + +template +class constlist; + +template +using indexlist = constlist; + template -struct serialization_size { - constexpr static auto value = serialization_size_impl(0); +struct typebox { + using type = T; + + constexpr auto operator->() const noexcept -> const type * { return nullptr; } +}; + +} // namespace upd + +template +struct std::tuple_size> { + constexpr static auto value = sizeof...(Ts); +}; + +template +struct std::tuple_element> { + using type = upd::detail::variadic::at_t, I>; +}; + +template +struct std::tuple_size> { + constexpr static auto value = sizeof...(Ts); +}; + +template +struct std::tuple_element> { + using type = upd::detail::variadic::at_t, I>; +}; + +template +struct std::tuple_size> { + constexpr static auto value = sizeof...(Vs); }; -//! \brief Make a tuple view according to a typelist -template -auto make_view_from_typelist(const It &it, detail::tlist_t) - -> decltype(tuple_view{it}) { - return tuple_view{it}; +template +struct std::tuple_element> { + using type = upd::detail::variadic::at_t...>, I>; +}; + +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{}...); } -//! \brief Provides tuple features to the derived class throught CRTP -//! \tparam D Type of the derived class -//! \tparam Endianess endianess of the stored data -//! \tparam Signed_Mode signed mode of the stored data -//! \tparam Ts... Types of the serialized values -template -class tuple_base { - D &derived() { return static_cast(*this); } - const D &derived() const { return static_cast(*this); } - - static_assert(detail::conjunction::value && !std::is_volatile::value && - !std::is_reference::value)>...>::value, - "Type parameters cannot be cv-qualified or ref-qualified."); - - static_assert(detail::conjunction...>::value, - "Some of the provided types are not serializable (serializable types are integer types, types with " - "user-defined extension and array types of any of these)"); +template +struct leaf { + [[nodiscard]] constexpr auto at(auto_constant) &noexcept -> T & { return value; } -public: - //! \brief Typelist holding `Ts...` - using types_t = upd::typelist_t; + [[nodiscard]] constexpr auto at(auto_constant) const &noexcept -> const T & { return value; } + + [[nodiscard]] constexpr auto at(auto_constant) &&noexcept -> T && { return std::move(value); } + + [[nodiscard]] constexpr auto at(auto_constant) const &&noexcept -> const T && { return std::move(value); } + + [[nodiscard]] constexpr static auto typebox_at(auto_constant) noexcept -> typebox; + + T value; +}; + +template +struct leaves; - //! \brief Typelist holding the sizes in byte of each type of `Ts...` when serialized - using sizes_t = upd::typelist_t...>; +template +struct leaves, Ts...> : leaf... { + using leaf::at...; + using leaf::typebox_at...; - //! \brief Type of one of the serialized values - //! \tparam I Index of the requested type in `Ts...` template - using arg_t = detail::at; + using raw_type = typename decltype(typebox_at(auto_constant{}))::type; + + constexpr static auto size = sizeof...(Ts); + + explicit constexpr leaves() = default; + + explicit constexpr leaves(Ts... xs) : leaf{UPD_FWD(xs)}... {} + + [[nodiscard]] constexpr auto at(...) const noexcept -> variadic::not_found_t { return variadic::not_found; } +}; + +} // 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 &&; + +template +[[nodiscard]] constexpr auto fittest_tuple_like(Ts &&...); + +template +[[nodiscard]] constexpr auto apply(Tuple &&, F &&); - //! \brief Storage size in byte - constexpr static auto size = detail::sum::value; +template +constexpr void for_each(Tuple &&, F &&); - //! \brief Equals the endianess given as template parameter - constexpr static auto storage_endianess = Endianess; +template +class tuple_implementation { + constexpr static auto normalize = [](auto &&...xs) noexcept { return fittest_tuple_like(UPD_FWD(xs)...); }; - //! \brief Equals the signed mode given as template parameter - constexpr static auto storage_signed_mode = Signed_Mode; + [[nodiscard]] constexpr auto derived() noexcept -> Derived & { return reinterpret_cast(*this); } - //! \brief Copy each element of a tuple-like object into the content - //! \param t Tuple-like object to copy from - template - D &operator=(Tuple &&t) { - lay_tuple(make_index_sequence{}, UPD_FWD(t)); - return derived(); + [[nodiscard]] constexpr auto derived() const noexcept -> const Derived & { + return reinterpret_cast(*this); } - //! \brief Unserialize one of the value held by the object - //! \tparam I Index of the requested value - //! \return A copy of the serialized value or a `std::array` instance if `I` designates an array type -#ifdef DOXYGEN - template - auto get() const; -#else - template - decltype(read_as, Endianess, Signed_Mode>(nullptr)) get() const { - using detail::clip; - using detail::sum; +public: + template + [[nodiscard]] constexpr auto operator[](I i) &noexcept -> auto & { + return get(derived()); + } - constexpr auto offset = sum>::value; - return read_as, Endianess, Signed_Mode>(derived().src(), offset); + template + [[nodiscard]] constexpr auto operator[](I i) const &noexcept -> const auto & { + return get(derived()); } -#endif - //! \brief Set one of the value held by the object - //! \tparam I Index of the value which will be set - //! \param value Value to be copied from - template - void set(const arg_t &value) { - using detail::clip; - using detail::sum; + template + [[nodiscard]] constexpr auto operator[](I i) &&noexcept -> auto && { + return get(std::move(derived())); + } + + template + [[nodiscard]] constexpr auto operator[](I i) const &&noexcept -> const auto && { + return get(std::move(derived())); + } + + template + [[nodiscard]] constexpr auto at(I i) &noexcept -> auto & { + return get(derived()); + } + + template + [[nodiscard]] constexpr auto at(I i) const &noexcept -> const auto & { + return get(derived()); + } + + template + [[nodiscard]] constexpr auto at(I i) &&noexcept -> auto && { + return get(std::move(derived())); + } + + template + [[nodiscard]] constexpr auto at(I i) const &&noexcept -> const auto && { + return get(std::move(derived())); + } + + [[nodiscard]] constexpr auto flatten() const { + struct subelement_index { + std::size_t sub_index; + std::size_t index; + }; + + auto get_sub_size = [](auto sub) { return sub.size(); }; + auto retval_size = derived().transform_const(get_sub_size).fold_const(auto_constant<0>{}, plus); + + auto make_shape = [&](auto... xs) { + auto shape = std::array{}; + auto cursor = shape.begin(); + auto sub_index = std::size_t{0}; + + auto add_sub_shape = [&](const auto &sub) { + for (auto i = std::size_t{0}; i < sub.size(); ++i, ++cursor) { + *cursor = subelement_index{sub_index, i}; + } + + ++sub_index; + }; + + (add_sub_shape(xs), ...); + + return shape; + }; + + auto shape = derived().apply_const(make_shape, encapsulate); + auto shape_seq = sequence; + auto get_subelement = [&](auto i) -> auto && { + constexpr auto subelement_index = shape()[i]; + auto sub_index = auto_constant{}; + auto index = auto_constant{}; + + return UPD_FWD(derived()).at(sub_index).at(index); + }; - constexpr auto offset = sum>::value; - write_as(value, derived().src(), offset); + return shape_seq.transform(get_subelement); + } + + template + [[nodiscard]] constexpr auto fold_const(Init init, BinaryOp op) const noexcept { + auto impl = [](auto init, auto op, auto... xs) { + auto acc = init.value; + ((void)(acc = op(acc, xs)), ...); + + return acc; + }; + + auto invoke_impl_const = [&](auto... xs) { + constexpr auto result = impl(init, op, xs...); + return auto_constant{}; + }; + + return derived().apply(invoke_impl_const); + } + + template typename TT> + [[nodiscard]] constexpr static auto metatransform() noexcept { + auto apply_and_box_type = [](auto i) { + using raw = typename Derived::template raw_type; + using result = TT; + + return typebox{}; + }; + + return sequence.transform(apply_and_box_type); + } + + [[nodiscard]] constexpr auto square() { + auto seq = sequence; + auto pair_up = [&](auto i) { return seq.transform([&](auto j) { return indexlist{}; }); }; + + auto squared_seq = seq.transform(pair_up).flatten(); + + auto make_pair = [&](auto ipair) { + auto [i, j] = ipair; + return std::pair{at(i), at(j)}; + }; + + return squared_seq.transform(make_pair); } - //! \brief Invoke a functor with the stored values - //! \param ftor Callback to be invoked - //! \return `ftor(upd::get()...)` with `Is` = `0`, `1`, ..., `sizeof...(Ts)` -#ifdef DOXYGEN template - auto invoke(F &&ftor) const; -#else + [[nodiscard]] constexpr auto transform(F &&f) & { + return transform_and_apply(derived(), UPD_FWD(f), normalize); + } + template - detail::return_t invoke(F &&ftor) const { - return invoke_impl(UPD_FWD(ftor), make_index_sequence{}); + [[nodiscard]] constexpr auto transform(F &&f) const & { + return transform_and_apply(derived(), UPD_FWD(f), normalize); } -#endif -protected: - //! \brief Serialize values into the object content - template - void lay(detail::index_sequence, const Args &...args) { - using discard = int[]; - (void)discard{0, (set(args), 0)...}; + template + [[nodiscard]] constexpr auto transform(F &&f) && { + return transform_and_apply(std::move(derived()), UPD_FWD(f), normalize); } - //! \brief Lay the element of a tuple-like object into the content - template - void lay_tuple(detail::index_sequence is, T &&t) { - lay(is, t.template get()...); + template + [[nodiscard]] constexpr auto transform(F &&f) const && { + return transform_and_apply(std::move(derived()), UPD_FWD(f), normalize); } -private: - //! \brief Unserialize the tuple content and forward it as parameters to the provided functor - template - detail::return_t invoke_impl(F &&ftor, detail::index_sequence) const { - return UPD_FWD(ftor)(detail::normalize(get())...); + template + [[nodiscard]] constexpr auto transform_const(F f) const noexcept { + auto apply_const = [&](auto x) { return auto_constant{}; }; + + return derived().transform(apply_const); } -}; -} // namespace detail - -//! \brief Call the member function `get()` from an Unpadded tuple-like instance -//! -//! This function create a coherent interface with std::tuple for the sake of genericity. -//! -//! \param t Tuple to get a value from -#if defined(DOXYGEN) -template -auto get(const detail::tuple_base &t) -#else // defined(DOXYGEN) -template -decltype(std::declval>().template get()) -get(const detail::tuple_base &t) -#endif // defined(DOXYGEN) -{ - return t.template get(); -} + template + [[nodiscard]] constexpr auto transform_type(F &&) const noexcept { + auto apply_and_box_type = [](auto i) { + using raw = typename Derived::template raw_type; + using result = std::invoke_result_t; -//! \brief Call the member function `set()` from an Unpadded tuple-like instance -//! -//! This function create a coherent interface std::tuple for the sake of genericity -//! -//! \param t Tuple to set a value in -//! \param value Value to set -template -void set(detail::tuple_base &t, U &&value) { - return t.template set(UPD_FWD(value)); -} + return typebox{}; + }; -//! \brief Binds a byte sequence to a tuple view -//! -//! Once bound, the byte sequence content can be read and modified as it were the content of a \ref tuple -//! instance. The byte sequence and the tuple view are bound through an iterator. It must at least be a forward -//! iterator, but tuple views are faster with random access iterators. Best case would be a non-volatile plain pointer, -//! as it can be called with memcpy. \tparam It Type of the iterator used for binding with the byte sequence \tparam -//! Endianess, Signed_Mode Serialization parameters \tparam Ts... Types of the serialized values -template -class tuple_view - : public detail::tuple_base, Endianess, Signed_Mode, Ts...> { - using base_t = detail::tuple_base, Endianess, Signed_Mode, Ts...>; + return sequence.transform(apply_and_box_type); + } -public: - using base_t::operator=; + [[nodiscard]] constexpr static auto type_only() noexcept { + auto apply_and_box_type = [](auto i) { + using raw = typename Derived::template raw_type; + return typebox{}; + }; - //! \brief Bind the view to a byte sequence through an iterator - //! \param src Iterator to the start of the byte sequence - explicit tuple_view(const It &src) : m_begin{src}, m_end{src} { std::advance(m_end, base_t::size); } + return sequence.transform(apply_and_box_type); + } - //! \brief Beginning of the byte sequence - const It &begin() const { return m_begin; } + template + [[nodiscard]] constexpr auto apply(F &&f) & -> decltype(auto) { + return upd::apply(derived(), UPD_FWD(f)); + } - //! \brief End of the byte sequence - const It &end() const { return m_end; } - //! @} + template + [[nodiscard]] constexpr auto apply(F &&f) const & -> decltype(auto) { + return upd::apply(derived(), UPD_FWD(f)); + } - //! \copydoc begin() - const It &src() const { return m_begin; } + template + [[nodiscard]] constexpr auto apply(F &&f) && -> decltype(auto) { + return upd::apply(std::move(derived()), UPD_FWD(f)); + } -private: - It m_begin, m_end; + template + [[nodiscard]] constexpr auto apply(F &&f) const && -> decltype(auto) { + return upd::apply(std::move(derived()), UPD_FWD(f)); + } + + template + [[nodiscard]] constexpr auto apply_const(F f, encapsulate_t) const noexcept { + auto impl = [&](auto... xs) { + constexpr auto result = f(xs...); + return [&] { return result; }; + }; + + return derived().apply(impl); + } + + template + constexpr void for_each(F &&f) & { + upd::for_each(derived(), UPD_FWD(f)); + } + + template + constexpr void for_each(F &&f) const & { + upd::for_each(derived(), UPD_FWD(f)); + } + + template + constexpr void for_each(F &&f) && { + upd::for_each(std::move(derived()), UPD_FWD(f)); + } + + template + constexpr void for_each(F &&f) const && { + upd::for_each(std::move(derived()), UPD_FWD(f)); + } + + [[nodiscard]] constexpr static auto size() noexcept -> std::size_t { return std::tuple_size_v; } }; -//! \brief Bind a byte sequence to a tuple view -//! \tparam Ts... Types held by the tuple -//! \tparam Endianess Target endianess for serialization -//! \tparam Signed_Mode Target signed representation for serialization -//! \param src Start of the byte sequence -//! \return a \ref tuple_view instance bound to the byte sequence -//! \related tuple_view -template -tuple_view -make_view(endianess_h, signed_mode_h, const It &src) { - return tuple_view{src}; -} +template +class tuple : public tuple_implementation> { + template + friend constexpr auto get(Tuple &&) noexcept -> auto &&; -//! \upd_doc{MakeView_Tuple} -//! \brief Bind a view to a slice of a \ref tuple -//! \tparam I First element in the view -//! \tparam L Number of elements in the view -//! \param tuple Tuple to bind the view to -//! \return a \ref tuple_view including every elements in the given range -//! \related tuple_view - -//! \copydoc MakeView_Tuple -#if defined(DOXYGEN) -template -auto make_view(tuple &tuple) -#else // defined(DOXYGEN) -template -auto make_view(tuple &tuple) -> decltype(tuple.template view()) -#endif // defined(DOXYGEN) -{ - return tuple.template view(); -} + using leaves = detail::leaves, Ts...>; -//! \copydoc MakeView_Tuple -#if defined(DOXYGEN) -template -auto make_view(const tuple &tuple) -#else // defined(DOXYGEN) -template -auto make_view(const tuple &tuple) -> decltype(tuple.template view()) -#endif // defined(DOXYGEN) -{ - return tuple.template view(); -} +public: + template + using raw_type = typename leaves::template raw_type; + + template + [[nodiscard]] constexpr static auto normalize(Tuple &&tuple) { + return fittest_tuple_like(UPD_FWD(tuple)); + } -//! \brief Unaligned storage tuple -//! -//! \ref tuple instances hold values like a tuple but in an unaligned manner (i.e., there is no padding between -//! two consecutive values). -//! -//! \tparam Endianess, Signed_Mode Serialization parameters -//! \tparam Ts... Types of the serialized values -template -class tuple : public detail::tuple_base, Endianess, Signed_Mode, Ts...> { - using base_t = detail::tuple_base, Endianess, Signed_Mode, Ts...>; - using types_t = typename base_t::types_t; - using sizes_t = typename base_t::sizes_t; + explicit constexpr tuple(Ts... xs) noexcept : m_leaves{std::move(xs)...} {} + +private: + leaves m_leaves; +}; + +template +class typelist : public tuple_implementation> { + template + friend constexpr auto get(Tuple &&) noexcept -> auto &&; + + using leaves = detail::leaves, typebox...>; public: - using base_t::operator=; - - //! \brief Initialize the internal storage with default constructed values - tuple() : tuple(Ts{}...) {} - - //! \brief Serialize the provided values - //! \param args... Values to be serialized - explicit tuple(const Ts &...args) { base_t::lay(detail::make_index_sequence{}, args...); } - -#if __cplusplus >= 201703L - //! \brief (C++17) Serialize the provided values - //! - //! \tparam Endianess, Signed_Mode Serialization parameters - //! \param values... Values to be serialized - explicit tuple(endianess_h, signed_mode_h, const Ts &...values) : tuple(values...) {} -#endif // __cplusplus >= 201703L - - //! \brief Beginning of the internal storage - byte_t *begin() { return m_storage; } - - //! \copydoc begin() - const byte_t *begin() const { return m_storage; } - - //! \brief End of the internal storage - byte_t *end() { return m_storage + base_t::size; } - - //! \copydoc end() - const byte_t *end() const { return m_storage + base_t::size; } - - //! \brief Access the object content - //! - //! \warning There is no bound check performed. - //! - //! \param i Index of the accessed byte - byte_t &operator[](std::size_t i) { return m_storage[i]; } - - //! \brief Access the object content - //! - //! \warning There is no bound check performed. - //! - //! \param i Index of the accessed byte - const byte_t &operator[](std::size_t i) const { return m_storage[i]; } - - //! \copydoc begin() - byte_t *src() { return begin(); } - - //! \copydoc begin() - const byte_t *src() const { return begin(); } - - //! \brief Make a view out of a subset of the tuple - //! \tparam I First element in the view - //! \tparam L Number of elements in the view - //! \return a \ref tuple_view instance including every elements in the given range -#if defined(DOXYGEN) - template - auto view() -#else // defined(DOXYGEN) - template - decltype(detail::make_view_from_typelist((byte_t *)nullptr, detail::clip{})) - view() -#endif // defined(DOXYGEN) - { - using detail::clip; - using detail::sum; - - constexpr auto offset = sum>::value; - - return detail::make_view_from_typelist(begin() + offset, clip{}); - } - - //! \copydoc view -#if defined(DOXYGEN) - template - auto view() const -#else // defined(DOXYGEN) - template - decltype(detail::make_view_from_typelist((const byte_t *)nullptr, - detail::clip{})) - view() const -#endif // defined(DOXYGEN) - { - using detail::clip; - using detail::sum; - constexpr auto offset = sum>::value; - return detail::make_view_from_typelist(begin() + offset, clip{}); + template + using raw_type = typename leaves::template raw_type::type; + + template + [[nodiscard]] constexpr static auto normalize(Tuple &&tuple) { + return fittest_tuple_like(UPD_FWD(tuple)); } + constexpr typelist() noexcept = default; + private: - upd::byte_t m_storage[base_t::size]; + leaves m_leaves; }; -template -class tuple : public detail::tuple_base, Endianess, Signed_Mode> { +template +class constlist : public tuple_implementation> { + template + friend constexpr auto get(Tuple &&) noexcept -> auto &&; + + using leaves = detail::leaves, auto_constant...>; + public: - constexpr byte_t *begin() const { return nullptr; } - constexpr byte_t *end() const { return nullptr; } - constexpr byte_t *src() const { return begin(); } + template + using raw_type = typename leaves::template raw_type; + + template + [[nodiscard]] constexpr static auto normalize(Tuple &&tuple) { + return fittest_tuple_like(UPD_FWD(tuple)); + } + + constexpr constlist() noexcept = default; + +private: + leaves m_leaves; }; -//! \brief Construct a \ref tuple instance from provided values -//! \tparam Endianess, Signed_Mode Serialization parameters -//! \param args... Values to be serialized into the return value -//! \return a \ref tuple instance initialized from `args...` -//! \related tuple -template -tuple -make_tuple(endianess_h, signed_mode_h, const Args &...args) { - return tuple{args...}; +template +[[nodiscard]] constexpr auto fittest_tuple_like(Ts &&...xs) { + if constexpr ((detail::is_instance_of_v, typebox> && ...)) { + return typelist::type...>{}; + } else if constexpr ((is_auto_constant_v && ...)) { + return constlist{}; + } else { + return tuple{UPD_FWD(xs)...}; + } } -//! \brief Construct a \ref tuple instance holding default values -//! \tparam Args... Types of the values held by the tuple -//! \tparam Endianess, Signed_Mode Serialization parameters -//! \related tuple -template -tuple make_tuple(endianess_h, signed_mode_h) { - return tuple{}; +template +[[nodiscard]] constexpr auto operator+(tuple lhs, tuple rhs) noexcept -> tuple { + auto impl = [&](auto i) { + auto lhs_size = auto_constant{}; + + if constexpr (i < sizeof...(Ts)) { + return std::move(lhs[i]); + } else { + return std::move(rhs[i - lhs_size]); + } + }; + + return sequence.transform(impl); } -} // namespace upd +template +[[nodiscard]] constexpr auto get(Tuple &&tuple) noexcept -> auto && { + auto &&retval = UPD_FWD(tuple).m_leaves.at(auto_constant{}); -template -struct std::tuple_size> { - constexpr static auto value = sizeof...(Ts); -}; + using retval_type = decltype(retval); + static_assert(!std::is_same_v, "`I` is not a valid index for `tuple`"); -template -struct std::tuple_element> { - using type = decltype(std::declval>().template get()); -}; + return retval; +} -template -struct std::tuple_size> { - constexpr static auto value = sizeof...(Ts); -}; +template +[[nodiscard]] constexpr auto transform_and_apply(Tuple &&t, F &&f, Aggregator &&agg) { + constexpr auto size = std::tuple_size_v>; + constexpr auto seq = std::make_index_sequence{}; -template -struct std::tuple_element> { - using type = decltype(std::declval>().template get()); -}; + 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); +} + +template +[[nodiscard]] constexpr auto apply(Tuple &&t, F &&f) { + constexpr auto size = std::tuple_size_v>; + constexpr auto seq = std::make_index_sequence{}; + + auto apply_f = [&](auto... is) -> decltype(auto) { return UPD_FWD(f)(get(UPD_FWD(t))...); }; + + return detail::apply_on_index_sequence(apply_f, seq); +} + +template +constexpr void for_each(Tuple &&t, F &&f) { + auto impl = [&](auto &&...xs) { ((void)f(UPD_FWD(xs)), ...); }; + + apply(UPD_FWD(t), impl); +} + +} // namespace upd diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4a15fed..611fde6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -101,7 +101,7 @@ add_custom_target(check_revamp COMMAND ctest -L check_revamp --output-on-failure) add_revamp_test(basic_tuple basic_tuple.cpp) -add_revamp_test(basic_ibytestream basic_ibytestream.cpp) +# add_revamp_test(basic_ibytestream basic_ibytestream.cpp) add_revamp_test(basic_obytestream basic_obytestream.cpp) add_revamp_test(detail_variadic_clean detail/variadic/clean.cpp) add_revamp_test(detail_variadic_clip detail/variadic/clip.cpp) diff --git a/test/basic_ibytestream.cpp b/test/basic_ibytestream.cpp index e708f5d..52545c5 100644 --- a/test/basic_ibytestream.cpp +++ b/test/basic_ibytestream.cpp @@ -63,9 +63,8 @@ TEST_CASE("Deserializing a packet...") { using namespace upd::literals; using namespace upd::descriptor; - auto descr = - field<"a"_h>(upd::signed_int, upd::width<16>) | field<"b"_h>(upd::unsigned_int, upd::width<8>) | - field<"c"_h>(upd::unsigned_int, upd::width<16>) | field<"d"_h>(upd::signed_int, upd::width<8>); + auto descr = field<"a"_h>(upd::signed_int, upd::width<16>) | field<"b"_h>(upd::unsigned_int, upd::width<8>) | + field<"c"_h>(upd::unsigned_int, upd::width<16>) | field<"d"_h>(upd::signed_int, upd::width<8>); auto signed_value = std::array{-64, 1}; auto unsigned_value = std::array{48, 16}; diff --git a/test/integer.cpp b/test/integer.cpp index 9ca34c9..6ccc7a0 100644 --- a/test/integer.cpp +++ b/test/integer.cpp @@ -13,9 +13,7 @@ using namespace fakeit; template struct Catch::StringMaker> { - constexpr static auto convert = [](auto xn) { - return std::to_string(xn.value()); - }; + constexpr static auto convert = [](auto xn) { return std::to_string(xn.value()); }; }; // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) @@ -27,13 +25,12 @@ TEST_CASE("Decompose an extended integer") { using byte16 = upd::xinteger<10, std::uint16_t>; using byte32 = upd::xinteger<2, std::uint32_t>; - auto x = upd::xinteger_fit{0b1011'0000'1001'1100'0111} - .resize(upd::width<20>) - .value(); + auto x = upd::xinteger_fit{0b1011'0000'1001'1100'0111}.resize(upd::width<20>).value(); REQUIRE(upd::decompose_into_xuint(x) == std::array{0b00111, 0b01110, 0b00010, 0b10110}); REQUIRE(upd::decompose_into_xuint(x) == std::array{0b0111000111, 0b1011000010}); - REQUIRE(upd::decompose_into_xuint(x) == std::array{0b11, 0b01, 0b00, 0b11, 0b01, 0b10, 0b00, 0b00, 0b11, 0b10}); + REQUIRE(upd::decompose_into_xuint(x) == + std::array{0b11, 0b01, 0b00, 0b11, 0b01, 0b10, 0b00, 0b00, 0b11, 0b10}); } TEST_CASE("Recompose an extended integer") {