From 2cc2e2f82c47ce54ed9c141bfb8e670e9453766a Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 9 Apr 2026 18:11:25 -0700 Subject: [PATCH] handle swapping two `__any` objects when one is in situ and the other is on heap fixes #2007 --- include/stdexec/__detail/__any.hpp | 35 +- test/stdexec/detail/test_any.cpp | 494 +++++++++++++++-------------- 2 files changed, 286 insertions(+), 243 deletions(-) diff --git a/include/stdexec/__detail/__any.hpp b/include/stdexec/__detail/__any.hpp index 6600a5a43..26bd3b2e2 100644 --- a/include/stdexec/__detail/__any.hpp +++ b/include/stdexec/__detail/__any.hpp @@ -916,14 +916,14 @@ namespace STDEXEC::__any return std::swap(__this_ptr, __that_ptr); if (__this_ptr == nullptr) - return __value(__other).__move_to(__root_ptr_, __buff_); + return __other.__move_to_empty(*this); if (__that_ptr == nullptr) - return __value(*this).__move_to(__other.__root_ptr_, __other.__buff_); + return (*this).__move_to_empty(__other); - auto temp = std::move(*this); - __value(__other).__move_to(__root_ptr_, __buff_); - __value(temp).__move_to(__other.__root_ptr_, __other.__buff_); + auto __temp = std::move(*this); + __other.__move_to_empty(*this); + __temp.__move_to_empty(__other); } } @@ -1050,6 +1050,31 @@ namespace STDEXEC::__any } } + constexpr void __move_to_empty(__value_proxy_root &__other) noexcept + requires __movable + { + STDEXEC_IF_CONSTEVAL + { + __other.__root_ptr_ = std::exchange(__root_ptr_, nullptr); + } + else + { + STDEXEC_ASSERT(!__empty(*this)); + STDEXEC_ASSERT(__empty(__other)); + auto &__this_ptr = *__std::start_lifetime_as<__tagged_ptr>(__buff_); + auto &__that_ptr = *__std::start_lifetime_as<__tagged_ptr>(__other.__buff_); + if (__this_ptr.__is_tagged()) + { + __that_ptr = std::exchange(__this_ptr, nullptr); + } + else + { + __value(*this).__move_to(__other.__root_ptr_, __other.__buff_); + __reset(*this); + } + } + } + STDEXEC_ATTRIBUTE(always_inline) constexpr void __reset_() noexcept final { diff --git a/test/stdexec/detail/test_any.cpp b/test/stdexec/detail/test_any.cpp index 23d6ddf36..c6f98ef73 100644 --- a/test/stdexec/detail/test_any.cpp +++ b/test/stdexec/detail/test_any.cpp @@ -24,302 +24,320 @@ namespace any = STDEXEC::__any; -template -struct test_allocator +namespace { - using value_type = _Value; + template + struct test_allocator + { + using value_type = _Value; - constexpr explicit test_allocator(std::size_t &bytes) - : bytes_(bytes) - {} + constexpr explicit test_allocator(std::size_t &bytes) + : bytes_(bytes) + {} - template - constexpr test_allocator(test_allocator<_Other> const &other) noexcept - : bytes_(other.bytes_) - {} + template + constexpr test_allocator(test_allocator<_Other> const &other) noexcept + : bytes_(other.bytes_) + {} - [[nodiscard]] - constexpr _Value *allocate(std::size_t n) - { - bytes_ += n * sizeof(_Value); - return static_cast<_Value *>(::operator new(n * sizeof(_Value))); - } - - constexpr void deallocate(_Value *ptr, std::size_t n) noexcept - { - bytes_ -= n * sizeof(_Value); - ::operator delete(ptr); - } + [[nodiscard]] + constexpr _Value *allocate(std::size_t n) + { + bytes_ += n * sizeof(_Value); + return static_cast<_Value *>(::operator new(n * sizeof(_Value))); + } - bool operator==(test_allocator const &other) const noexcept - { - return &bytes_ == &other.bytes_; - } + constexpr void deallocate(_Value *ptr, std::size_t n) noexcept + { + bytes_ -= n * sizeof(_Value); + ::operator delete(ptr); + } - private: - template - friend struct test_allocator; + bool operator==(test_allocator const &other) const noexcept + { + return &bytes_ == &other.bytes_; + } - std::size_t &bytes_; -}; + private: + template + friend struct test_allocator; -static_assert(STDEXEC::__simple_allocator>); + std::size_t &bytes_; + }; -template -struct ifoo : any::__interface_base -{ - using ifoo::__interface_base::__interface_base; + static_assert(STDEXEC::__simple_allocator>); - constexpr virtual void foo() + template + struct ifoo : any::__interface_base { - any::__value(*this).foo(); - } + using ifoo::__interface_base::__interface_base; - constexpr virtual void cfoo() const - { - any::__value(*this).cfoo(); - } -}; + constexpr virtual void foo() + { + any::__value(*this).foo(); + } -template -struct ibar : any::__interface_base> -{ - using ibar::__interface_base::__interface_base; + constexpr virtual void cfoo() const + { + any::__value(*this).cfoo(); + } + }; - constexpr virtual void bar() + template + struct ibar : any::__interface_base> { - any::__value(*this).bar(); - } -}; - -template -struct ibaz : any::__interface_base, 5 * sizeof(void *)> -{ - using ibaz::__interface_base::__interface_base; + using ibar::__interface_base::__interface_base; - constexpr ~ibaz() = default; + constexpr virtual void bar() + { + any::__value(*this).bar(); + } + }; - constexpr virtual void baz() + template + struct ibaz : any::__interface_base, 5 * sizeof(void *)> { - any::__value(*this).baz(); - } -}; + using ibaz::__interface_base::__interface_base; -using Small = char; -using Big = char[sizeof(any::__any) + 1]; + constexpr ~ibaz() = default; -template -struct foobar -{ - constexpr void foo() - { - STDEXEC_IF_NOT_CONSTEVAL + constexpr virtual void baz() { - std::printf("foo override, __value = %d\n", __value); + any::__value(*this).baz(); } - } + }; + + using Small = char; + using Big = char[sizeof(any::__any) + 1]; - constexpr void cfoo() const + template + struct foobar { - STDEXEC_IF_NOT_CONSTEVAL + constexpr void foo() { - std::printf("cfoo override, __value = %d\n", __value); + STDEXEC_IF_NOT_CONSTEVAL + { + std::printf("foo override, __value = %d\n", __value); + } } - } - constexpr void bar() - { - STDEXEC_IF_NOT_CONSTEVAL + constexpr void cfoo() const { - std::printf("bar override, __value = %d\n", __value); + STDEXEC_IF_NOT_CONSTEVAL + { + std::printf("cfoo override, __value = %d\n", __value); + } } - } - constexpr void baz() - { - STDEXEC_IF_NOT_CONSTEVAL + constexpr void bar() { - std::printf("baz override, __value = %d\n", __value); + STDEXEC_IF_NOT_CONSTEVAL + { + std::printf("bar override, __value = %d\n", __value); + } } - } - bool operator==(foobar const &other) const noexcept = default; + constexpr void baz() + { + STDEXEC_IF_NOT_CONSTEVAL + { + std::printf("baz override, __value = %d\n", __value); + } + } - int __value = 42; - State state; -}; + bool operator==(foobar const &other) const noexcept = default; -static_assert( - std::derived_from, any::__iabstract>); -static_assert(std::derived_from, any::__iabstract>); -static_assert(!std::derived_from, any::__iabstract>); -static_assert(any::__extension_of, any::__icopyable>); + int __value = 42; + State state; + }; -// Test the Diamond of Death inheritance problem: -template -struct IFoo : any::__interface_base> -{ - using IFoo::__interface_base::__interface_base; + static_assert( + std::derived_from, any::__iabstract>); + static_assert(std::derived_from, any::__iabstract>); + static_assert(!std::derived_from, any::__iabstract>); + static_assert(any::__extension_of, any::__icopyable>); - constexpr virtual void foo() + // Test the Diamond of Death inheritance problem: + template + struct IFoo : any::__interface_base> { - any::__value(*this).foo(); - } -}; + using IFoo::__interface_base::__interface_base; -template -struct IBar : any::__interface_base> -{ - using IBar::__interface_base::__interface_base; + constexpr virtual void foo() + { + any::__value(*this).foo(); + } + }; - constexpr virtual void bar() + template + struct IBar : any::__interface_base> { - any::__value(*this).bar(); - } -}; + using IBar::__interface_base::__interface_base; -template -struct IBaz : any::__interface_base> // inherits twice - // from __icopyable -{ - using IBaz::__interface_base::__interface_base; + constexpr virtual void bar() + { + any::__value(*this).bar(); + } + }; - constexpr virtual void baz() + template + struct IBaz : any::__interface_base> // inherits twice + // from __icopyable { - any::__value(*this).baz(); - } -}; + using IBaz::__interface_base::__interface_base; -static_assert(std::derived_from, any::__iabstract>); -static_assert(std::derived_from, any::__iabstract>); + constexpr virtual void baz() + { + any::__value(*this).baz(); + } + }; -template -void test_deadly_diamond_of_death() -{ - any::__any m(foobar{}); + static_assert(std::derived_from, any::__iabstract>); + static_assert(std::derived_from, any::__iabstract>); - m.foo(); - m.bar(); - m.baz(); -} + template + void test_deadly_diamond_of_death() + { + any::__any m(foobar{}); -static_assert(any::__iabstract::__buffer_size < any::__iabstract::__buffer_size); + m.foo(); + m.bar(); + m.baz(); + } -// test constant evaluation works -template -consteval void test_consteval() -{ - any::__any m(foobar{}); - [[maybe_unused]] - auto x = any::__any_static_cast>(m); - x = any::__any_cast>(m); - m.foo(); - [[maybe_unused]] - auto n = m; - [[maybe_unused]] - auto p = any::__caddressof(m); - - any::__any a = 42; - if (a != a) - throw "error"; - - any::__any_ptr pifoo = any::__addressof(m); - [[maybe_unused]] - auto y = any::__any_cast>(pifoo); -} - -TEMPLATE_TEST_CASE("basic usage of any::__any", "[detail][any]", foobar, foobar) -{ - static constexpr bool is_small = std::same_as>; + static_assert(any::__iabstract::__buffer_size < any::__iabstract::__buffer_size); + + // test constant evaluation works + template + consteval void test_consteval() + { + any::__any m(foobar{}); + [[maybe_unused]] + auto x = any::__any_static_cast>(m); + x = any::__any_cast>(m); + m.foo(); + [[maybe_unused]] + auto n = m; + [[maybe_unused]] + auto p = any::__caddressof(m); + + any::__any a = 42; + if (a != a) + throw "error"; + + any::__any_ptr pifoo = any::__addressof(m); + [[maybe_unused]] + auto y = any::__any_cast>(pifoo); + } + + TEMPLATE_TEST_CASE("basic usage of any::__any", "[detail][any]", foobar, foobar) + { + static constexpr bool is_small = std::same_as>; #if STDEXEC_CLANG() || (STDEXEC_GCC() && STDEXEC_GCC_VERSION >= 14'03) - test_consteval(); // NOLINT(invalid_consteval_call) + test_consteval(); // NOLINT(invalid_consteval_call) #endif - any::__any m(foobar{}); - REQUIRE(m.__in_situ_() == (sizeof(TestType) <= any::__iabstract::__buffer_size)); - REQUIRE(any::__type(m) == STDEXEC::__mtypeid>); - - m.foo(); - m.bar(); - m.baz(); - - any::__any n = std::move(m); - n.foo(); - - m = foobar{}; - - auto ptr = any::__caddressof(m); - STDEXEC::__unconst(*ptr).foo(); - // ptr->foo(); // does not compile because it is a const-correctness violation - ptr->cfoo(); - auto const ptr2 = any::__addressof(m); - ptr2->foo(); - any::__any_ptr pifoo = ptr2; - m = *ptr; // assignment from type-erased references is supported - - any::__any a = 42; - any::__any b = 42; - any::__any c = 43; - REQUIRE(a == b); - REQUIRE(!(a != b)); - REQUIRE(!(a == c)); - REQUIRE(a != c); - - any::__reset(b); - REQUIRE(!(a == b)); - REQUIRE(a != b); - REQUIRE(!(b == a)); - REQUIRE(b != a); - - any::__any x = a; - REQUIRE(x == x); - REQUIRE(x == a); - REQUIRE(a == x); - a = 43; - REQUIRE(x != a); - REQUIRE(a != x); - - any::__reset(a); - REQUIRE(b == a); - - auto z = any::__caddressof(c); - [[maybe_unused]] - int const *p = &any::__any_cast(c); - [[maybe_unused]] - int const *q = any::__any_cast(z); - - REQUIRE(any::__any_cast(z) == &any::__any_cast(c)); - - auto y = any::__addressof(c); - int *r = any::__any_cast(std::move(y)); - REQUIRE(r == &any::__any_cast(c)); - - z = y; // assign non-const ptr to const ptr - z = &*y; - - REQUIRE(y == z); - - // test allocator support - SECTION("allocator support") - { - std::size_t bytes = 0; + any::__any m(foobar{}); + REQUIRE(m.__in_situ_() == (sizeof(TestType) <= any::__iabstract::__buffer_size)); + REQUIRE(any::__type(m) == STDEXEC::__mtypeid>); + + m.foo(); + m.bar(); + m.baz(); + + any::__any n = std::move(m); + n.foo(); + + m = foobar{}; + + auto ptr = any::__caddressof(m); + STDEXEC::__unconst(*ptr).foo(); + // ptr->foo(); // does not compile because it is a const-correctness violation + ptr->cfoo(); + auto const ptr2 = any::__addressof(m); + ptr2->foo(); + any::__any_ptr pifoo = ptr2; + m = *ptr; // assignment from type-erased references is supported + + any::__any a = 42; + any::__any b = 42; + any::__any c = 43; + REQUIRE(a == b); + REQUIRE(!(a != b)); + REQUIRE(!(a == c)); + REQUIRE(a != c); + + any::__reset(b); + REQUIRE(!(a == b)); + REQUIRE(a != b); + REQUIRE(!(b == a)); + REQUIRE(b != a); + + any::__any x = a; + REQUIRE(x == x); + REQUIRE(x == a); + REQUIRE(a == x); + a = 43; + REQUIRE(x != a); + REQUIRE(a != x); + + any::__reset(a); + REQUIRE(b == a); + + auto z = any::__caddressof(c); + [[maybe_unused]] + int const *p = &any::__any_cast(c); + [[maybe_unused]] + int const *q = any::__any_cast(z); + + REQUIRE(any::__any_cast(z) == &any::__any_cast(c)); + + auto y = any::__addressof(c); + int *r = any::__any_cast(std::move(y)); + REQUIRE(r == &any::__any_cast(c)); + + z = y; // assign non-const ptr to const ptr + z = &*y; + + REQUIRE(y == z); + + // test allocator support + SECTION("allocator support") { - [[maybe_unused]] - any::__any a1(TestType{}, test_allocator{bytes}); - if (is_small) - { - REQUIRE(bytes == 0); // small objects should not use the allocator - } - else + std::size_t bytes = 0; { - REQUIRE(bytes > 0); + [[maybe_unused]] + any::__any a1(TestType{}, test_allocator{bytes}); + if (is_small) + { + REQUIRE(bytes == 0); // small objects should not use the allocator + } + else + { + REQUIRE(bytes > 0); + } } + REQUIRE(bytes == 0); // memory should be deallocated } - REQUIRE(bytes == 0); // memory should be deallocated + + test_deadly_diamond_of_death(); } - test_deadly_diamond_of_death(); -} + TEST_CASE("can swap an in-situ any with an on-heap any", "[detail][any]") + { + any::__any a(foobar{}); + any::__any b(foobar{}); + + REQUIRE(a.__in_situ_()); + REQUIRE_FALSE(b.__in_situ_()); + + using std::swap; + swap(a, b); + + REQUIRE_FALSE(a.__in_situ_()); + REQUIRE(b.__in_situ_()); + } +} // namespace // NOLINTEND(modernize-use-override)