diff --git a/example/dynamixel-protocol-2.0.cpp b/example/dynamixel-protocol-2.0.cpp index 6141e81..39cc10f 100644 --- a/example/dynamixel-protocol-2.0.cpp +++ b/example/dynamixel-protocol-2.0.cpp @@ -159,14 +159,8 @@ int main() { auto oit = std::ostream_iterator{std::cout, " "}; std::cout << std::hex; - auto ping_ex1 = description.instantiate(ser, - "id"_kw = 1, "length"_kw = 3, "instruction"_kw = 1); - if (!ping_ex1) { - return (int)ping_ex1.error(); - } - std::printf("Ping: example 1\n"); - ping_ex1->serialize(ser, oit); + description.encode(("id"_kw = 1, "length"_kw = 3, "instruction"_kw = 1), ser, oit); std::printf("\n\n"); auto answer1_seq = bytearray{0xff, 0xff, 0xfd, 0x00, 0x01, 0x07, 0x00, 0x55, 0x00, 0x06, 0x04, 0x26, 0x65, 0x5d}; diff --git a/include/upd/constexpr.hpp b/include/upd/constexpr.hpp index 28f4512..ee01b66 100644 --- a/include/upd/constexpr.hpp +++ b/include/upd/constexpr.hpp @@ -25,7 +25,7 @@ struct auto_constant { constexpr static auto value = Value; - template + template requires std::convertible_to [[nodiscard]] constexpr operator T() const { return static_cast(value); } diff --git a/include/upd/description.hpp b/include/upd/description.hpp index 816389d..a28fb78 100644 --- a/include/upd/description.hpp +++ b/include/upd/description.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "detail/always_false.hpp" #include "detail/has_value_member.hpp" @@ -22,19 +24,16 @@ #include "tuple.hpp" #include "upd.hpp" -namespace upd::descriptor { - -template -struct field_t; - -template -struct constant_t; - -} // namespace upd::descriptor +#define UPD_WELL_FORMED(...) \ + do { \ + if (requires { __VA_ARGS__; }) { \ + __VA_ARGS__; \ + } \ + } while (false) namespace upd::detail { -template +template class iterator_reference { public: using iterator_type = Iter; @@ -58,10 +57,12 @@ class iterator_reference { return *this; } - constexpr auto operator++() const -> const iterator_reference & { + constexpr auto operator++(int) -> iterator_reference { + auto retval = *this; m_cache = *m_iter; ++m_iter.get(); - return *this; + + return retval; } private: @@ -73,6 +74,99 @@ class iterator_reference { namespace upd { +struct no_error {}; + +struct not_matching_deduction { + const char *identifier; +}; + +using error_data_types = typelist< + no_error, + not_matching_deduction +>; + +template +concept variadic_instance = requires(T x) { + [] typename TT, typename ...Ts>(const TT &) {} (x); +}; + +template +using is_variadic_instance = auto_constant>; + +template +concept value_variadic_instance = requires(T x) { + [] typename TT, auto ...Values>(const TT &) {} (x); +}; + +template +using is_value_variadic_instance = auto_constant>; + +template +concept element_of = variadic_instance +&& instantiate_variadic::has_type(typebox{}); + +template +using is_element_of = auto_constant>; + +template +concept value_element_of = value_variadic_instance +&& instantiate_variadic::has_type(expr); + +template +using is_value_element_of = auto_constant>; + +template +concept error_data = element_of, error_data_types>; + +class error { + using data_type = instantiate_variadic; + + public: + constexpr error() noexcept(release) = default; + constexpr error(const error&) noexcept(release) = default; + constexpr error(error&&) noexcept(release) = default; + + template + constexpr error(ErrorData &&err_data) noexcept(release) : m_data{UPD_FWD(err_data)} {} + + constexpr auto operator=(const error&) noexcept(release) -> error & = default; + constexpr auto operator=(error&&) noexcept(release) -> error & = default; + + template + constexpr auto operator=(ErrorData &&err_data) noexcept(release) -> error& { + m_data = UPD_FWD(err_data); + return *this; + } + + [[nodiscard]] constexpr operator bool() const noexcept(release) { + return !std::holds_alternative(m_data); + } + + private: + data_type m_data; +}; + +template +using result = std::expected; + +template +[[nodiscard]] constexpr auto result_if_no_error(T &&x, const error &err) -> result> { + if (err) { + return std::unexpected{err}; + } else { + return UPD_FWD(x); + } +} + +template +[[nodiscard]] constexpr auto result_if_no_error(T &&x, error &&err) -> result> { + if (err) { + return std::unexpected{std::move(err)}; + } else { + return UPD_FWD(x); + } +} + template class invoker_iterator; @@ -160,98 +254,6 @@ class transformer_iterator { F m_f; }; -enum class error { - none, - checksum_mismatch, - constant_mismatch, -}; - -template -class unexpected { -public: - constexpr unexpected() noexcept = default; - - constexpr unexpected(E e) noexcept : m_error{std::move(e)} {} - - [[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; -}; - -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; } - - [[nodiscard]] constexpr auto operator*() noexcept -> T & { return value(); } - - [[nodiscard]] constexpr auto operator*() const noexcept -> const T & { return value(); } - - [[nodiscard]] constexpr auto operator->() noexcept -> T * { return &value(); } - - [[nodiscard]] constexpr auto operator->() const noexcept -> const T * { return &value(); } - - [[nodiscard]] constexpr auto value() & noexcept(release) -> T & { - auto *v = value_if(); - UPD_ASSERT(v != nullptr); - - return *v; - } - - [[nodiscard]] constexpr auto value() const & noexcept(release) -> const T & { - const auto *v = value_if(); - UPD_ASSERT(v != nullptr); - - return *v; - } - - [[nodiscard]] constexpr auto value() && noexcept(release) -> T && { - auto *v = value_if(); - UPD_ASSERT(v != nullptr); - - return std::move(*v); - } - - [[nodiscard]] constexpr auto error() & noexcept(release) -> E & { - auto *e = error_if(); - UPD_ASSERT(e != nullptr); - - return *e; - } - - [[nodiscard]] constexpr auto error() const & noexcept(release) -> const E & { - const auto *e = error_if(); - UPD_ASSERT(e != nullptr); - - return *e; - } - - [[nodiscard]] constexpr auto error() && noexcept(release) -> 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() 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() const noexcept -> const E * { return std::get_if<1>(&m_value_or_error); } - -private: - std::variant m_value_or_error; -}; - } // namespace upd namespace upd::descriptor { @@ -290,165 +292,120 @@ enum class field_tag { checksum, }; -template -class description { - template - friend constexpr auto operator|(description lhs, description rhs) noexcept; +template +using named_value_bundle = decltype(named_tuple{std::declval()...}); + +template +concept field_like = requires(T) { + typename T::value_type; +} && requires(T x) { + { x.default_value() } -> std::same_as; + { x.deduce(named_tuple{}) } -> std::same_as; +}; - template - friend constexpr auto descriptor::field(signedness_t, width_t) noexcept(release); +template +concept decodable_field = field_like +&& serializer +&& requires( + T x, + Serializer ser, + const byte_type *src) { + { x.decode(src, ser) } -> std::same_as; +}; - template - friend constexpr auto descriptor::constant(T, width_t) noexcept(release); +template +concept encodable_field = field_like +&& serializer +&& requires( + T x, + Serializer ser, + typename T::value_type value, + byte_type *dest) { + { x.encode(value, ser, dest) } -> std::same_as; +}; - template - friend constexpr auto descriptor::checksum(BinaryOp, width_t, Init, descriptor::all_fields_t) noexcept(release); +template +class description { + template + friend constexpr auto operator|(description<_Ts...> lhs, description rhs) noexcept(release); public: constexpr static auto identifiers = typelist{} - .transform([](auto field_type) { return expridentifier>; }); - - 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 = named_tuple{UPD_FWD(nvs)...}; - 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>; - auto value = named_args[keyword{}]; - return keyword{} = representation{value}; - } else if constexpr (field.tag == field_tag::constant) { - return keyword{} = field.value; - } else if constexpr (field.tag == field_tag::checksum) { - constexpr auto width = field.width; - return keyword{} = xuint{field.init}; - } else { - static_assert(UPD_ALWAYS_FALSE, "`field` is not a field object"); - } - }; - - auto make_named_tuple = [](auto &&...nvs) { return named_tuple{UPD_FWD(nvs)...}; }; - 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[keyword{}]; - auto folder = invoker_iterator{ - [&, op=op](auto x) { acc = op(acc, x); } - }; - - get_range(identifiers).for_each([&](auto id) { - return retval[keyword{}].serialize(ser, folder); - }); - } - }; - - m_fields.for_each(second_pass); - return expected{std::move(retval)}; + .transform([](typebox) { return expr; }); + + explicit constexpr description(Ts ...fields) : m_fields{std::move(fields)...} {} + + template> OutputIt> + constexpr void encode(const NamedTuple &nargs, Serializer &ser, OutputIt dest) const { + auto packet = m_fields + .transform([&](const Field &field) { + using value_type = typename Field::value_type; + auto id = expr; + if constexpr (nargs.contains(id)) { + return keyword{} = value_type{nargs[id]}; + } else { + return keyword{} = field.default_value(); + } + }) + .apply([](auto &&... field_values) { return named_tuple{UPD_FWD(field_values)...}; }); + + zip(packet, m_fields) + .for_each(unpack | [&](auto &field_value, const auto &field) { + UPD_WELL_FORMED(field_value.value() = field.deduce(std::as_const(packet))); + }); + + zip(packet, m_fields) + .for_each(unpack | [&](const auto &field_value, const auto &field) { + field.encode(field_value.value(), ser, dest); + }); } - template - [[nodiscard]] constexpr auto decode(InputIt src, Serializer &ser) const noexcept(release) { - auto err = unexpected{}; - auto accumulators = m_fields - .filter([](const auto &field) { return expr; }) - .apply([](auto &&... checksum_fields) { - return named_tuple { (keyword{} = checksum_fields.init)...}; - } - ); - - auto first_pass = [&](const auto &field) { - if constexpr (field.tag == field_tag::checksum) { - const auto &[op, init, get_range] = field; - auto &acc = accumulators[keyword{}]; - auto range = get_range(identifiers); - auto accumulate = [&, &op = op](auto identifier, const auto &value) { - auto position = range - .find_if(to_metafunction([=](auto id) { return id == identifier; })); - - if constexpr (position < range.size()) { - acc = op(acc, value); - } - }; - - return accumulate; - } - }; - - auto checksum_accs = m_fields.transform(first_pass, filter_void); - 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>; - auto decorated_src = transformer_iterator{ - detail::iterator_reference{src}, - [&](const auto &value) { - checksum_accs.for_each([&](auto acc) { acc(auto_constant{}, value); }); - return value; - } - }; - return keyword{} = deserialize_into_xinteger(decorated_src, ser); - } else if constexpr (field.tag == field_tag::constant) { - constexpr auto width = field.width; - using representation = std::conditional_t, xuint>; - auto decorated_src = transformer_iterator{ - detail::iterator_reference{src}, - [&](const auto &value) { - checksum_accs.for_each([&](auto acc) { acc(auto_constant{}, value); }); - return value; - } - }; - - auto received = deserialize_into_xinteger(decorated_src, ser); - if (!err && received != field.value) { - err = error::constant_mismatch; - } - } 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[keyword{}] != received) { - err = error::checksum_mismatch; + template + [[nodiscard]] constexpr auto decode(InputIt src, Serializer &ser) const { + auto err = error{}; + auto retval = m_fields + .apply([](const auto & ...fields) { + return named_tuple { (keyword{} = fields.default_value())... }; + }); + + m_fields + .for_each([&](const auto &field) { + retval[expr] = field.decode(detail::iterator_reference{src}, ser); + }); + + zip(retval, m_fields) + .for_each(unpack | [&](const auto &field_value, const auto &field) { + if (!err && field_value.value() == field.deduce(retval)) { + err = not_matching_deduction{field_value.identifier.string}; } - } else { - static_assert(UPD_ALWAYS_FALSE, "`field.tag` is not a valid `field_tag` enumerator"); - } - }; + }); - auto retval = m_fields - .transform(second_pass, filter_void) - .apply([](auto &&... nvs) { return named_tuple{UPD_FWD(nvs)...}; }); - return err ? err : expected{std::move(retval)}; + return result_if_no_error(std::move(retval), std::move(err)); } private: - explicit constexpr description(tuple fields) : m_fields{std::move(fields)} {} - tuple m_fields; }; template -[[nodiscard]] constexpr inline auto operator|(description lhs, description rhs) noexcept { - auto fields = std::move(lhs.m_fields) + std::move(rhs.m_fields); +[[nodiscard]] constexpr auto operator|(description lhs, description rhs) noexcept(release) { + auto concatenated_fields = std::move(lhs.m_fields) + std::move(rhs.m_fields); // 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 + auto self_comparison_count = concatenated_fields .type_only() .transform([](typebox) { return expr; }) .square() .transform(unpack | equal_to) .fold_left(expr<0uz>, plus); - static_assert(self_comparison_count == fields.size(), "Merging these descriptions would result in duplicate IDs"); + static_assert(self_comparison_count == concatenated_fields.size(), "Merging these descriptions would result in duplicate IDs"); - return description{std::move(fields)}; + return std::move(concatenated_fields).apply( + [](auto && ...fields) { return description{std::move(fields)...}; } + ); } } // namespace upd @@ -457,53 +414,130 @@ namespace upd::descriptor { template struct field_t { - constexpr static auto tag = field_tag::pure_field; constexpr static auto identifier = Identifier; constexpr static auto is_signed = Signedness; constexpr static auto width = Width; + + using value_type = std::conditional_t, xuint>; + + [[nodiscard]] constexpr static auto default_value() noexcept(release) -> value_type { + return value_type{}; + } + + template + [[nodiscard]] constexpr static auto deduce(const Packet &packet) -> value_type { + return packet.at(expr); + } + + template + [[nodiscard]] constexpr static auto decode(InputIt src, Serializer &ser) -> value_type { + if constexpr (is_signed) { + return ser.deserialize_signed(src, upd::width); + } else { + return ser.deserialize_unsigned(src, upd::width); + } + } + + template> OutputIt> + constexpr static void encode(value_type value, Serializer &ser, OutputIt dest) { + if constexpr (is_signed) { + return ser.serialize_signed(value, dest); + } else { + return ser.serialize_unsigned(value, dest); + } + } }; template [[nodiscard]] constexpr auto field(signedness_t, width_t) noexcept(release) { - auto retval = field_t{}; - return description{tuple{retval}}; + field_like auto retval = field_t{}; + + return description{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; + using value_type = xuint; + + value_type field_value; + + [[nodiscard]] constexpr auto default_value() const noexcept(release) -> value_type { + return field_value; + } + + template + [[nodiscard]] constexpr static auto deduce(const Packet &packet) -> value_type { + return packet.at(expr); + } + + template + [[nodiscard]] constexpr static auto decode(InputIt src, Serializer &ser) -> value_type { + return ser.deserialize_unsigned(src, upd::width); + } + + template> OutputIt> + constexpr static void encode(value_type value, Serializer &ser, OutputIt dest) { + return ser.serialize_unsigned(value, dest); + } }; template [[nodiscard]] constexpr auto constant(T n, width_t) noexcept(release) { auto retval = constant_t{n}; - return description{tuple{retval}}; + return description{retval}; } -template +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; + using value_type = xuint; + BinaryOp op; Init init; - GetRange get_range; + FieldFilter identifier_filter; + + [[nodiscard]] constexpr static auto default_value() noexcept(release) -> value_type { + return 0u; + } + + template + [[nodiscard]] constexpr auto deduce(const Packet &packet) const -> value_type { + auto field_filter = [&](const auto &nv) { + return UPD_INVOKE(identifier_filter, expr); + }; + + return packet + .filter(field_filter) + .transform([](const auto &named_field) -> const auto & { return named_field.value(); }) + .fold_left(init, op); + } + + template + [[nodiscard]] constexpr static auto decode(InputIt src, Serializer &ser) -> value_type { + return ser.deserialize_unsigned(src, upd::width); + } + + template> OutputIt> + constexpr static void encode(value_type value, Serializer &ser, OutputIt dest) { + return ser.serialize_unsigned(value, dest); + } }; template [[nodiscard]] constexpr auto checksum(BinaryOp op, width_t, Init init, all_fields_t) noexcept(release) { - auto get_range = [](auto identifiers) { return identifiers.filter([](auto id) { return auto_constant{}; }); }; + auto is_not_this_field = [](auto id) { + return expr; + }; + auto retval = checksum_t{std::move(op), init, get_range}; + Init, decltype(is_not_this_field)>{std::move(op), init, is_not_this_field}; - return description{tuple{retval}}; + return description{std::move(retval)}; } } // namespace upd::descriptor diff --git a/include/upd/named_value.hpp b/include/upd/named_value.hpp index d210794..2613c44 100644 --- a/include/upd/named_value.hpp +++ b/include/upd/named_value.hpp @@ -71,6 +71,11 @@ struct name { char string[name_max_size]; }; +template +[[nodiscard]] constexpr auto get(Tuple &&t) noexcept(release) -> auto && { + return UPD_FWD(t).template get(); +} + template struct keyword; @@ -80,6 +85,8 @@ struct keyword; template struct names { + constexpr static auto size = N; + template... Strings> requires (sizeof...(Strings) == N) consteval names(Strings... strs) noexcept(release): strings{} { using namespace std::ranges; @@ -93,6 +100,28 @@ struct names { template... Strings> names(Strings...) -> names; +template +concept unique_names = sizeof...(NameLists) > 0 && [] { + namespace stdr = std::ranges; + + auto to_sv_array = [](auto &name_list) { + auto sv_array = std::array{}; + auto last = stdr::copy(name_list.strings, sv_array.begin()); + + UPD_CONSTEXPR_ASSERT(last.out == sv_array.end()); + + return sv_array; + }; + + auto joined_names = tuple{ref{NameLists}...} + .transform(to_sv_array) + .flatten() + .apply([](auto... name_views) { return std::array{name_views...}; }); + + stdr::sort(joined_names); + return stdr::adjacent_find(joined_names) == joined_names.end(); +}(); + template class named_value { public: @@ -153,8 +182,8 @@ template [[nodiscard]] constexpr auto concat_named_tuple(Named_Tuple_Ts &&...); template -requires (std::size(Identifiers.strings) == sizeof...(Ts)) -class named_tuple { +requires (Identifiers.size == sizeof...(Ts)) +class named_tuple : public tuple_implementation> { constexpr static auto element_types = zip(sequence, typelist{}) .transform(unpack | [](auto i, typebox) { using type = named_value; @@ -165,40 +194,68 @@ class named_tuple { using content_type = instantiate_variadic>; public: + constexpr static auto identifiers = sequence + .transform([](auto i) { + return expr; + }) + .to_constlist(); + + template + [[nodiscard]] constexpr static auto make_tuple(typelist, Args &&... xs) { + return tuple{UPD_FWD(xs)...}; + } + + template + [[nodiscard]] constexpr static auto make_typelist(typelist) noexcept(release) { + return typelist{}; + } + + template + [[nodiscard]] constexpr static auto make_constlist(constlist) noexcept(release) { + return constlist{}; + } + + constexpr named_tuple() requires (sizeof...(Ts) == 0) = default; + template requires (sizeof...(NamedValues) == sizeof...(Ts)) constexpr explicit named_tuple(NamedValues && ...nvs): m_nvs{UPD_FWD(nvs)...} {} - template - [[nodiscard]] constexpr auto get() & noexcept -> auto && { - auto position = m_nvs.find_if([](const auto &nv) { + template requires (identifiers.find(expr) < identifiers.size()) + [[nodiscard]] constexpr auto get(this Self &&self) noexcept(release) -> auto && { + auto position = self.m_nvs.find_if([](const auto &nv) { return expr; }); - if constexpr (position < m_nvs.size()) { - return m_nvs.at(position).value(); + if constexpr (position < self.m_nvs.size()) { + return UPD_FWD(self).m_nvs.at(position).value(); } else { static_assert(UPD_ALWAYS_FALSE, "There are no element named `Identifier`"); } } - template - [[nodiscard]] constexpr auto operator[](keyword) & noexcept(release) -> auto & { - return get(); + template + [[nodiscard]] constexpr auto get(this Self &&self) noexcept(release) -> auto && { + return UPD_FWD(self).m_nvs.at(expr); } - template - [[nodiscard]] constexpr auto operator[](keyword) const & noexcept(release) -> const auto & { - return get(); + template + [[nodiscard]] constexpr auto operator[](this Self &&self, keyword) noexcept(release) -> auto && { + return UPD_FWD(self).template get(); } - template - [[nodiscard]] constexpr auto operator[](keyword) && noexcept(release) -> auto && { - return std::move(*this).template get(); + template + [[nodiscard]] constexpr auto operator[](this Self &&self, auto_constant) noexcept(release) -> auto && { + return UPD_FWD(self).template get(); + } + + template + [[nodiscard]] constexpr auto operator[](this Self &&self, auto_constant) noexcept(release) -> auto && { + return UPD_FWD(self).template get(); } template - [[nodiscard]] constexpr auto operator[](keyword) const && noexcept(release) -> const auto && { - return std::move(*this).template get(); + [[nodiscard]] constexpr static auto contains(auto_constant) noexcept(release) -> bool { + return !unique_names; } template> OutputIt> @@ -211,6 +268,8 @@ class named_tuple { content_type m_nvs; }; +named_tuple() -> named_tuple<{}>; + template explicit named_tuple(NamedValues...) -> named_tuple<{NamedValues::identifier...}, typename NamedValues::value_type...>; @@ -219,10 +278,9 @@ concept named_tuple_instance = requires(T x) { { named_tuple{x} } -> std::same_as; }; - -template +template [[nodiscard]] constexpr auto concat(NamedTuples &&...nts) { - return tuple{nts...} + return tuple{UPD_FWD(nts)...} .flatten() .apply([](auto &&... nvs) { return named_tuple{UPD_FWD(nvs)...}; }); } @@ -250,6 +308,29 @@ struct keyword { } // namespace upd +template +struct std::tuple_size> { + constexpr static auto value = sizeof...(Ts); +}; + +template +struct std::tuple_element> { + using type = upd::named_value< + upd::named_tuple_identifier_v>, + upd::named_tuple_element_t> + >; +}; + +template +struct upd::named_tuple_element> { + using type = typename decltype(auto{upd::detail::lite_tuple...>{}.at(upd::expr)})::type; +}; + +template +struct upd::named_tuple_identifier> { + constexpr static auto value = upd::name{Names.strings[I]}; +}; + namespace upd::literals { template diff --git a/include/upd/tuple.hpp b/include/upd/tuple.hpp index af7062c..ea73442 100644 --- a/include/upd/tuple.hpp +++ b/include/upd/tuple.hpp @@ -18,20 +18,32 @@ #define UPD_CONSTEXPR_ASSERT(...) \ (std::is_constant_evaluated() && (__VA_ARGS__) ? (void) 0 : throw) +#define UPD_THIS_DEDUCTION_REQUIRES_WORKAROUND(...) \ + static_assert(__VA_ARGS__) + +#define UPD_VARIADIC_CONSTRAINT_WORKAROUND(...) \ + ((__VA_ARGS__) && ...) + +#define UPD_INVOKE(INVOCABLE, ...) ((INVOCABLE)(__VA_ARGS__)) + namespace upd::detail { template -struct leaf { +struct leaf{ [[nodiscard]] constexpr auto at(auto_constant) &noexcept -> T & { return value; } [[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) &&noexcept -> T && { return UPD_FWD(value); } - [[nodiscard]] constexpr auto at(auto_constant) const &&noexcept -> const T && { return std::move(value); } + [[nodiscard]] constexpr auto at(auto_constant) const &&noexcept -> const T && { return UPD_FWD(value); } [[nodiscard]] constexpr static auto typebox_at(auto_constant) noexcept -> typebox; + [[nodiscard]] constexpr static auto has_type(typebox) noexcept(release) -> bool { + return true; + } + T value; }; @@ -42,9 +54,12 @@ template struct leaves, Ts...> : leaf... { using leaf::at...; using leaf::typebox_at...; + using leaf::has_type...; [[nodiscard]] constexpr auto typebox_at(...) const noexcept -> variadic::not_found_t { return variadic::not_found; } + [[nodiscard]] constexpr static auto has_type(...) noexcept(release) -> bool { return false; } + template using raw_type = typename decltype(typebox_at(auto_constant{}))::type; @@ -113,16 +128,6 @@ struct reference_like { template using reference_like_t = typename reference_like::type; -template -[[nodiscard]] constexpr auto get(Tuple &&tuple) noexcept(release) -> auto && { - decltype(auto) retval = UPD_FWD(tuple).m_leaves.at(expr); - - using retval_type = decltype(retval); - static_assert(!std::is_same_v, "`I` is not a valid index for `tuple`"); - - return UPD_FWD(retval); -} - template class accumulable_t { template _BinaryOp> @@ -180,8 +185,9 @@ concept tuple_like = requires(std::remove_reference_t x) { }; return (has_tuple_element(auto_constant{}) && ...); } (std::make_index_sequence>>{}) -&& [](std::index_sequence) { - [[maybe_unused]] auto is_nth_gettable = [](auto i) { +&& +[](std::index_sequence) { + [[maybe_unused]] auto is_nth_gettable = []([[maybe_unused]] auto i) { return requires(T &&x) { { get(UPD_FWD(x)) } -> std::same_as< transfert_reference_t> &&, T &&> @@ -189,7 +195,12 @@ concept tuple_like = requires(std::remove_reference_t x) { }; }; return (is_nth_gettable(auto_constant{}) && ...); -} (std::make_index_sequence>>{}); +}(std::make_index_sequence>>{}); + +template +[[nodiscard]] constexpr auto get(Tuple &&t) noexcept(release) -> auto && { + return UPD_FWD(t).template get(); +} template constexpr auto sequence = [](std::index_sequence) { @@ -251,6 +262,79 @@ concept constlist_like = tuple_like && [](constlist return (metavalue> && ...); }(sequence_for); +template +struct named_tuple_element; + +template +struct named_tuple_element { + using type = const typename named_tuple_element::type; +}; + +template +struct named_tuple_element { + using type = volatile typename named_tuple_element::type; +}; + +template +struct named_tuple_element { + using type = const volatile typename named_tuple_element::type; +}; + +template +using named_tuple_element_t = typename named_tuple_element::type; + +template +struct named_tuple_identifier; + +template +struct named_tuple_identifier { + constexpr static auto value = named_tuple_identifier::value; +}; + +template +struct named_tuple_identifier { + constexpr static auto value = named_tuple_identifier::value; +}; + +template +struct named_tuple_identifier { + constexpr static auto value = named_tuple_identifier::value; +}; + +template +constexpr auto named_tuple_identifier_v = named_tuple_identifier::value; + +template +concept named_tuple_like = tuple_like +&& [](std::index_sequence) { + [[maybe_unused]] auto has_tuple_identifier = [](auto i) { + return requires(std::remove_reference_t x) { + named_tuple_identifier::value; + }; + }; + return (has_tuple_identifier(expr) && ...); +} (std::make_index_sequence>>{}) +&& [](std::index_sequence) { + [[maybe_unused]] auto has_tuple_identifier = [](auto i) { + return requires(std::remove_reference_t x) { + typename named_tuple_element::type; + }; + }; + return (has_tuple_identifier(expr) && ...); +} (std::make_index_sequence>>{}) +&& [](std::index_sequence) { + [[maybe_unused]] auto is_nth_id_gettable = []([[maybe_unused]] auto i) { + using noref_type = std::remove_reference_t; + [[maybe_unused]] constexpr auto identifier = named_tuple_identifier_v; + return requires(T &&x) { + { get>(UPD_FWD(x)) } -> std::same_as< + transfert_reference_t &&, T &&> + >; + }; + }; + return (is_nth_id_gettable(expr) && ...); +}(std::make_index_sequence>>{}); + template concept array_like = tuple_like && std::tuple_size_v > 0 @@ -362,12 +446,12 @@ class tuple_implementation { template [[nodiscard]] constexpr auto operator[](this Self &&self, I i) noexcept(release) -> auto && { - return get(UPD_FWD(self).derived()); + return UPD_FWD(self).at(i); } template [[nodiscard]] constexpr auto at(this Self &&self, I i) noexcept(release) -> auto && { - return get(UPD_FWD(self).derived()); + return UPD_FWD(self).derived().template get(); } template @@ -417,10 +501,16 @@ class tuple_implementation { return transform(filter_one).clean(typebox{}); } + template requires constlist_like + [[nodiscard]] constexpr auto find(auto_constant) const { + return find_if([](const auto &x) { return expr; }); + } + template UnaryPred> [[nodiscard]] constexpr auto find_if(UnaryPred &&p) const { auto filtered = zip(sequence, derived()) - .filter(unpack | [&](auto, const auto &x) { return std::invoke(p, x); }); + .filter(unpack | [&](auto, const auto &x) { return std::invoke(p, x); }) + .clone(); if constexpr (filtered.size() > 0) { return filtered[expr<0uz>][expr<0uz>]; @@ -554,7 +644,7 @@ class tuple_implementation { template [[nodiscard]] constexpr auto transform(this Self &&self, F &&f) { - static_assert(transformer_on_each); + UPD_THIS_DEDUCTION_REQUIRES_WORKAROUND(transformer_on_each); auto invoke_f_at = [&](auto i) -> decltype(auto) { return f(UPD_FWD(self).at(i)); @@ -589,8 +679,9 @@ class tuple_implementation { return upd::type_only(derived()); } - template F> + template [[nodiscard]] constexpr auto apply(this Self &&self, F &&f) -> decltype(auto) { + // UPD_THIS_DEDUCTION_REQUIRES_WORKAROUND(applicable); auto seq = std::make_index_sequence{}; return [&](std::index_sequence) -> decltype(auto) { @@ -603,7 +694,7 @@ class tuple_implementation { static_assert(invocable_on_each); UPD_FWD(self).apply([&](auto &&... xs) { - ((void) std::invoke(f, UPD_FWD(xs)), ...); + ((void) UPD_INVOKE(f, UPD_FWD(xs)), ...); }); } @@ -614,9 +705,6 @@ class tuple_implementation { template class tuple : public tuple_implementation> { - template - friend constexpr auto get(Tuple &&) noexcept(release) -> auto &&; - using leaves = detail::leaves, Ts...>; public: @@ -641,6 +729,16 @@ class tuple : public tuple_implementation> { template constexpr explicit tuple(std::in_place_t, Us &&... xs): m_leaves{UPD_FWD(xs)...} {} + template + [[nodiscard]] constexpr auto get(this Self &&self) noexcept(release) -> auto && { + decltype(auto) retval = UPD_FWD(self).m_leaves.at(expr); + + using retval_type = decltype(retval); + static_assert(!std::is_same_v, "`I` is not a valid index for `tuple`"); + + return UPD_FWD(retval); + } + private: leaves m_leaves; }; @@ -657,9 +755,6 @@ explicit tuple(std::in_place_t, Ts...) -> tuple< template class typelist : public tuple_implementation> { - template - friend constexpr auto get(Tuple &&) noexcept(release) -> auto &&; - using leaves = detail::leaves, typebox...>; public: @@ -683,6 +778,16 @@ class typelist : public tuple_implementation> { template constexpr explicit typelist(Metas...) noexcept(release) {} + template + [[nodiscard]] constexpr auto get(this Self &&self) noexcept(release) -> auto && { + decltype(auto) retval = UPD_FWD(self).m_leaves.at(expr); + + using retval_type = decltype(retval); + static_assert(!std::is_same_v, "`I` is not a valid index for `tuple`"); + + return UPD_FWD(retval); + } + private: leaves m_leaves; }; @@ -720,6 +825,16 @@ class constlist : public tuple_implementation> { template constexpr explicit constlist(Metas...) noexcept(release) {} + template + [[nodiscard]] constexpr auto get(this Self &&self) noexcept(release) -> auto && { + decltype(auto) retval = UPD_FWD(self).m_leaves.at(expr); + + using retval_type = decltype(retval); + static_assert(!std::is_same_v, "`I` is not a valid index for `tuple`"); + + return UPD_FWD(retval); + } + private: leaves m_leaves; }; @@ -738,7 +853,7 @@ template .apply([](auto ...types) { return typelist {types...}; }); } -template requires (!(typelist_like && ...)) +template requires (!UPD_VARIADIC_CONSTRAINT_WORKAROUND(typelist_like || named_tuple_like)) [[nodiscard]] constexpr auto concat(Tuples &&... ts) noexcept(release) { auto element_types = concat(type_only(ts)...); using retval_type = instantiate_variadic; @@ -783,7 +898,7 @@ template struct unpacker { template [[nodiscard]] constexpr auto operator()(this Self &&self, Tuple &&t) noexcept(release) -> decltype(auto) { - static_assert(applicable); + UPD_THIS_DEDUCTION_REQUIRES_WORKAROUND(applicable); using noref_type = std::remove_reference_t; constexpr auto size = std::tuple_size_v;