diff --git a/library/include/larra/utils.hpp b/library/include/larra/utils.hpp index 59a5081..507458e 100644 --- a/library/include/larra/utils.hpp +++ b/library/include/larra/utils.hpp @@ -3,8 +3,26 @@ namespace larra::xmpp::utils { +namespace impl { + template -constexpr auto GetType(R(T::* ptr)) -> R; +constexpr auto GetType(R(T::*ptr)) -> R; + +template +using GetTypeT = decltype(GetType(std::declval())); + +template +concept SetConcept = + (std::conditional_t, Type>, + std::true_type, + std::integral_constant< + bool, + std::is_reference_v + ? std::copy_constructible> + : std::move_constructible>&& requires { std::declval().*std::declval(); }>>::value && + ...); + +} // namespace impl // Fields description // struct SomeStruct { @@ -34,12 +52,13 @@ struct FieldsDescription { */ template constexpr auto With(Self&& self, Value&& value) const - requires([] { - if constexpr(I < sizeof...(Fs)) { - return std::is_constructible_v(tuple))), decltype(value)>; - }; - return false; - }()) + requires(std::is_constructible_v...> && impl::SetConcept, Fs...> && + [] { + if constexpr(I < sizeof...(Fs)) { + return std::is_constructible_v(tuple))>, decltype(value)>; + }; + return false; + }()) { return [&](auto... is) -> T { @@ -61,8 +80,9 @@ struct FieldsDescription { * \return T with new field value and values from self */ template - constexpr auto With(Type(T::* ptr), Self&& self, Value&& value) const - requires std::is_constructible_v + constexpr auto With(Type(T::*ptr), Self&& self, Value&& value) const + requires(std::is_constructible_v...> && std::is_constructible_v && + impl::SetConcept) { return utempl::Unpack(this->tuple, [&](auto... fs) -> T { return {[&] { @@ -75,7 +95,7 @@ struct FieldsDescription { }()...}; }); }; -}; +}; // namespace larra::xmpp::utils namespace impl { @@ -118,47 +138,76 @@ struct FieldSetHelper { /* Method accepting field index, self and new value for field and returns object with new field value * * \param I field index for object T + * \param T Type for return and pfr reflection * \param self old object * \param value new value for field * \return T with new field value and values from self */ - template - static constexpr auto With(Self&& self, Value&& value) - requires([] { - if constexpr(I < boost::pfr::tuple_size_v>) { - return std::is_constructible_v(std::declval()))>, decltype(value)>; - }; - return false; - }()) + template , std::decay_t, T>, + typename TT = decltype([] -> decltype(auto) { + if constexpr(std::same_as) { + return [] -> Self { + std::unreachable(); + }(); + } else { + return [] -> std::remove_reference_t(std::declval()))> { + std::unreachable(); + }(); + } + }())> + static constexpr auto With(Self&& self, Value&& value) -> Type + requires( + std::is_aggregate_v && (std::same_as || requires { static_cast(self); }) && + [] { + if constexpr(I < boost::pfr::tuple_size_v) { + return std::is_constructible_v(std::declval()))>, decltype(value)>; + }; + return false; + }() && + [](auto... is) { + return ((*is == I ? true + : std::is_reference_v ? std::copy_constructible(std::declval()))> + : std::move_constructible(std::declval()))>) && + ...) && + std::is_constructible_v(std::declval()))...>; + } | utempl::kSeq>) { - using T = std::decay_t; - return [&](auto... is) -> T { + return [&](auto... is) -> Type { return {[&] -> decltype(auto) { if constexpr(I == *is) { return std::forward(value); } else { - return std::forward_like(boost::pfr::get<*is>(self)); - }; + return std::forward_like(boost::pfr::get<*is>(static_cast(self))); + } }()...}; - } | utempl::kSeq>; + } | utempl::kSeq>; }; + // clang-format off /* Method accepting field name, self and new value for field and returns object with new field value * * \param FieldName field name for object T + * \param T Type for return and pfr reflection * \param self old object * \param value new value for field * \return T with new field value and values from self */ - template + template static constexpr auto With(Self&& self, Value&& value) -> decltype(With<[] { - auto names = boost::pfr::names_as_array>(); + constexpr auto names = boost::pfr::names_as_array, std::decay_t, T>>(); return std::ranges::find(names, static_cast(FieldName)) - names.begin(); - }()>(std::forward(self), std::forward(value))) { - constexpr auto names = boost::pfr::names_as_array>(); + }(), + T>(std::forward(self), std::forward(value))) { + constexpr auto names = boost::pfr::names_as_array, std::decay_t, T>>(); constexpr auto id = std::ranges::find(names, static_cast(FieldName)) - names.begin(); - return With(std::forward(self), std::forward(value)); + return With(std::forward(self), std::forward(value)); }; + // clang-format on }; } // namespace larra::xmpp::utils diff --git a/tests/set.cpp b/tests/set.cpp index 6fba1e3..699a579 100644 --- a/tests/set.cpp +++ b/tests/set.cpp @@ -84,4 +84,28 @@ TEST(NonPfrSet, NonCopyable) { auto obj = SomeStruct5{}.Field1({}).Field2({}); } +struct SomeStruct6 { + std::string field1; + std::string field2; + template + auto Field1(this Self&& self, std::string field1) -> std::decay_t { + return utils::FieldSetHelper::With<"field1", SomeStruct6>(std::forward(self), std::move(field1)); + } + template + auto Field2(this Self&& self, std::string field2) -> std::decay_t { + return utils::FieldSetHelper::With<"field2", SomeStruct6>(std::forward(self), std::move(field2)); + } +}; + +struct SomeStruct7 : SomeStruct6 { + SomeStruct7(SomeStruct6&& obj) : SomeStruct6(std::move(obj)) {}; + using SomeStruct6::SomeStruct6; +}; + +TEST(Set, Inheritance) { + const SomeStruct7 obj = SomeStruct7{}.Field1("Hello").Field2("World"); + ASSERT_EQ(obj.field1, "Hello"); + ASSERT_EQ(obj.field2, "World"); +} + } // namespace larra::xmpp