diff --git a/src/lib/library.cpp b/src/lib/library.cpp index ab7903a..8859ef6 100644 --- a/src/lib/library.cpp +++ b/src/lib/library.cpp @@ -1,2 +1,3 @@ export module larra.library; export import larra.library.jid; +export import larra.library.utils; diff --git a/src/lib/utils.cpp b/src/lib/utils.cpp new file mode 100644 index 0000000..a7cde5d --- /dev/null +++ b/src/lib/utils.cpp @@ -0,0 +1,99 @@ +module; +#include +export module larra.library.utils; +import std; +import utempl; + +namespace larra::xmpp::utils { + +template +struct FieldDescription { + utempl::Tuple tuple; + + template + constexpr auto GetType(R(T::* ptr)) -> R; + + 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; + }()) + + { + return [&](auto... is) -> T { + return {[&] -> decltype(auto) { + if constexpr(*is == I) { + return std::forward(value); + } else { + return std::forward_like(self.*Get<*is>(this->tuple)); + }; + }()...}; + } | utempl::kSeq; + }; + + template + constexpr auto With(Type(T::* ptr), Self&& self, Value&& value) const + requires std::is_constructible_v + { + return utempl::Unpack(this->tuple, [&](auto... fs) -> T { + return {[&] { + if constexpr(std::is_same_v) { + if(fs == ptr) { + return std::forward(value); + }; + }; + return std::forward_like(self.*fs); + }()...}; + }); + }; +}; + +template +consteval auto CreateFieldsDescriptionHelper(Fs&&... fs) -> FieldDescription...> { + return {.tuple = {std::forward(fs)...}}; +}; + +export template +consteval auto CreateFieldsDescription(Rs(T::*... ptr)) { + return [&](auto... is) { + return CreateFieldsDescriptionHelper(ptr...); + } | utempl::kSeq; +}; + +export struct FieldSetHelper { + 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; + }()) + { + using T = std::decay_t; + return [&](auto... is) -> T { + return {[&] -> decltype(auto) { + if constexpr(I == *is) { + return std::forward(value); + } else { + return std::forward_like(boost::pfr::get<*is>(self)); + }; + }()...}; + } | utempl::kSeq>; + }; + + template + static constexpr auto With(Self&& self, Value&& value) -> decltype(With<[] { + auto names = boost::pfr::names_as_array>(); + return std::ranges::find(names, static_cast(FieldName)) - names.begin(); + }()>(std::forward(self), std::forward(value))) { + constexpr auto names = boost::pfr::names_as_array>(); + constexpr auto id = std::ranges::find(names, static_cast(FieldName)) - names.begin(); + return With(std::forward(self), std::forward(value)); + }; +}; + +} // namespace larra::xmpp::utils diff --git a/tests/set.cpp b/tests/set.cpp new file mode 100644 index 0000000..cea7de7 --- /dev/null +++ b/tests/set.cpp @@ -0,0 +1,88 @@ +module; +#include +export module tests.set; +import larra.library.utils; + +namespace larra::xmpp { + +struct SomeStruct { + std::string field1; + float field2 = 1.f; + template + auto Field1(this Self&& self, std::string field1) -> decltype(auto) { + return utils::FieldSetHelper::With<0>(std::forward(self), std::move(field1)); + } + template + auto Field2(this Self&& self, float field2) -> decltype(auto) { + return utils::FieldSetHelper::With<"field2">(std::forward(self), std::move(field2)); + } +}; + +TEST(Set, Basic) { + auto obj = SomeStruct{}.Field1("Hello").Field2(.0f); + EXPECT_EQ(obj.field1, "Hello"); + EXPECT_EQ(obj.field2, .0f); +} + +struct SomeStruct2 { + SomeStruct2(const SomeStruct2&) = delete; + SomeStruct2() = default; + SomeStruct2(SomeStruct2&&) = default; +}; + +struct SomeStruct3 { + SomeStruct2 field1; + SomeStruct2 field2; + template + auto Field1(this Self&& self, SomeStruct2 field1) -> decltype(auto) { + return utils::FieldSetHelper::With<0>(std::forward(self), std::move(field1)); + } + template + auto Field2(this Self&& self, SomeStruct2 field2) -> decltype(auto) { + return utils::FieldSetHelper::With<"field2">(std::forward(self), std::move(field2)); + } +}; + +TEST(Set, NonCopyable) { + auto obj = SomeStruct3{}.Field1({}).Field2({}); +} + +struct SomeStruct4 { + std::string field1; + float field2 = 1.f; + static constexpr auto kUtils = utils::CreateFieldsDescription(&SomeStruct4::field1, &SomeStruct4::field2); + template + auto Field1(this Self&& self, std::string field1) -> decltype(auto) { + return kUtils.With<0>(std::forward(self), std::move(field1)); + } + template + auto Field2(this Self&& self, float field2) -> decltype(auto) { + return kUtils.With(&SomeStruct4::field2, std::forward(self), std::move(field2)); + } +}; + +struct SomeStruct5 { + SomeStruct2 field1; + SomeStruct2 field2; + static constexpr auto kUtils = utils::CreateFieldsDescription(&SomeStruct5::field1, &SomeStruct5::field2); + template + auto Field1(this Self&& self, SomeStruct2 field1) -> decltype(auto) { + return kUtils.With<0>(std::forward(self), std::move(field1)); + } + template + auto Field2(this Self&& self, SomeStruct2 field2) -> decltype(auto) { + return kUtils.With(&SomeStruct5::field2, std::forward(self), std::move(field2)); + } +}; + +TEST(NonPfrSet, Basic) { + auto obj = SomeStruct4{}.Field1("Hello").Field2(.0f); + EXPECT_EQ(obj.field1, "Hello"); + EXPECT_EQ(obj.field2, .0f); +} + +TEST(NonPfrSet, NonCopyable) { + auto obj = SomeStruct5{}.Field1({}).Field2({}); +} + +} // namespace larra::xmpp