From: Jonathan Wakely Date: Tue, 27 May 2025 09:38:41 +0000 (+0200) Subject: libstdc++: Implement C++26 std::polymorphic [PR119152] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a2e1c97205063d7550d9b9c32319715961abd73f;p=thirdparty%2Fgcc.git libstdc++: Implement C++26 std::polymorphic [PR119152] This patch implements C++26 std::polymorphic as specified in P3019 with amendment to move assignment from LWG 4251. The implementation always allocate stored object on the heap. The manager function (_M_manager) is similary keep with the object (polymorphic::_Obj), which reduces the size of the polymorphic to size of the single pointer plus allocator (that is declared with [[no_unique_address]]). The implementation does not not use small-object optimization (SSO). We may consider adding this in the future, as SSO is allowed by the standard. However, storing any polimorphic object will require providing space for two pointers (manager function and vtable pointer) and user-declared data members. PR libstdc++/119152 libstdc++-v3/ChangeLog: * include/bits/indirect.h (std::polymorphic, pmr::polymorphic) [__glibcxx_polymorphic]: Define. * include/bits/version.def (polymorphic): Define. * include/bits/version.h: Regenerate. * include/std/memory: Define __cpp_lib_polymorphic. * testsuite/std/memory/polymorphic/copy.cc: New test. * testsuite/std/memory/polymorphic/copy_alloc.cc: New test. * testsuite/std/memory/polymorphic/ctor.cc: New test. * testsuite/std/memory/polymorphic/ctor_poly.cc: New test. * testsuite/std/memory/polymorphic/incomplete.cc: New test. * testsuite/std/memory/polymorphic/invalid_neg.cc: New test. * testsuite/std/memory/polymorphic/move.cc: New test. * testsuite/std/memory/polymorphic/move_alloc.cc: New test. Co-authored-by: Tomasz Kamiński Signed-off-by: Tomasz Kamiński --- diff --git a/libstdc++-v3/include/bits/indirect.h b/libstdc++-v3/include/bits/indirect.h index 85908e219b7..e8000d7c024 100644 --- a/libstdc++-v3/include/bits/indirect.h +++ b/libstdc++-v3/include/bits/indirect.h @@ -452,7 +452,381 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { }; #endif // __glibcxx_indirect - _GLIBCXX_END_NAMESPACE_VERSION +#if __glibcxx_polymorphic // C++26 && HOSTED + template> + class polymorphic; + + namespace pmr + { + template + using polymorphic = polymorphic<_Tp, polymorphic_allocator<_Tp>>; + } + + // [polymorphic], class template polymorphic + template + class polymorphic + { + static_assert(is_object_v<_Tp>); + static_assert(!is_array_v<_Tp>); + static_assert(!is_same_v<_Tp, in_place_t>); + static_assert(!__is_in_place_type_v<_Tp>); + static_assert(!is_const_v<_Tp> && !is_volatile_v<_Tp>); + + using _ATraits = allocator_traits<_Alloc>; + static_assert(is_same_v<_Tp, typename _ATraits::value_type>); + + // The owned object is embedded within a control block which knows the + // dynamic type and manages cloning and destroying the owned object. + struct _Obj + { + typename _ATraits::pointer _M_objp{}; // pointer to the owned object. + + // A pointer to this type, e.g. _Obj* + using pointer + = typename _ATraits::template rebind_traits<_Obj>::pointer; + + enum class _Op { _Dispose = 1, _Copy = 2, _Move = 3 }; + + constexpr virtual pointer + _M_manage(const _Alloc&, _Op, void* = nullptr) = 0; + }; + + template + struct _Obj_impl : _Obj + { + using _MyTraits + = typename _ATraits::template rebind_traits<_Obj_impl>; + + using _Op = _Obj::_Op; + + union _Uninitialized { + constexpr _Uninitialized() { } + constexpr ~_Uninitialized() { } + _Up _M_objp; + }; + _Uninitialized _M_u; + + template + constexpr + _Obj_impl(typename _MyTraits::allocator_type& __a, + _Args&&... __args) + { + using _PtrTr = pointer_traits; + _MyTraits::construct(__a, __builtin_addressof(_M_u._M_objp), + std::forward<_Args>(__args)...); + this->_M_objp = _PtrTr::pointer_to(_M_u._M_objp); + } + + constexpr virtual typename _Obj::pointer + _M_manage(const _Alloc& __a, _Op __op, void*) override + { + + switch (__op) + { + case _Op::_Move: + return _S_make_obj<_Up>(__a, std::move(_M_u._M_objp)); + case _Op::_Copy: + return _S_make_obj<_Up>(__a, + const_cast(_M_u._M_objp)); + case _Op::_Dispose: + { + using _PtrTr = pointer_traits; + typename _MyTraits::allocator_type __a2(__a); + _MyTraits::destroy(__a2, std::__addressof(_M_u._M_objp)); + _MyTraits::deallocate(__a2, _PtrTr::pointer_to(*this), 1); + return nullptr; + } + } + __builtin_unreachable(); + } + }; + + // TODO: the standard permits a small-object optimization where the + // owned object is nested within the std::polymorphic not on the heap. + + public: + + using value_type = _Tp; + using allocator_type = _Alloc; + using pointer = typename allocator_traits<_Alloc>::pointer; + using const_pointer = typename allocator_traits<_Alloc>::const_pointer; + + constexpr explicit + polymorphic() requires is_default_constructible_v<_Alloc> + : polymorphic(in_place_type<_Tp>) + { } + + constexpr explicit + polymorphic(allocator_arg_t, const _Alloc& __a) + : polymorphic(allocator_arg, __a, in_place_type<_Tp>) + { } + + constexpr + polymorphic(const polymorphic& __other) + : polymorphic(allocator_arg, + _ATraits::select_on_container_copy_construction( + __other._M_alloc), + __other) + { } + + constexpr + polymorphic(allocator_arg_t, const _Alloc& __a, + const polymorphic& __other) + : _M_alloc(__a) + { + if (__other._M_objp) + _M_objp = __other._M_objp->_M_manage(__a, _Obj::_Op::_Copy); + else + _M_objp = nullptr; + } + + constexpr + polymorphic(polymorphic&& __other) noexcept + : _M_alloc(std::move(__other._M_alloc)), + _M_objp(std::__exchange(__other._M_objp, nullptr)) + { } + + constexpr + polymorphic(allocator_arg_t, const _Alloc& __a, polymorphic&& __other) + noexcept(_ATraits::is_always_equal::value) + : _M_alloc(__a), + _M_objp(std::__exchange(__other._M_objp, nullptr)) + { + if constexpr (!_ATraits::is_always_equal::value) + if (_M_objp && _M_alloc != __other._M_alloc) + { + // _M_alloc cannot free _M_objp, give it back to __other. + __other._M_objp = std::__exchange(_M_objp, nullptr); + // And create a new object that can be freed by _M_alloc. + _M_objp = __other._M_objp->_M_manage(__a, _Obj::_Op::_Move); + } + } + + template> + requires (!is_same_v<_UUp, polymorphic>) + && (!__is_in_place_type_v<_UUp>) + && derived_from<_UUp, _Tp> + && is_constructible_v<_UUp, _Up> + && is_copy_constructible_v<_UUp> + && is_default_constructible_v<_Alloc> + constexpr explicit + polymorphic(_Up&& __u) + : _M_objp(_M_make_obj<_UUp>(std::forward<_Up>(__u))) + { } + + template> + requires (!is_same_v<_UUp, polymorphic>) + && (!__is_in_place_type_v<_UUp>) + && derived_from<_UUp, _Tp> + && is_constructible_v<_UUp, _Up> + && is_copy_constructible_v<_UUp> + constexpr explicit + polymorphic(allocator_arg_t, const _Alloc& __a, _Up&& __u) + : _M_alloc(__a), _M_objp(_M_make_obj<_UUp>(std::forward<_Up>(__u))) + { } + + template + requires is_same_v, _Up> + && derived_from<_Up, _Tp> + && is_constructible_v<_Up, _Ts...> + && is_copy_constructible_v<_Up> + && is_default_constructible_v<_Alloc> + constexpr explicit + polymorphic(in_place_type_t<_Up> __t, _Ts&&... __ts) + : _M_objp(_M_make_obj<_Up>(std::forward<_Ts>(__ts)...)) + { } + + template + requires is_same_v, _Up> + && derived_from<_Up, _Tp> + && is_constructible_v<_Up, _Ts...> + && is_copy_constructible_v<_Up> + constexpr explicit + polymorphic(allocator_arg_t, const _Alloc& __a, + in_place_type_t<_Up>, _Ts&&... __ts) + : _M_alloc(__a), + _M_objp(_M_make_obj<_Up>(std::forward<_Ts>(__ts)...)) + { } + + template + requires is_same_v, _Up> + && derived_from<_Up, _Tp> + && is_constructible_v<_Up, initializer_list<_Ip>&, _Us...> + && is_copy_constructible_v<_Up> + && is_default_constructible_v<_Alloc> + constexpr explicit + polymorphic(in_place_type_t<_Up>, initializer_list<_Ip> __il, + _Us&&... __us) + : _M_objp(_M_make_obj<_Up>(__il, std::forward<_Us>(__us)...)) + { } + + template + requires is_same_v, _Up> + && derived_from<_Up, _Tp> + && is_constructible_v<_Up, initializer_list<_Ip>&, _Us...> + && is_copy_constructible_v<_Up> + constexpr explicit + polymorphic(allocator_arg_t, const _Alloc& __a, + in_place_type_t<_Up>, initializer_list<_Ip> __il, + _Us&&... __us) + : _M_alloc(__a), + _M_objp(_M_make_obj<_Up>(__il, std::forward<_Us>(__us)...)) + { } + + constexpr ~polymorphic() + { + static_assert(sizeof(_Tp) != 0, "must be a complete type"); + _M_reset(nullptr); + } + + constexpr polymorphic& + operator=(const polymorphic& __other) + { + static_assert(sizeof(_Tp) != 0, "must be a complete type"); + + if (__builtin_addressof(__other) == this) [[unlikely]] + return *this; + + constexpr bool __pocca + = _ATraits::propagate_on_container_copy_assignment::value; + + typename _Obj::pointer __ptr = nullptr; + if (__other._M_objp) + { + auto& __a = __pocca ? __other._M_alloc : _M_alloc; + __ptr = __other._M_objp->_M_manage(__a, _Obj::_Op::_Copy); + } + + _M_reset(__ptr); + + if constexpr (__pocca) + _M_alloc = __other._M_alloc; + + return *this; + } + + constexpr polymorphic& + operator=(polymorphic&& __other) + noexcept(_ATraits::propagate_on_container_move_assignment::value + || _ATraits::is_always_equal::value) + { + if (__builtin_addressof(__other) == this) [[unlikely]] + return *this; + + constexpr bool __pocma + = _ATraits::propagate_on_container_move_assignment::value; + + typename _Obj::pointer __ptr = nullptr; + + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 4251. Move assignment for indirect unnecessarily requires copy construction + if constexpr (_ATraits::is_always_equal::value || __pocma) + __ptr = std::__exchange(__other._M_objp, nullptr); + else if (_M_alloc == __other._M_alloc) + __ptr = std::__exchange(__other._M_objp, nullptr); + else if (__other._M_objp) + { + static_assert(sizeof(_Tp) != 0, "must be a complete type"); + __ptr = __other._M_objp->_M_manage(_M_alloc, _Obj::_Op::_Move); + } + + _M_reset(__ptr); + + if constexpr (__pocma) + _M_alloc = __other._M_alloc; + + return *this; + } + + constexpr const _Tp& + operator*() const noexcept + { + __glibcxx_assert(_M_objp != nullptr); + return *_M_objp->_M_objp; + } + + constexpr _Tp& + operator*() noexcept + { + __glibcxx_assert(_M_objp != nullptr); + return *_M_objp->_M_objp; + } + + constexpr const_pointer + operator->() const noexcept + { + __glibcxx_assert(_M_objp != nullptr); + return _M_objp ? _M_objp->_M_objp : const_pointer{}; + } + + constexpr pointer + operator->() noexcept + { + __glibcxx_assert(_M_objp != nullptr); + return _M_objp ? _M_objp->_M_objp : pointer{}; + } + + constexpr bool + valueless_after_move() const noexcept { return _M_objp == nullptr; } + + constexpr allocator_type + get_allocator() const noexcept { return _M_alloc; } + + constexpr void + swap(polymorphic& __other) + noexcept(_ATraits::propagate_on_container_swap::value + || _ATraits::is_always_equal::value) + { + using std::swap; + swap(_M_objp, __other._M_objp); + if constexpr (_ATraits::propagate_on_container_swap::value) + swap(_M_alloc, __other._M_alloc); + else if constexpr (!_ATraits::is_always_equal::value) + __glibcxx_assert(_M_alloc == __other._M_alloc); + } + + friend constexpr void + swap(polymorphic& __lhs, polymorphic& __rhs) + noexcept(_ATraits::propagate_on_container_swap::value + || _ATraits::is_always_equal::value) + { __lhs.swap(__rhs); } + + private: + template + static constexpr typename _Obj::pointer + _S_make_obj(const _Alloc& __a, _Args&&... __args) + { + __alloc_rebind<_Alloc, _Obj_impl<_Up>> __objalloc(__a); + _Scoped_allocation __sa(__objalloc, in_place, __objalloc, + std::forward<_Args>(__args)...); + auto __obj = __sa.release(); + // FIXME: We need to downcast from _Obj_impl* to _Obj* but the + // the pointer_traits usage breaks in constexpr. PR c++/110714 + if constexpr (is_pointer_v) + return __obj; + else + return pointer_traits::pointer_to(*__obj); + } + + template + constexpr typename _Obj::pointer + _M_make_obj(_Args&&... __args) const + { return _S_make_obj<_Up>(_M_alloc, std::forward<_Args>(__args)...); } + + constexpr void + _M_reset(typename _Obj::pointer __ptr) noexcept + { + if (_M_objp) + _M_objp->_M_manage(_M_alloc, _Obj::_Op::_Dispose); + _M_objp = __ptr; + } + + [[no_unique_address]] _Alloc _M_alloc = _Alloc(); + typename _Obj::pointer _M_objp; + }; +#endif // __glibcxx_polymorphic + +_GLIBCXX_END_NAMESPACE_VERSION } // namespace #endif // C++26 __glibcxx_indirect || __glibcxx_polymorphic diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 9ab14a0310e..5efe4d1a284 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1978,6 +1978,15 @@ ftms = { }; }; +ftms = { + name = polymorphic; + values = { + v = 202502; + cxxmin = 26; + hosted = yes; + }; +}; + // Standard test specifications. stds[97] = ">= 199711L"; stds[03] = ">= 199711L"; diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 3358e84f160..5e905da29b1 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -2213,4 +2213,14 @@ #endif /* !defined(__cpp_lib_indirect) && defined(__glibcxx_want_indirect) */ #undef __glibcxx_want_indirect +#if !defined(__cpp_lib_polymorphic) +# if (__cplusplus > 202302L) && _GLIBCXX_HOSTED +# define __glibcxx_polymorphic 202502L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_polymorphic) +# define __cpp_lib_polymorphic 202502L +# endif +# endif +#endif /* !defined(__cpp_lib_polymorphic) && defined(__glibcxx_want_polymorphic) */ +#undef __glibcxx_want_polymorphic + #undef __glibcxx_want_all diff --git a/libstdc++-v3/include/std/memory b/libstdc++-v3/include/std/memory index d64e65cb6ce..1da03b3ea6a 100644 --- a/libstdc++-v3/include/std/memory +++ b/libstdc++-v3/include/std/memory @@ -113,6 +113,7 @@ #define __glibcxx_want_make_unique #define __glibcxx_want_out_ptr #define __glibcxx_want_parallel_algorithm +#define __glibcxx_want_polymorphic #define __glibcxx_want_ranges #define __glibcxx_want_raw_memory_algorithms #define __glibcxx_want_shared_ptr_arrays diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/copy.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/copy.cc new file mode 100644 index 00000000000..bea05acfcab --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/copy.cc @@ -0,0 +1,157 @@ +// { dg-do run { target c++26 } } + +#include +#include +#include +#include + +#include +#include + +struct Base { + friend constexpr + bool operator==(const Base& lhs, const Base& rhs) + { return lhs.eq(rhs); } + +private: + constexpr virtual bool + eq(const Base& other) const = 0; +}; + +struct Derived : Base +{ + constexpr Derived() + : x(0), y(0), z(0) + { } + + constexpr Derived(int a, int b, int c) + : x(a), y(b), z(c) + { } + +private: + constexpr bool + eq(const Base& other) const override + { + if (auto op = dynamic_cast(&other)) + return this->x == op->x && this->y == op->y && this->z == op->z; + return false; + } + + int x; + int y; + int z; +}; + +using __gnu_test::tracker_allocator; +using Counter = __gnu_test::tracker_allocator_counter; +using Polymorhic = std::polymorphic>; +const Polymorhic src(std::in_place_type, 1, 2, 3); + +constexpr void +test_ctor() +{ + Counter::reset(); + Polymorhic i1(src); + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( Counter::get_allocation_count() >= sizeof(Derived) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Counter::reset(); + Polymorhic i2(std::allocator_arg, {}, src); + VERIFY( *i2 == *src ); + VERIFY( &*i2 != &*src ); + VERIFY( Counter::get_allocation_count() >= sizeof(Derived) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +constexpr void +test_assign() +{ + Counter::reset(); + Polymorhic i1(std::in_place_type); + const size_t holderSize = Counter::get_allocation_count(); + VERIFY( holderSize >= sizeof(Derived) ); + Counter::reset(); + + i1 = src; + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_deallocation_count() == holderSize ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + auto(std::move(i1)); + Counter::reset(); + + i1 = src; + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +constexpr void +test_valueless() +{ + Polymorhic e(std::in_place_type); + auto(std::move(e)); + VERIFY( e.valueless_after_move() ); + + Counter::reset(); + Polymorhic i1(e); + VERIFY( i1.valueless_after_move() ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Polymorhic i2(std::allocator_arg, {}, e); + VERIFY( i2.valueless_after_move() ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Polymorhic i3(src); + Counter::reset(); + i3 = e; + VERIFY( i3.valueless_after_move() ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() >= sizeof(Derived) ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Counter::reset(); + i3 = e; + VERIFY( i3.valueless_after_move() ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +constexpr void +test_all() +{ + test_ctor(); + test_assign(); + test_valueless(); +} + +int main() +{ + test_all(); + + static_assert([] { + test_all(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/copy_alloc.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/copy_alloc.cc new file mode 100644 index 00000000000..f41c32e1e1d --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/copy_alloc.cc @@ -0,0 +1,270 @@ +// { dg-do run { target c++26 } } + +#include +#include +#include +#include + +#include +#include + +struct Base { + friend constexpr + bool operator==(const Base& lhs, const Base& rhs) + { return lhs.eq(rhs); } + + virtual constexpr int + get_alloc_personality() const + { return -1; } + +private: + constexpr virtual bool + eq(const Base& other) const = 0; +}; + +template +struct VecDerived : Base, std::vector +{ + using VecBase = std::vector; + + using VecBase::VecBase; + + constexpr int + get_alloc_personality() const override + { return this->get_allocator().get_personality(); } + +private: + + constexpr bool + eq(const Base& other) const override + { + if (auto op = dynamic_cast(&other)) + return *static_cast(this) + == *static_cast(op); + return false; + } +}; + +using __gnu_test::propagating_allocator; +using __gnu_test::tracker_allocator; +using Counter = __gnu_test::tracker_allocator_counter; + +template +constexpr void +test_ctor() +{ + using PropAlloc = propagating_allocator; + using Vector = VecDerived; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator>, + PropAlloc>; + using Polymorphic = std::polymorphic; + + const Polymorphic src(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place_type, {1, 2, 3}); + + Counter::reset(); + Polymorphic i1(src); + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + if (Propagate) + { + VERIFY( i1->get_alloc_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + } + else + { + VERIFY( i1->get_alloc_personality() == 0 ); + VERIFY( i1.get_allocator().get_personality() == 0 ); + } + VERIFY( Counter::get_allocation_count() >= sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + + Counter::reset(); + Polymorphic i2(std::allocator_arg, ScopedAlloc{33, 44}, src); + VERIFY( *i2 == *src ); + VERIFY( &*i2 != &*src ); + VERIFY( i2->get_alloc_personality() == 44 ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() >= sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template +constexpr void +test_assign() +{ + using PropAlloc = propagating_allocator; + using Vector = VecDerived; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator>, + PropAlloc>; + using Polymorphic = std::polymorphic; + + const Polymorphic src(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place_type, {1, 2, 3}); + + Counter::reset(); + Polymorphic i1(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place_type); + const size_t holderSize = Counter::get_allocation_count(); + VERIFY( holderSize >= sizeof(Vector) ); + Counter::reset(); + + i1 = src; + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( i1->get_alloc_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_deallocation_count() == holderSize ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Polymorphic i2(std::allocator_arg, ScopedAlloc{33, 44}); + Counter::reset(); + + i2 = src; + VERIFY( *i2 == *src ); + VERIFY( &*i2 != &*src ); + if (Propagate) + { + VERIFY( i2->get_alloc_personality() == 22 ); + VERIFY( i2.get_allocator().get_personality() == 11 ); + } + else + { + VERIFY( i2->get_alloc_personality() == 44 ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + } + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_deallocation_count() == holderSize ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Polymorphic i3(std::allocator_arg, ScopedAlloc{11, 22}); + auto(std::move(i3)); + Counter::reset(); + + i3 = src; + VERIFY( *i3 == *src ); + VERIFY( &*i3 != &*src ); + VERIFY( i3->get_alloc_personality() == 22 ); + VERIFY( i3.get_allocator().get_personality() == 11 ); + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Polymorphic i4(std::allocator_arg, ScopedAlloc{33, 44}); + auto(std::move(i4)); + Counter::reset(); + + i4 = src; + VERIFY( *i4 == *src ); + VERIFY( &*i4 != &*src ); + if (Propagate) + { + VERIFY( i4->get_alloc_personality() == 22 ); + VERIFY( i4.get_allocator().get_personality() == 11 ); + } + else + { + VERIFY( i4->get_alloc_personality() == 44 ); + VERIFY( i4.get_allocator().get_personality() == 33 ); + } + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template +constexpr void +test_valueless() +{ + using PropAlloc = propagating_allocator; + using Vector = VecDerived; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator>, + PropAlloc>; + using Polymorphic = std::polymorphic; + + Polymorphic e(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place_type); + auto(std::move(e)); + VERIFY( e.valueless_after_move() ); + + Counter::reset(); + Polymorphic i1(e); + VERIFY( i1.valueless_after_move() ); + if (Propagate) + VERIFY( i1.get_allocator().get_personality() == 11 ); + else + VERIFY( i1.get_allocator().get_personality() == 0 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Counter::reset(); + Polymorphic i2(std::allocator_arg, ScopedAlloc{33, 44}, e); + VERIFY( i2.valueless_after_move() ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Polymorphic i3(std::allocator_arg, ScopedAlloc{33, 44}); + Counter::reset(); + + i3 = e; + VERIFY( i3.valueless_after_move() ); + if (Propagate) + VERIFY( i3.get_allocator().get_personality() == 11 ); + else + VERIFY( i3.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() >= sizeof(Vector) ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Counter::reset(); + i2 = e; + VERIFY( i2.valueless_after_move() ); + if (Propagate) + VERIFY( i2.get_allocator().get_personality() == 11 ); + else + VERIFY( i2.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template +constexpr void +test_all() +{ + test_ctor(); + test_assign(); + test_valueless(); +} + +int main() +{ + test_all(); + test_all(); + + static_assert([] { + test_all(); + test_all(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/ctor.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/ctor.cc new file mode 100644 index 00000000000..bb4c947285e --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/ctor.cc @@ -0,0 +1,190 @@ +// { dg-do run { target c++26 } } + +#include +#include +#include +#include + +#ifndef __cpp_lib_polymorphic +# error __cpp_lib_polymorphic feature test macro missing in +#elif __cpp_lib_polymorphic != 202502 +# error __cpp_lib_polymorphic feature test macro has wrong value in +#endif + +#include +#include + +using __gnu_test::uneq_allocator; +using UneqAlloc = uneq_allocator; +using ScopedAlloc = std::scoped_allocator_adaptor< + uneq_allocator>, + UneqAlloc>; + +struct Obj +{ + int i; + char c[2]; +}; + +constexpr void +test_default_ctor() +{ + using __gnu_test::default_init_allocator; + + std::polymorphic> i1; + default_init_allocator a{}; + + // The contained object and the allocator should be value-initialized. + VERIFY( i1->i == 0 ); + VERIFY( i1->c[0] == 0 ); + VERIFY( i1->c[1] == 0 ); + VERIFY( i1.get_allocator() == a ); + + a.state = 5; + // Allocator-extended default constructor: + std::polymorphic> i2(std::allocator_arg, a); + VERIFY( i2.get_allocator() == a ); + + // Object is constructed using allocator-aware constructor. + std::polymorphic, ScopedAlloc> + i3(std::allocator_arg, ScopedAlloc(11, 22)); + VERIFY( i3->empty() ); + VERIFY( i3->get_allocator().get_personality() == 22 ); + VERIFY( i3.get_allocator().get_personality() == 11 ); +} + +constexpr void +test_forwarding_ctor() +{ + Obj obj{1, {'2', '3'}}; + auto verify = [](std::polymorphic const& i) + { + VERIFY( i->i == 1 ); + VERIFY( i->c[0] == '2' ); + VERIFY( i->c[1] == '3' ); + }; + + std::polymorphic i1(std::as_const(obj)); + verify(i1); + std::polymorphic i2(std::move(std::as_const(obj))); + verify(i2); + std::polymorphic i3(obj); + verify(i3); + std::polymorphic i4(std::move(obj)); + verify(i4); + + std::polymorphic i5({1, {'2', '3'}}); + verify(i5); + + std::vector v{1, 2, 3, 4, 5}; + // Object is constructed using allocator-aware constructor. + std::polymorphic, ScopedAlloc> + i7(std::allocator_arg, ScopedAlloc(11, 22), v); + VERIFY( i7->size() == 5 ); + VERIFY( v.size() == 5 ); + VERIFY( i7->get_allocator().get_personality() == 22 ); + VERIFY( i7.get_allocator().get_personality() == 11 ); + + std::polymorphic, ScopedAlloc> + i8(std::allocator_arg, ScopedAlloc(11, 22), std::move(v)); + VERIFY( i8->size() == 5 ); + VERIFY( v.size() == 0 ); + VERIFY( i8->get_allocator().get_personality() == 22 ); + VERIFY( i8.get_allocator().get_personality() == 11 ); +} + +constexpr void +test_inplace_ctor() +{ + std::polymorphic i1(std::in_place_type); + VERIFY( i1->i == 0 ); + VERIFY( i1->c[0] == 0 ); + VERIFY( i1->c[1] == 0 ); + + std::polymorphic i2(std::in_place_type, 10); + VERIFY( i2->i == 10 ); + VERIFY( i2->c[0] == 0 ); + VERIFY( i2->c[1] == 0 ); + + std::polymorphic> + i3(std::allocator_arg, 42, std::in_place_type); + VERIFY( i3->i == 0 ); + VERIFY( i3->c[0] == 0 ); + VERIFY( i3->c[1] == 0 ); + VERIFY( i3.get_allocator().get_personality() == 42 ); + + std::polymorphic> + i4(std::allocator_arg, 42, std::in_place_type, 10); + VERIFY( i4->i == 10 ); + VERIFY( i4->c[0] == 0 ); + VERIFY( i4->c[1] == 0 ); + VERIFY( i4.get_allocator().get_personality() == 42 ); + + std::polymorphic> + i5(std::in_place_type>); + VERIFY( i5->size() == 0 ); + + std::polymorphic> + i6(std::in_place_type>, 5, 13); + VERIFY( i6->size() == 5 ); + VERIFY( i6->at(0) == 13 ); + + std::polymorphic> + i7(std::in_place_type>, {1, 2, 3, 4}); + VERIFY( i7->size() == 4 ); + VERIFY( i7->at(2) == 3 ); + + std::polymorphic> + i8(std::in_place_type>, UneqAlloc{42}); + VERIFY( i8->size() == 0 ); + VERIFY( i8->get_allocator().get_personality() == 42 ); + + std::polymorphic> + i9(std::in_place_type>, 5, 13, UneqAlloc{42}); + VERIFY( i9->size() == 5 ); + VERIFY( i9->at(0) == 13 ); + VERIFY( i9->get_allocator().get_personality() == 42 ); + + std::polymorphic> + i10(std::in_place_type>, {1, 2, 3, 4}, UneqAlloc{42}); + VERIFY( i10->size() == 4 ); + VERIFY( i10->at(2) == 3 ); + VERIFY( i10->get_allocator().get_personality() == 42 ); + + std::polymorphic, ScopedAlloc> + i14(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place_type>); + VERIFY( i14->size() == 0 ); + VERIFY( i14->get_allocator().get_personality() == 22 ); + VERIFY( i14.get_allocator().get_personality() == 11 ); + + std::polymorphic, ScopedAlloc> + i15(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place_type>, 5, 13); + VERIFY( i15->size() == 5 ); + VERIFY( i15->at(0) == 13 ); + VERIFY( i15->get_allocator().get_personality() == 22 ); + VERIFY( i15.get_allocator().get_personality() == 11 ); + + std::polymorphic, ScopedAlloc> + i16(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place_type>, {1, 2, 3, 4}); + VERIFY( i16->size() == 4 ); + VERIFY( i16->at(2) == 3 ); + VERIFY( i16->get_allocator().get_personality() == 22 ); + VERIFY( i16.get_allocator().get_personality() == 11 ); +} + +int main() +{ + test_default_ctor(); + test_forwarding_ctor(); + test_inplace_ctor(); + + static_assert([] { + test_default_ctor(); + test_forwarding_ctor(); + test_inplace_ctor(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/ctor_poly.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/ctor_poly.cc new file mode 100644 index 00000000000..03519a1db00 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/ctor_poly.cc @@ -0,0 +1,220 @@ +// { dg-do run { target c++26 } } + +#include +#include +#include +#include + +#ifndef __cpp_lib_polymorphic +# error __cpp_lib_polymorphic feature test macro missing in +#elif __cpp_lib_polymorphic != 202502 +# error __cpp_lib_polymorphic feature test macro has wrong value in +#endif + +#include +#include + +struct Base { + friend constexpr + bool operator==(const Base& lhs, const Base& rhs) + { return lhs.eq(rhs); } + + virtual constexpr int + get_personality() const + { return -1; } + +private: + constexpr virtual bool + eq(const Base& other) const + { return true; } +}; + +struct ObjDerived : Base +{ + constexpr ObjDerived() + : x(0), y(0), z(0) + { } + + constexpr ObjDerived(int a, int b, int c) + : x(a), y(b), z(c) + { } + + virtual constexpr int + get_personality() const + { return -2; } + +private: + constexpr bool + eq(const Base& other) const override + { + if (auto op = dynamic_cast(&other)) + return this->x == op->x && this->y == op->y && this->z == op->z; + return false; + } + + int x; + int y; + int z; +}; + +template +struct VecDerived : Base, std::vector +{ + using VecBase = std::vector; + + using VecBase::VecBase; + + constexpr int + get_personality() const override + { return this->get_allocator().get_personality(); } + +private: + + constexpr bool + eq(const Base& other) const override + { + if (auto op = dynamic_cast(&other)) + return *static_cast(this) + == *static_cast(op); + return false; + } +}; + +using __gnu_test::uneq_allocator; +using UneqAlloc = uneq_allocator; +using ScopedAlloc = std::scoped_allocator_adaptor< + uneq_allocator, + UneqAlloc>; + +constexpr void +test_default_ctor() +{ + using __gnu_test::default_init_allocator; + + std::polymorphic> i1; + default_init_allocator a{}; + + // The contained object and the allocator should be value-initialized. + VERIFY( *i1 == Base() ); + VERIFY( i1->get_personality() == -1 ); + VERIFY( i1.get_allocator() == a ); + + a.state = 5; + // Allocator-extended default constructor: + std::polymorphic> i2(std::allocator_arg, a); + VERIFY( *i1 == Base() ); + VERIFY( i1->get_personality() == -1 ); +} + +constexpr void +test_forwarding_ctor() +{ + const ObjDerived src(1, 2, 3); + + std::polymorphic i1(src); + VERIFY( *i1 == src ); + VERIFY( i1->get_personality() == -2 ); + std::polymorphic i2(std::move(src)); + VERIFY( *i2 == src ); + VERIFY( i2->get_personality() == -2 ); + + ObjDerived obj = src; + std::polymorphic i3(obj); + VERIFY( *i3 == src ); + VERIFY( i3->get_personality() == -2 ); + std::polymorphic i4(std::move(obj)); + VERIFY( *i4 == src ); + VERIFY( i4->get_personality() == -2 ); + + const VecDerived v{1, 2, 3, 4, 5}; + // Object is constructed using allocator-aware constructor. + std::polymorphic + i5(std::allocator_arg, ScopedAlloc(11, 22), v); + VERIFY( *i5 == v ); + VERIFY( i5->get_personality() == 22 ); + VERIFY( i5.get_allocator().get_personality() == 11 ); + + std::polymorphic + i6(std::allocator_arg, ScopedAlloc(11, 22), auto(v)); + VERIFY( *i6 == v ); + VERIFY( i6->get_personality() == 22 ); + VERIFY( i6.get_allocator().get_personality() == 11 ); +} + +constexpr void +test_inplace_ctor() +{ + std::polymorphic i1(std::in_place_type); + VERIFY( *i1 == ObjDerived() ); + VERIFY( i1->get_personality() == -2 ); + + std::polymorphic i2(std::in_place_type, 10, 20, 30); + VERIFY( *i2 == ObjDerived(10, 20, 30) ); + VERIFY( i2->get_personality() == -2 ); + + std::polymorphic> + i3(std::allocator_arg, 42, std::in_place_type); + VERIFY( *i3 == ObjDerived() ); + VERIFY( i3->get_personality() == -2 ); + VERIFY( i3.get_allocator().get_personality() == 42 ); + + std::polymorphic> + i4(std::allocator_arg, 42, std::in_place_type, 10, 20, 30); + VERIFY( *i4 == ObjDerived(10, 20, 30) ); + VERIFY( i4->get_personality() == -2 ); + VERIFY( i4.get_allocator().get_personality() == 42 ); + + const VecDerived ze; + const VecDerived fe(5, 13); + const VecDerived il{1, 2, 3 ,4}; + + std::polymorphic + i5(std::in_place_type>, UneqAlloc{42}); + VERIFY( *i5 == ze ); + VERIFY( i5->get_personality() == 42 ); + + std::polymorphic + i6(std::in_place_type>, 5, 13, UneqAlloc{42}); + VERIFY( *i6 == fe ); + VERIFY( i6->get_personality() == 42 ); + + std::polymorphic + i7(std::in_place_type>, {1, 2, 3, 4}, UneqAlloc{42}); + VERIFY( *i7 == il ); + VERIFY( i7->get_personality() == 42 ); + + std::polymorphic + i8(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place_type>); + VERIFY( *i8 == ze ); + VERIFY( i8->get_personality() == 22 ); + VERIFY( i8.get_allocator().get_personality() == 11 ); + + std::polymorphic + i9(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place_type>, 5, 13); + VERIFY( *i9 == fe ); + VERIFY( i9->get_personality() == 22 ); + VERIFY( i9.get_allocator().get_personality() == 11 ); + + std::polymorphic + i10(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place_type>, {1, 2, 3, 4}); + VERIFY( *i10 == il ); + VERIFY( i10->get_personality() == 22 ); + VERIFY( i10.get_allocator().get_personality() == 11 ); +} + +int main() +{ + test_default_ctor(); + test_forwarding_ctor(); + test_inplace_ctor(); + + static_assert([] { + test_default_ctor(); + test_forwarding_ctor(); + test_inplace_ctor(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/incomplete.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/incomplete.cc new file mode 100644 index 00000000000..e5dd78f13fe --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/incomplete.cc @@ -0,0 +1,13 @@ +// { dg-do compile { target c++26 } } + +#include + +struct Incomplete; + +std::polymorphic* +test_move(std::polymorphic& i1, std::polymorphic& i2) +{ + i1 = std::move(i2); + swap(i1, i2); + return new std::polymorphic(std::move(i1)); +} diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/invalid_neg.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/invalid_neg.cc new file mode 100644 index 00000000000..a01af3fe88f --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/invalid_neg.cc @@ -0,0 +1,28 @@ +// { dg-do compile { target c++26 } } + +#include + +// In every specialization polymorphic, if the type +// allocator_traits::value_type is not the same type as T, +// the program is ill-formed. +using T1 = std::polymorphic>::value_type; // { dg-error "here" } + +// A program that instantiates the definition of the template +// polymorphic with a type for the T parameter that is +// a non-object type, an array type, in_place_t, +// a specialization of in_place_type_t, or a cv-qualified type is ill-formed. + +using T2 = std::polymorphic::value_type; // { dg-error "here" } + +using T3 = std::polymorphic::value_type; // { dg-error "here" } + +using T4 = std::polymorphic::value_type; // { dg-error "here" } + +using T5 = std::polymorphic>::value_type; // { dg-error "here" } + +using T6 = std::polymorphic::value_type; // { dg-error "here" } + +using T7 = std::polymorphic::value_type; // { dg-error "here" } + +// { dg-error "static assertion failed" "" { target *-*-* } 0 } +// { dg-prune-output "forming pointer to reference" } diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/move.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/move.cc new file mode 100644 index 00000000000..c80215983b6 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/move.cc @@ -0,0 +1,177 @@ +// { dg-do run { target c++26 } } + +#include +#include +#include +#include +#include + +#include +#include + +struct Base { + friend constexpr + bool operator==(const Base& lhs, const Base& rhs) + { return lhs.eq(rhs); } + +private: + constexpr virtual bool + eq(const Base& other) const = 0; +}; + +struct Derived : Base +{ + constexpr Derived() + : x(0), y(0), z(0) + { } + + constexpr Derived(int a, int b, int c) + : x(a), y(b), z(c) + { } + +private: + constexpr bool + eq(const Base& other) const override + { + if (auto op = dynamic_cast(&other)) + return this->x == op->x && this->y == op->y && this->z == op->z; + return false; + } + + int x; + int y; + int z; +}; + +using __gnu_test::tracker_allocator; +using Counter = __gnu_test::tracker_allocator_counter; +using Polymorphic = std::polymorphic>; +const Polymorphic val(std::in_place_type, 1, 2, 3); + +constexpr void +verifyNoAllocations() +{ + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +constexpr void +test_ctor() +{ + std::optional src; + auto make = [&src] -> Polymorphic&& { + src.emplace(val); + Counter::reset(); + return std::move(*src); + }; + + Polymorphic i1(make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + verifyNoAllocations(); + + Polymorphic i2(std::allocator_arg, {}, make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i2 == *val ); + verifyNoAllocations(); +} + +constexpr void +test_assign() +{ + std::optional src; + auto make = [&src] -> Polymorphic&& { + src.emplace(val); + Counter::reset(); + return std::move(*src); + }; + + Polymorphic i1(std::in_place_type); + + i1 = make(); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() >= sizeof(Derived) ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + auto(std::move(i1)); + i1 = make(); + VERIFY( *i1 == *val ); + VERIFY( src->valueless_after_move() ); + verifyNoAllocations(); +} + +constexpr void +test_swap() +{ + const Polymorphic val1(std::in_place_type, 1, 2, 3); + const Polymorphic val2(std::in_place_type, 2, 4, 6); + + Polymorphic i1(val1); + Polymorphic i2(val2); + Counter::reset(); + i1.swap(i2); + VERIFY( *i2 == *val1 ); + VERIFY( *i1 == *val2 ); + verifyNoAllocations(); + + auto(std::move(i1)); + + Counter::reset(); + i1.swap(i2); + VERIFY( *i1 == *val1 ); + VERIFY( i2.valueless_after_move() ); + verifyNoAllocations(); +} + +constexpr void +test_valueless() +{ + auto e = [] { + Polymorphic res(std::in_place_type); + auto(std::move(res)); + Counter::reset(); + return res; + }; + + Polymorphic i1(e()); + VERIFY( i1.valueless_after_move() ); + verifyNoAllocations(); + + Polymorphic i2(std::allocator_arg, {}, e()); + VERIFY( i2.valueless_after_move() ); + verifyNoAllocations(); + + Polymorphic i3(val); + i3 = e(); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() >= sizeof(Derived) ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + i3 = e(); + verifyNoAllocations(); +} + +constexpr void +test_all() +{ + test_ctor(); + test_assign(); + test_swap(); + test_valueless(); +} + +int main() +{ + test_all(); + + static_assert([] { + test_all(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/polymorphic/move_alloc.cc b/libstdc++-v3/testsuite/std/memory/polymorphic/move_alloc.cc new file mode 100644 index 00000000000..09afedb7884 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/polymorphic/move_alloc.cc @@ -0,0 +1,339 @@ +// { dg-do run { target c++26 } } + +#include +#include +#include +#include +#include + +#include +#include + +struct Base { + friend constexpr + bool operator==(const Base& lhs, const Base& rhs) + { return lhs.eq(rhs); } + + virtual constexpr int + get_alloc_personality() const + { return -1; } + +private: + constexpr virtual bool + eq(const Base& other) const = 0; +}; + +template +struct VecDerived : Base, std::vector +{ + using VecBase = std::vector; + + using VecBase::VecBase; + + constexpr int + get_alloc_personality() const override + { return this->get_allocator().get_personality(); } + +private: + + constexpr bool + eq(const Base& other) const override + { + if (auto op = dynamic_cast(&other)) + return *static_cast(this) + == *static_cast(op); + return false; + } +}; + +using __gnu_test::propagating_allocator; +using __gnu_test::tracker_allocator; +using Counter = __gnu_test::tracker_allocator_counter; + +constexpr void +verifyNoAllocations() +{ + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template +constexpr void +test_ctor() +{ + using PropAlloc = propagating_allocator; + using Vector = VecDerived; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator>, + PropAlloc>; + using Polymorphic = std::polymorphic; + + const Polymorphic val(std::in_place_type, {1, 2, 3}); + std::optional src; + auto make = [&val, &src] -> Polymorphic&& { + src.emplace(std::allocator_arg, ScopedAlloc{11, 22}, val); + Counter::reset(); + return std::move(*src); + }; + + Polymorphic i1(make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + VERIFY( i1->get_alloc_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Polymorphic i2(std::allocator_arg, ScopedAlloc{11, 22}, make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i2 == *val ); + VERIFY( i2->get_alloc_personality() == 22 ); + VERIFY( i2.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Polymorphic i3(std::allocator_arg, ScopedAlloc{33, 44}, make()); + // We move-from contained object + VERIFY( !src->valueless_after_move() ); + VERIFY( *i3 == *val ); + VERIFY( i3->get_alloc_personality() == 44 ); + VERIFY( i3.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() >= sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 2 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template +constexpr void +test_assign() +{ + using PropAlloc = propagating_allocator; + using Vector = VecDerived; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator>, + PropAlloc>; + using Polymorphic = std::polymorphic; + + const Polymorphic val(std::in_place_type, {1, 2, 3}); + std::optional src; + auto make = [&val, &src] -> Polymorphic&& { + src.emplace(std::allocator_arg, ScopedAlloc{11, 22}, val); + Counter::reset(); + return std::move(*src); + }; + + Counter::reset(); + Polymorphic i1(std::allocator_arg, ScopedAlloc{11, 22}); + const std::size_t holderSize = Counter::get_allocation_count(); + VERIFY( holderSize >= sizeof(Vector) ); + + i1 = make(); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + VERIFY( i1->get_alloc_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == holderSize ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Polymorphic i2(std::allocator_arg, ScopedAlloc{33, 44}); + + i2 = make(); + VERIFY( *i2 == *val ); + if (Propagate) + { + VERIFY( src->valueless_after_move() ); + VERIFY( i2->get_alloc_personality() == 22 ); + VERIFY( i2.get_allocator().get_personality() == 11 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + } + else + { + // We allocate new holder and move-from contained object + VERIFY( !src->valueless_after_move() ); + VERIFY( i2->get_alloc_personality() == 44 ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_construct_count() == 2 ); + } + VERIFY( Counter::get_deallocation_count() == holderSize ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Polymorphic i3(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place_type); + auto(std::move(i3)); + + i3 = make(); + VERIFY( *i3 == *val ); + VERIFY( src->valueless_after_move() ); + VERIFY( i3->get_alloc_personality() == 22 ); + VERIFY( i3.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Polymorphic i4(std::allocator_arg, ScopedAlloc{33, 44}, + std::in_place_type); + auto(std::move(i4)); + + i4 = make(); + VERIFY( *i4 == *val ); + if (Propagate) + { + VERIFY( src->valueless_after_move() ); + VERIFY( i4->get_alloc_personality() == 22 ); + VERIFY( i4.get_allocator().get_personality() == 11 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + } + else + { + // We allocate new holder and move-from contained object + VERIFY( !src->valueless_after_move() ); + VERIFY( i4->get_alloc_personality() == 44 ); + VERIFY( i4.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == holderSize ); + VERIFY( Counter::get_construct_count() == 2 ); + } + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template +constexpr void +test_swap() +{ + using PropAlloc = propagating_allocator; + using Vector = VecDerived; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator>, + PropAlloc>; + using Polymorphic = std::polymorphic; + + const Polymorphic val1(std::in_place_type, {1, 2, 3}); + const Polymorphic val2(std::in_place_type, {2, 4, 6}); + + Polymorphic i1(std::allocator_arg, ScopedAlloc{11, 22}, val1); + Polymorphic i2(std::allocator_arg, ScopedAlloc{11, 22}, val2); + Counter::reset(); + i1.swap(i2); + VERIFY( *i2 == *val1 ); + VERIFY( *i1 == *val2 ); + verifyNoAllocations(); + + auto(std::move(i1)); + + Counter::reset(); + i1.swap(i2); + VERIFY( *i1 == *val1 ); + VERIFY( i2.valueless_after_move() ); + verifyNoAllocations(); + + if (!Propagate) + return; + + Polymorphic i3(std::allocator_arg, ScopedAlloc{33, 44}, val2); + Counter::reset(); + i1.swap(i3); + VERIFY( *i1 == *val2 ); + VERIFY( i1->get_alloc_personality() == 44 ); + VERIFY( i1.get_allocator().get_personality() == 33 ); + VERIFY( *i3 == *val1 ); + VERIFY( i3->get_alloc_personality() == 22 ); + VERIFY( i3.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + i1.swap(i2); + VERIFY( i1.valueless_after_move() ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + VERIFY( *i2 == *val2 ); + VERIFY( i2->get_alloc_personality() == 44 ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + verifyNoAllocations(); +} + +template +constexpr void +test_valueless() +{ + using PropAlloc = propagating_allocator; + using Vector = VecDerived; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator>, + PropAlloc>; + using Polymorphic = std::polymorphic; + + auto e = [] { + Polymorphic res(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place_type); + auto(std::move(res)); + Counter::reset(); + return res; + }; + + Polymorphic i1(e()); + VERIFY( i1.valueless_after_move() ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Polymorphic i2(std::allocator_arg, ScopedAlloc{33, 44}, e()); + VERIFY( i2.valueless_after_move() ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + verifyNoAllocations(); + + Polymorphic i3(std::allocator_arg, ScopedAlloc{33, 44}); + + i3 = e(); + VERIFY( i3.valueless_after_move() ); + if (Propagate) + VERIFY( i3.get_allocator().get_personality() == 11 ); + else + VERIFY( i3.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() >= sizeof(Vector) ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + i2 = e(); + VERIFY( i2.valueless_after_move() ); + if (Propagate) + VERIFY( i2.get_allocator().get_personality() == 11 ); + else + VERIFY( i2.get_allocator().get_personality() == 33 ); + verifyNoAllocations(); + + i3.swap(i2); + VERIFY( i2.valueless_after_move() ); + VERIFY( i1.valueless_after_move() ); + verifyNoAllocations(); + + if (!Propagate) + return; + + Polymorphic i4(std::allocator_arg, ScopedAlloc{33, 44}, e()); + i4.swap(i1); + verifyNoAllocations(); +} + +template +constexpr void +test_all() +{ + test_ctor(); + test_assign(); + test_swap(); + test_valueless(); +} + +int main() +{ + test_all(); + test_all(); + + static_assert([] { + test_all(); + test_all(); + return true; + }); +}