From f329e47aa0847c6a230c8ddb7baf71e9cef9ff51 Mon Sep 17 00:00:00 2001 From: Artyom Kolpakov Date: Wed, 29 Apr 2026 16:04:05 +0000 Subject: [PATCH] fix universal: make ForwardLike conform to the standard --- .../include/userver/utils/forward_like.hpp | 33 ++-- .../include/userver/utils/struct_subsets.hpp | 35 +++- universal/src/utils/forward_like_test.cpp | 171 ++++++++++++++++++ universal/src/utils/struct_subsets_test.cpp | 142 +++++++++++++++ 4 files changed, 361 insertions(+), 20 deletions(-) create mode 100644 universal/src/utils/forward_like_test.cpp diff --git a/universal/include/userver/utils/forward_like.hpp b/universal/include/userver/utils/forward_like.hpp index 380f3e5736d0..658518925e1d 100644 --- a/universal/include/userver/utils/forward_like.hpp +++ b/universal/include/userver/utils/forward_like.hpp @@ -4,26 +4,35 @@ /// @brief @copybrief utils::ForwardLike #include -#include USERVER_NAMESPACE_BEGIN namespace utils { -// Analogue of std::forward_like from c++23. -template -decltype(auto) ForwardLike(TMember& member) { - if constexpr (std::is_lvalue_reference_v || std::is_lvalue_reference_v) { - return member; - } else { - return std::move(member); - } -} +namespace impl { + +template +struct ForwardLikeHelper; + +template +struct ForwardLikeHelper : std::type_identity {}; + +template +struct ForwardLikeHelper : std::type_identity {}; + +template +struct ForwardLikeHelper : std::type_identity {}; + +template +struct ForwardLikeHelper : std::type_identity {}; + +} // namespace impl // Analogue of std::forward_like from c++23. template -decltype(auto) ForwardLike(const TMember& member) { - return member; +constexpr auto&& ForwardLike(TMember&& member) noexcept { + using RType = impl::ForwardLikeHelper::type; + return static_cast(member); } } // namespace utils diff --git a/universal/include/userver/utils/struct_subsets.hpp b/universal/include/userver/utils/struct_subsets.hpp index c3262e0b834e..1902bde67af7 100644 --- a/universal/include/userver/utils/struct_subsets.hpp +++ b/universal/include/userver/utils/struct_subsets.hpp @@ -34,6 +34,24 @@ constexpr auto IsDefinedAndAggregate(Args...) -> bool { return false; } +template +auto&& GetSupersetField(T &t, InternalTag) noexcept { + if constexpr (std::is_reference_v) { + return std::forward(t); + } else { + return utils::ForwardLike(t); + } +} + +template +struct SubsetField : std::type_identity {}; + +template +struct SubsetFieldRef : std::type_identity {}; + +template +struct SubsetFieldRef : std::type_identity {}; + } // namespace utils::impl USERVER_NAMESPACE_END @@ -41,8 +59,9 @@ USERVER_NAMESPACE_END /// @cond // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define USERVER_IMPL_STRUCT_MAP(r, data, elem) \ - USERVER_NAMESPACE::utils::ForwardLike(other.elem), +#define USERVER_IMPL_STRUCT_MAP(r, data, elem) \ + USERVER_NAMESPACE::utils::impl::GetSupersetField( \ + other.elem, USERVER_NAMESPACE::utils::impl::InternalTag{}), // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define USERVER_IMPL_MAKE_FROM_SUPERSET(Self, ...) \ @@ -56,14 +75,14 @@ USERVER_NAMESPACE_END } // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define USERVER_IMPL_STRUCT_SUBSET_MAP(r, data, elem) \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - decltype(data::elem) elem; +#define USERVER_IMPL_STRUCT_SUBSET_MAP(r, data, elem) \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + USERVER_NAMESPACE::utils::impl::SubsetField::type elem; // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define USERVER_IMPL_STRUCT_SUBSET_REF_MAP(r, data, elem) \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - std::add_const_t& elem; +#define USERVER_IMPL_STRUCT_SUBSET_REF_MAP(r, data, elem) \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + USERVER_NAMESPACE::utils::impl::SubsetFieldRef::type elem; /// @endcond diff --git a/universal/src/utils/forward_like_test.cpp b/universal/src/utils/forward_like_test.cpp new file mode 100644 index 000000000000..426b21072ac1 --- /dev/null +++ b/universal/src/utils/forward_like_test.cpp @@ -0,0 +1,171 @@ +#include + +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace { + +template +using ForwardLikeResult = decltype(utils::ForwardLike(std::declval())); + +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +static_assert(std::is_same_v, int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, volatile int&>); +static_assert(std::is_same_v, const volatile int&>); + +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const volatile int&>); + +static_assert(std::is_same_v, int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, volatile int&>); +static_assert(std::is_same_v, const volatile int&>); + +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const int&>); +static_assert(std::is_same_v, const volatile int&>); +static_assert(std::is_same_v, const volatile int&>); + +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const int&&>); +static_assert(std::is_same_v, const volatile int&&>); +static_assert(std::is_same_v, const volatile int&&>); + +} // namespace + +USERVER_NAMESPACE_END diff --git a/universal/src/utils/struct_subsets_test.cpp b/universal/src/utils/struct_subsets_test.cpp index be38d0ab0afa..e6299c63c5b3 100644 --- a/universal/src/utils/struct_subsets_test.cpp +++ b/universal/src/utils/struct_subsets_test.cpp @@ -4,6 +4,8 @@ #include +#include + USERVER_NAMESPACE_BEGIN namespace { @@ -235,6 +237,146 @@ TEST(DefineStructSubsetRef, Sample) { /// [ref usage] } +namespace rvalue { + +struct Movable { + int value; + Movable(int v) : value(v) {} + Movable(Movable&& that) noexcept : value(std::exchange(that.value, 0)) {} +}; + +struct Deps { + Movable a; + Movable& b; + Movable&& c; + const Movable&& d; + + USERVER_ALLOW_CONVERSIONS_TO_SUBSET(); +}; + +struct TestData { + Movable b{42}; + Movable c{43}; + Movable d{44}; + Deps deps = {.a = {41}, .b = b, .c = std::move(c), .d = std::move(d)}; +}; + +USERVER_DEFINE_STRUCT_SUBSET(SmolDeps, Deps, c, d); + +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +TEST(DefineStructSubset, RRefCopy) { + TestData data; + auto& [b, c, d, deps] = data; + + SmolDeps smol = deps; + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +TEST(DefineStructSubset, RRefCopyConst) { + TestData data; + const auto& [b, c, d, deps] = data; + + SmolDeps smol = deps; + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +TEST(DefineStructSubset, RRefMove) { + TestData data; + auto& [b, c, d, deps] = data; + + SmolDeps smol = std::move(deps); + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +TEST(DefineStructSubset, RRefMoveConst) { + TestData data; + const auto& [b, c, d, deps] = data; + + SmolDeps smol = std::move(deps); + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +USERVER_DEFINE_STRUCT_SUBSET(SmolDepsRef, Deps, c, d); + +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +TEST(DefineStructSubsetRef, RRefCopy) { + TestData data; + auto& [b, c, d, deps] = data; + + SmolDepsRef smol = deps; + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +TEST(DefineStructSubsetRef, RRefCopyConst) { + TestData data; + const auto& [b, c, d, deps] = data; + + SmolDepsRef smol = deps; + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +TEST(DefineStructSubsetRef, RRefMove) { + TestData data; + auto& [b, c, d, deps] = data; + + SmolDepsRef smol = std::move(deps); + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +TEST(DefineStructSubsetRef, RRefMoveConst) { + TestData data; + const auto& [b, c, d, deps] = data; + + SmolDepsRef smol = std::move(deps); + EXPECT_EQ(deps.a.value, 41); + EXPECT_EQ(deps.b.value, 42); + EXPECT_EQ(deps.c.value, 43); + EXPECT_EQ(deps.d.value, 44); + smol.c.value = 1; + EXPECT_EQ(c.value, 1); +} + +} // namespace rvalue + } // namespace USERVER_NAMESPACE_END