From aa384eb391c21eeaadacbb31bcad4cac1c79cbdb Mon Sep 17 00:00:00 2001 From: Mingxin Wang Date: Mon, 22 Apr 2024 16:15:14 +0800 Subject: [PATCH] Implement make_proxy_inplace and inplace_proxiable_target with freestanding (#92) * Implement make_proxy_inplace and inplace_proxiable_target with freestanding * Add freestanding tests * Merge * Fix regression in code generation for `copying_default_dispatcher` * Fix build * Add noexcept * Remove empty line * Resolve comments --- CMakeLists.txt | 2 +- proxy.h | 72 +++++++++++++------ tests/CMakeLists.txt | 10 +++ .../freestanding/proxy_freestanding_tests.cpp | 48 +++++++++++++ tests/proxy_creation_tests.cpp | 47 ++++++------ tests/proxy_lifetime_tests.cpp | 19 +++++ 6 files changed, 154 insertions(+), 44 deletions(-) create mode 100644 tests/freestanding/proxy_freestanding_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f73e8a4..88a9513 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ if (BUILD_TESTING) googletest URL /~https://github.com/google/googletest/archive/e2239ee6043f73722e7aa812a459f54a28552929.zip ) - + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) # Disable GMock FetchContent_MakeAvailable(googletest) diff --git a/proxy.h b/proxy.h index bb03890..3c1a2e8 100644 --- a/proxy.h +++ b/proxy.h @@ -4,7 +4,6 @@ #ifndef _MSFT_PROXY_ #define _MSFT_PROXY_ -#include #include #include #include @@ -197,8 +196,8 @@ void copying_dispatcher(char* self, const char* rhs) } template void copying_default_dispatcher(char* self, const char* rhs) noexcept { - std::memcpy(std::assume_aligned(self), - std::assume_aligned(rhs), Len); + std::uninitialized_copy_n( + std::assume_aligned(rhs), Len, std::assume_aligned(self)); } template void relocation_dispatcher(char* self, const char* rhs) @@ -525,7 +524,7 @@ class proxy { if (rhs.meta_.has_value()) { if constexpr (F::constraints.relocatability == constraint_level::trivial) { - memcpy(ptr_, rhs.ptr_, F::constraints.max_size); + std::ranges::uninitialized_copy(rhs.ptr_, ptr_); } else { rhs.meta_->Traits::relocatability_meta::dispatcher(ptr_, rhs.ptr_); } @@ -698,16 +697,17 @@ constexpr proxiable_ptr_constraints trivial_ptr_constraints{ namespace details { template -class sbo_ptr { +class inplace_ptr { public: template - sbo_ptr(Args&&... args) noexcept(std::is_nothrow_constructible_v) + inplace_ptr(Args&&... args) + noexcept(std::is_nothrow_constructible_v) requires(std::is_constructible_v) : value_(std::forward(args)...) {} - sbo_ptr(const sbo_ptr&) noexcept(std::is_nothrow_copy_constructible_v) - = default; - sbo_ptr(sbo_ptr&&) noexcept(std::is_nothrow_move_constructible_v) - = default; + inplace_ptr(const inplace_ptr&) + noexcept(std::is_nothrow_copy_constructible_v) = default; + inplace_ptr(inplace_ptr&&) + noexcept(std::is_nothrow_move_constructible_v) = default; T* operator->() const noexcept { return &value_; } @@ -715,6 +715,7 @@ class sbo_ptr { mutable T value_; }; +#if __STDC_HOSTED__ template static auto rebind_allocator(const Alloc& alloc) { return typename std::allocator_traits::template rebind_alloc(alloc); @@ -761,7 +762,6 @@ class allocated_ptr { Alloc alloc_; T* ptr_; }; - template class compact_ptr { public: @@ -782,36 +782,63 @@ class compact_ptr { struct storage { template explicit storage(const Alloc& alloc, Args&&... args) - : alloc(alloc), value(std::forward(args)...) {} + : value(std::forward(args)...), alloc(alloc) {} - Alloc alloc; T value; + Alloc alloc; }; storage* ptr_; }; - template proxy allocate_proxy_impl(const Alloc& alloc, Args&&... args) { - if constexpr (proxiable, F>) { - return proxy{std::in_place_type>, - std::forward(args)...}; - } else if constexpr (proxiable, F>) { - return proxy{std::in_place_type>, + if constexpr (proxiable, F>) { + return proxy{std::in_place_type>, alloc, std::forward(args)...}; } else { - return proxy{std::in_place_type>, + return proxy{std::in_place_type>, alloc, std::forward(args)...}; } } template proxy make_proxy_impl(Args&&... args) { - return allocate_proxy_impl( - std::allocator{}, std::forward(args)...); + if constexpr (proxiable, F>) { + return proxy{std::in_place_type>, + std::forward(args)...}; + } else { + return allocate_proxy_impl( + std::allocator{}, std::forward(args)...); + } } +#endif // __STDC_HOSTED__ } // namespace details +template +concept inplace_proxiable_target = proxiable, F>; + +template T, class... Args> +proxy make_proxy_inplace(Args&&... args) + noexcept(std::is_nothrow_constructible_v) { + return proxy{std::in_place_type>, + std::forward(args)...}; +} +template T, class U, class... Args> +proxy make_proxy_inplace(std::initializer_list il, Args&&... args) + noexcept(std::is_nothrow_constructible_v< + T, std::initializer_list&, Args...>) { + return proxy{std::in_place_type>, + il, std::forward(args)...}; +} +template +proxy make_proxy_inplace(T&& value) + noexcept(std::is_nothrow_constructible_v, T>) + requires(inplace_proxiable_target, F>) { + return proxy{std::in_place_type>>, + std::forward(value)}; +} + +#if __STDC_HOSTED__ template proxy allocate_proxy(const Alloc& alloc, Args&&... args) { return details::allocate_proxy_impl(alloc, std::forward(args)...); @@ -837,6 +864,7 @@ template proxy make_proxy(T&& value) { return details::make_proxy_impl>(std::forward(value)); } +#endif // __STDC_HOSTED__ // The following types and macros aim to simplify definition of dispatch and // facade types prior to C++26 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e3ff327..72182f1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,3 +20,13 @@ endif() include(GoogleTest) gtest_discover_tests(msft_proxy_tests) + +if(NOT MSVC) + add_executable(msft_proxy_freestanding_tests freestanding/proxy_freestanding_tests.cpp) + target_include_directories(msft_proxy_freestanding_tests PRIVATE .) + target_compile_features(msft_proxy_freestanding_tests PRIVATE cxx_std_20) + target_compile_options(msft_proxy_freestanding_tests PRIVATE -ffreestanding -fno-exceptions -fno-rtti -Wall -Wextra -Wpedantic -Werror) + target_link_options(msft_proxy_freestanding_tests PRIVATE -nodefaultlibs -lc) + target_link_libraries(msft_proxy_freestanding_tests PRIVATE msft_proxy) + add_test(NAME ProxyFreestandingTests COMMAND msft_proxy_freestanding_tests) +endif() diff --git a/tests/freestanding/proxy_freestanding_tests.cpp b/tests/freestanding/proxy_freestanding_tests.cpp new file mode 100644 index 0000000..afb3ba2 --- /dev/null +++ b/tests/freestanding/proxy_freestanding_tests.cpp @@ -0,0 +1,48 @@ +#if __STDC_HOSTED__ +#error "This file shall be compiled targeting a freestanding environment." +#endif // __STDC_HOSTED__ + +#include "proxy.h" + +unsigned GetHash(int v) { return static_cast(v + 3) * 31; } +unsigned GetHash(double v) { return static_cast(v * v + 5) * 87; } +unsigned GetHash(const char* v) { + unsigned result = 91u; + for (int i = 0; v[i]; ++i) { + result = result * 47u + v[i]; + } + return result; +} +unsigned GetDefaultHash() { return -1; } + +namespace spec { + +PRO_DEF_FREE_DISPATCH_WITH_DEFAULT(GetHash, ::GetHash, ::GetDefaultHash, unsigned()); +PRO_DEF_FACADE(Hashable, GetHash); + +} // namespace spec + +extern "C" int main() { + int i = 123; + double d = 3.14159; + const char* s = "lalala"; + std::tuple t{11, 22}; + pro::proxy p; + p = &i; + if (p() != GetHash(i)) { + return 1; + } + p = &d; + if (p() != GetHash(d)) { + return 1; + } + p = pro::make_proxy_inplace(s); + if (p() != GetHash(s)) { + return 1; + } + p = &t; + if (p() != GetDefaultHash()) { + return 1; + } + return 0; +} diff --git a/tests/proxy_creation_tests.cpp b/tests/proxy_creation_tests.cpp index 50009cc..9af84ea 100644 --- a/tests/proxy_creation_tests.cpp +++ b/tests/proxy_creation_tests.cpp @@ -11,7 +11,7 @@ namespace { struct SboObserver { public: template - constexpr explicit SboObserver(std::in_place_type_t>) + constexpr explicit SboObserver(std::in_place_type_t>) : SboEnabled(true), AllocatorAllocatesForItself(false) {} template constexpr explicit SboObserver(std::in_place_type_t>) @@ -65,15 +65,20 @@ static_assert(!pro::proxiable) struct TestMemFn1_ReturnTypeNotExist { void MemFn1(double) {} }; static_assert(!pro::proxiable); +static_assert(pro::inplace_proxiable_target); +static_assert(!pro::inplace_proxiable_target); +static_assert(!noexcept(pro::make_proxy_inplace(std::declval()))); +static_assert(noexcept(pro::make_proxy_inplace(123))); + } // namespace -TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_FromValue) { +TEST(ProxyCreationTests, TestMakeProxyInplace_FromValue) { utils::LifetimeTracker tracker; std::vector expected_ops; utils::LifetimeTracker::Session session{ &tracker }; expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction); { - auto p = pro::allocate_proxy(std::allocator{}, session); + auto p = pro::make_proxy_inplace(session); ASSERT_TRUE(p.has_value()); ASSERT_EQ(p.invoke(), "Session 2"); ASSERT_TRUE(p.reflect().SboEnabled); @@ -84,11 +89,11 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_FromValue) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_InPlace) { +TEST(ProxyCreationTests, TestMakeProxyInplace_InPlace) { utils::LifetimeTracker tracker; std::vector expected_ops; { - auto p = pro::allocate_proxy(std::allocator{}, & tracker); + auto p = pro::make_proxy_inplace(&tracker); ASSERT_TRUE(p.has_value()); ASSERT_EQ(p.invoke(), "Session 1"); ASSERT_TRUE(p.reflect().SboEnabled); @@ -99,11 +104,11 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_InPlace) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_InPlaceInitializerList) { +TEST(ProxyCreationTests, TestMakeProxyInplace_InPlaceInitializerList) { utils::LifetimeTracker tracker; std::vector expected_ops; { - auto p = pro::allocate_proxy(std::allocator{}, { 1, 2, 3 }, &tracker); + auto p = pro::make_proxy_inplace({ 1, 2, 3 }, &tracker); ASSERT_TRUE(p.has_value()); ASSERT_EQ(p.invoke(), "Session 1"); ASSERT_TRUE(p.reflect().SboEnabled); @@ -114,11 +119,11 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_InPlaceInitializerList) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_Lifetime_Copy) { +TEST(ProxyCreationTests, TestMakeProxyInplace_Lifetime_Copy) { utils::LifetimeTracker tracker; std::vector expected_ops; { - auto p1 = pro::allocate_proxy(std::allocator{}, & tracker); + auto p1 = pro::make_proxy_inplace(&tracker); expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction); auto p2 = p1; ASSERT_TRUE(p1.has_value()); @@ -135,11 +140,11 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_Lifetime_Copy) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_Lifetime_Move) { +TEST(ProxyCreationTests, TestMakeProxyInplace_Lifetime_Move) { utils::LifetimeTracker tracker; std::vector expected_ops; { - auto p1 = pro::allocate_proxy(std::allocator{}, & tracker); + auto p1 = pro::make_proxy_inplace(&tracker); expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction); auto p2 = std::move(p1); ASSERT_FALSE(p1.has_value()); @@ -154,7 +159,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithSBO_Lifetime_Move) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_FromValue) { +TEST(ProxyCreationTests, TestAllocateProxy_DirectAllocator_FromValue) { utils::LifetimeTracker tracker; std::vector expected_ops; utils::LifetimeTracker::Session session{ &tracker }; @@ -172,7 +177,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_FromValue) ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_InPlace) { +TEST(ProxyCreationTests, TestAllocateProxy_DirectAllocator_InPlace) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -188,7 +193,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_InPlace) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_InPlaceInitializerList) { +TEST(ProxyCreationTests, TestAllocateProxy_DirectAllocator_InPlaceInitializerList) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -204,7 +209,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_InPlaceIni ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_Lifetime_Copy) { +TEST(ProxyCreationTests, TestAllocateProxy_DirectAllocator_Lifetime_Copy) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -227,7 +232,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_Lifetime_C ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_Lifetime_Move) { +TEST(ProxyCreationTests, TestAllocateProxy_DirectAllocator_Lifetime_Move) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -245,7 +250,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_DirectAllocator_Lifetime_M ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_FromValue) { +TEST(ProxyCreationTests, TestAllocateProxy_IndirectAllocator_FromValue) { utils::LifetimeTracker tracker; std::vector expected_ops; utils::LifetimeTracker::Session session{ &tracker }; @@ -264,7 +269,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_FromValu ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_InPlace) { +TEST(ProxyCreationTests, TestAllocateProxy_IndirectAllocator_InPlace) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -281,7 +286,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_InPlace) ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_InPlaceInitializerList) { +TEST(ProxyCreationTests, TestAllocateProxy_IndirectAllocator_InPlaceInitializerList) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -298,7 +303,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_InPlaceI ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_Lifetime_Copy) { +TEST(ProxyCreationTests, TestAllocateProxy_IndirectAllocator_Lifetime_Copy) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -322,7 +327,7 @@ TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_Lifetime ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyCreationTests, TestAllocateProxy_WithoutSBO_IndirectAllocator_Lifetime_Move) { +TEST(ProxyCreationTests, TestAllocateProxy_IndirectAllocator_Lifetime_Move) { utils::LifetimeTracker tracker; std::vector expected_ops; { diff --git a/tests/proxy_lifetime_tests.cpp b/tests/proxy_lifetime_tests.cpp index 71bc97c..f94b38a 100644 --- a/tests/proxy_lifetime_tests.cpp +++ b/tests/proxy_lifetime_tests.cpp @@ -8,6 +8,7 @@ namespace { PRO_DEF_FACADE(TestFacade, utils::spec::ToString, pro::copyable_ptr_constraints); +PRO_DEF_FACADE(TestTrivialFacade, utils::spec::ToString, pro::trivial_ptr_constraints); } // namespace @@ -189,6 +190,24 @@ TEST(ProxyLifetimeTests, TestMoveConstrction_FromValue) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } +TEST(ProxyLifetimeTests, TestMoveConstrction_FromValue_Trivial) { + utils::LifetimeTracker tracker; + std::vector expected_ops; + { + utils::LifetimeTracker::Session session{ &tracker }; + expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction); + pro::proxy p1 = &session; + ASSERT_TRUE(p1.has_value()); + auto p2 = std::move(p1); + ASSERT_FALSE(p1.has_value()); + ASSERT_TRUE(p2.has_value()); + ASSERT_EQ(p2.invoke(), "Session 1"); + ASSERT_TRUE(tracker.GetOperations() == expected_ops); + } + expected_ops.emplace_back(1, utils::LifetimeOperationType::kDestruction); + ASSERT_TRUE(tracker.GetOperations() == expected_ops); +} + TEST(ProxyLifetimeTests, TestMoveConstrction_FromNull) { pro::proxy p1; auto p2 = std::move(p1);