From: Jonathan Wakely <jwak...@redhat.com> This papers implements C++26 std::indirect as specified in P3019 with amendment to move assignment from LWG 4251.
PR libstdc++/119152 libstdc++-v3/ChangeLog: * include/Makefile.am: Add new header. * include/Makefile.in: Regenerate. * include/bits/indirect.h: New file. * include/bits/version.def (indirect): Define. * include/bits/version.h: Regenerate. * include/std/memory: Include new header. * testsuite/std/memory/indirect/copy.cc * testsuite/std/memory/indirect/copy_alloc.cc * testsuite/std/memory/indirect/ctor.cc * testsuite/std/memory/indirect/incomplete.cc * testsuite/std/memory/indirect/invalid_neg.cc * testsuite/std/memory/indirect/move.cc * testsuite/std/memory/indirect/move_alloc.cc * testsuite/std/memory/indirect/relops.cc Co-authored-by: Tomasz Kamiński <tkami...@redhat.com> Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> --- Changes in v2: - Fixed typos in commit messages as pointed by Jakub - Removed stray comment in indirect.h header as pointed out by Daniel libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/Makefile.in | 1 + libstdc++-v3/include/bits/indirect.h | 459 ++++++++++++++++++ libstdc++-v3/include/bits/version.def | 9 + libstdc++-v3/include/bits/version.h | 10 + libstdc++-v3/include/std/memory | 5 + .../testsuite/std/memory/indirect/copy.cc | 121 +++++ .../std/memory/indirect/copy_alloc.cc | 228 +++++++++ .../testsuite/std/memory/indirect/ctor.cc | 203 ++++++++ .../std/memory/indirect/incomplete.cc | 38 ++ .../std/memory/indirect/invalid_neg.cc | 28 ++ .../testsuite/std/memory/indirect/move.cc | 144 ++++++ .../std/memory/indirect/move_alloc.cc | 296 +++++++++++ .../testsuite/std/memory/indirect/relops.cc | 82 ++++ 14 files changed, 1625 insertions(+) create mode 100644 libstdc++-v3/include/bits/indirect.h create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/copy.cc create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/copy_alloc.cc create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/ctor.cc create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/incomplete.cc create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/invalid_neg.cc create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/move.cc create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/move_alloc.cc create mode 100644 libstdc++-v3/testsuite/std/memory/indirect/relops.cc diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 3e5b6c4142e..b67d470c27e 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -210,6 +210,7 @@ bits_headers = \ ${bits_srcdir}/gslice_array.h \ ${bits_srcdir}/hashtable.h \ ${bits_srcdir}/hashtable_policy.h \ + ${bits_srcdir}/indirect.h \ ${bits_srcdir}/indirect_array.h \ ${bits_srcdir}/ios_base.h \ ${bits_srcdir}/istream.tcc \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index 3531162b5f7..6f7f2be68fd 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -563,6 +563,7 @@ bits_freestanding = \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/gslice_array.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/hashtable.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/hashtable_policy.h \ +@GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/indirect.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/indirect_array.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/ios_base.h \ @GLIBCXX_HOSTED_TRUE@ ${bits_srcdir}/istream.tcc \ diff --git a/libstdc++-v3/include/bits/indirect.h b/libstdc++-v3/include/bits/indirect.h new file mode 100644 index 00000000000..3fd9807a8fd --- /dev/null +++ b/libstdc++-v3/include/bits/indirect.h @@ -0,0 +1,459 @@ +// Vocabulary Types for Composite Class Design -*- C++ -*- + +// Copyright The GNU Toolchain Authors. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// <http://www.gnu.org/licenses/>. + +/** @file include/bits/indirect.h + * This is an internal header file, included by other library headers. + * Do not attempt to use it directly. @headername{memory} + */ + +#ifndef _GLIBCXX_INDIRECT_H +#define _GLIBCXX_INDIRECT_H 1 + +#pragma GCC system_header + +#include <bits/version.h> + +#ifdef __glibcxx_indirect || __glibcxx_polymorphic // >= C++26 +#include <compare> +#include <initializer_list> +#include <bits/allocator.h> +#include <bits/alloc_traits.h> +#include <bits/allocated_ptr.h> // __allocate_guarded +#include <bits/uses_allocator.h> // allocator_arg_t +#include <bits/utility.h> // __is_in_place_type_v +#include <bits/functional_hash.h> // hash +#include <bits/memory_resource.h> // polymorphic_allocator + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + +#if __glibcxx_indirect + template<typename _Tp, typename _Alloc = allocator<_Tp>> + class indirect; + + template<typename _Tp> + constexpr bool __is_indirect = false; + template<typename _Tp, typename _Alloc> + constexpr bool __is_indirect<indirect<_Tp, _Alloc>> = true; + +#if _GLIBCXX_HOSTED + namespace pmr + { + template<typename _Tp> + using indirect = indirect<_Tp, polymorphic_allocator<_Tp>>; + } +#endif + + // [indirect], class template indirect + template<typename _Tp, typename _Alloc> + class indirect + { + 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>); + + 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 + indirect() requires is_default_constructible_v<_Alloc> + : _M_objp(_M_make_obj_chk()) + { } + + constexpr explicit + indirect(allocator_arg_t, const _Alloc& __a) + : _M_alloc(__a), _M_objp(_M_make_obj_chk()) + { } + + constexpr + indirect(const indirect& __o) + : indirect(allocator_arg, + _ATraits::select_on_container_copy_construction(__o._M_alloc), + __o) + { } + + constexpr + indirect(allocator_arg_t, const _Alloc& __a, const indirect& __other) + : _M_alloc(__a) + { + if (__other._M_objp) + _M_objp = _M_make_obj_chk(__other.__get()); + else + _M_objp = nullptr; + } + + constexpr + indirect(indirect&& __other) noexcept + : _M_alloc(std::move(__other._M_alloc)), + _M_objp(std::__exchange(__other._M_objp, nullptr)) + { } + + constexpr + indirect(allocator_arg_t, const _Alloc& __a, + indirect&& __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) + { + static_assert(sizeof(_Tp) != 0, "must be a complete type"); + + // _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 = _M_make_obj(std::move(*__other._M_objp)); + } + } + + template<typename _Up = _Tp> + requires (!is_same_v<remove_cvref_t<_Up>, in_place_t>) + && (!is_same_v<remove_cvref_t<_Up>, indirect>) + && is_constructible_v<_Tp, _Up> + && is_default_constructible_v<_Alloc> + constexpr explicit + indirect(_Up&& __u) + : _M_objp(_M_make_obj(std::forward<_Up>(__u))) + { } + + template<typename _Up = _Tp> + requires (!is_same_v<remove_cvref_t<_Up>, in_place_t>) + && (!is_same_v<remove_cvref_t<_Up>, indirect>) + && is_constructible_v<_Tp, _Up> + constexpr explicit + indirect(allocator_arg_t, const _Alloc& __a, _Up&& __u) + : _M_alloc(__a), _M_objp(_M_make_obj(std::forward<_Up>(__u))) + { } + + template<typename... _Us> + requires is_constructible_v<_Tp, _Us...> + && is_default_constructible_v<_Alloc> + constexpr explicit + indirect(in_place_t, _Us&&... __us) + : _M_objp(_M_make_obj(std::forward<_Us>(__us)...)) + { } + + template<typename... _Us> + requires is_constructible_v<_Tp, _Us...> + constexpr explicit + indirect(allocator_arg_t, const _Alloc& __a, in_place_t, _Us&&... __us) + : _M_alloc(__a), + _M_objp(_M_make_obj(std::forward<_Us>(__us)...)) + { } + + template<typename _Ip, typename... _Us> + requires is_constructible_v<_Tp, initializer_list<_Ip>&, _Us...> + && is_default_constructible_v<_Alloc> + constexpr explicit + indirect(in_place_t, initializer_list<_Ip> __il, _Us&&... __us) + : _M_objp(_M_make_obj(__il, std::forward<_Us>(__us)...)) + { } + + template<typename _Ip, typename... _Us> + requires is_constructible_v<_Tp, initializer_list<_Ip>&, _Us...> + constexpr explicit + indirect(allocator_arg_t, const _Alloc& __a, + in_place_t, initializer_list<_Ip> __il, _Us&&... __us) + : _M_alloc(__a), + _M_objp(_M_make_obj(__il, std::forward<_Us>(__us)...)) + { } + + constexpr ~indirect() + { + static_assert(sizeof(_Tp) != 0, "must be a complete type"); + _M_reset(nullptr); + } + + constexpr indirect& + operator=(const indirect& __other) + { + static_assert(is_copy_assignable_v<_Tp>); + static_assert(is_copy_constructible_v<_Tp>); + + if (__builtin_addressof(__other) == this) [[unlikely]] + return *this; + + constexpr bool __pocca + = _ATraits::propagate_on_container_copy_assignment::value; + + pointer __ptr = nullptr; + if (__other._M_objp) + { + if (_ATraits::is_always_equal::value + || _M_alloc == __other._M_alloc) + { + if (_M_objp) + { + *_M_objp = __other.__get(); + if constexpr (__pocca) + _M_alloc = __other._M_alloc; + return *this; + } + } + const indirect& __x = __pocca ? __other : *this; + __ptr = __x._M_make_obj(__other.__get()); + } + + _M_reset(__ptr); + + if constexpr (__pocca) + _M_alloc = __other._M_alloc; + + return *this; + } + + constexpr indirect& + operator=(indirect&& __other) + noexcept(_ATraits::propagate_on_container_move_assignment::value + || _ATraits::is_always_equal::value) + { + // N5008 says is_copy_constructible_v<T> here, but that seems wrong. + // We only require move-constructible, and only for unequal allocators. + + if (__builtin_addressof(__other) == this) [[unlikely]] + return *this; + + constexpr bool __pocma + = _ATraits::propagate_on_container_move_assignment::value; + + 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(is_move_constructible_v<_Tp>); + __ptr = _M_make_obj(std::move(*__other._M_objp)); + } + + _M_reset(__ptr); + + if constexpr (__pocma) + _M_alloc = __other._M_alloc; + + return *this; + } + + template<typename _Up = _Tp> + requires (!is_same_v<remove_cvref_t<_Up>, indirect>) + && is_constructible_v<_Tp, _Up> && is_assignable_v<_Tp&, _Up> + constexpr indirect& + operator=(_Up&& __u) + { + if (_M_objp == nullptr) + _M_objp = _M_make_obj(std::forward<_Up>(__u)); + else + *_M_objp = std::forward<_Up>(__u); + + return *this; + } + + template<typename _Self> + constexpr auto&& + operator*(this _Self&& __self) noexcept + { + __glibcxx_assert(__self._M_objp != nullptr); + return std::forward_like<_Self>(*((_Self)__self)._M_objp); + } + + constexpr const_pointer + operator->() const noexcept + { + // Do we want to enforce this? __glibcxx_assert(_M_objp != nullptr); + return _M_objp; + } + + constexpr pointer + operator->() noexcept + { + // Do we want to enforce this? __glibcxx_assert(_M_objp != nullptr); + return _M_objp; + } + + constexpr bool + valueless_after_move() const noexcept { return _M_objp == nullptr; } + + constexpr allocator_type + get_allocator() const noexcept { return _M_alloc; } + + constexpr void + swap(indirect& __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(indirect& __lhs, indirect& __rhs) + noexcept(_ATraits::propagate_on_container_swap::value + || _ATraits::is_always_equal::value) + { __lhs.swap(__rhs); } + + template<typename _Up, typename _Alloc2> + requires requires (const _Tp& __t, const _Up& __u) { __t == __u; } + friend constexpr bool + operator==(const indirect& __lhs, const indirect<_Up, _Alloc2>& __rhs) + noexcept(noexcept(*__lhs == *__rhs)) + { + if (!__lhs._M_objp || !__rhs._M_objp) + return bool(__lhs._M_objp) == bool(__rhs._M_objp); + else + return __lhs.__get() == __rhs.__get(); + } + + template<typename _Up> + requires (!__is_indirect<_Up>) // See PR c++/99599 + && requires (const _Tp& __t, const _Up& __u) { __t == __u; } + friend constexpr bool + operator==(const indirect& __lhs, const _Up& __rhs) + noexcept(noexcept(*__lhs == __rhs)) + { + if (!__lhs._M_objp) + return false; + else + return __lhs.__get() == __rhs; + } + + template<typename _Up, typename _Alloc2> + friend constexpr __detail::__synth3way_t<_Tp, _Up> + operator<=>(const indirect& __lhs, const indirect<_Up, _Alloc2>& __rhs) + noexcept(noexcept(__detail::__synth3way(*__lhs, *__rhs))) + { + if (!__lhs._M_objp || !__rhs._M_objp) + return bool(__lhs._M_objp) <=> bool(__rhs._M_objp); + else + return __detail::__synth3way(__lhs.__get(), __rhs.__get()); + } + + template<typename _Up> + requires (!__is_indirect<_Up>) // See PR c++/99599 + friend constexpr __detail::__synth3way_t<_Tp, _Up> + operator<=>(const indirect& __lhs, const _Up& __rhs) + noexcept(noexcept(__detail::__synth3way(*__lhs, __rhs))) + { + if (!__lhs._M_objp) + return strong_ordering::less; + else + return __detail::__synth3way(__lhs.__get(), __rhs); + } + + private: + template<typename, typename> friend class indirect; + + constexpr void + _M_reset(pointer __ptr) noexcept + { + if (_M_objp) + { + _ATraits::destroy(_M_alloc, std::to_address(_M_objp)); + _ATraits::deallocate(_M_alloc, _M_objp, 1); + } + _M_objp = __ptr; + } + + template<typename... _Args> + constexpr pointer + _M_make_obj(_Args&&... __args) const + { + _Scoped_allocation __sa(_M_alloc, in_place, + std::forward<_Args>(__args)...); + return __sa.release(); + } + + // Enforces is_constructible check and then calls _M_make_obj. + template<typename... _Args> + [[__gnu__::__always_inline__]] + constexpr pointer + _M_make_obj_chk(_Args&&... __args) const + { + static_assert(is_constructible_v<_Tp, _Args...>); + return _M_make_obj(std::forward<_Args>(__args)...); + } + + // Always-const accessor that avoids ADL for operator*. + // This can be preferable to using *_M_objp because that might give _Tp&. + // This can be preferable to using **this because that does ADL. + [[__gnu__::__always_inline__]] + constexpr const _Tp& + __get() const noexcept + { return *_M_objp; } + + [[no_unique_address]] _Alloc _M_alloc = _Alloc(); + pointer _M_objp; // Pointer to the owned object. + }; + + template<typename _Value> + indirect(_Value) -> indirect<_Value>; + + template<typename _Alloc, typename _Value> + indirect(allocator_arg_t, _Alloc, _Value) + -> indirect<_Value, __alloc_rebind<_Alloc, _Value>>; + + // [indirect.hash], hash support + template<typename _Tp, typename _Alloc> + requires is_default_constructible_v<hash<_Tp>> + struct hash<indirect<_Tp, _Alloc>> + { + constexpr size_t + operator()(const indirect<_Tp, _Alloc>& __t) const + noexcept(noexcept(hash<_Tp>{}(*__t))) + { + // We pick an arbitrary hash for valueless indirect objects + // which hopefully usual values of _Tp won't typically hash to. + if (__t.valueless_after_move()) + return -4444zu; + return hash<_Tp>{}(*__t); + } + }; + + template<typename _Tp, typename _Alloc> + struct __is_fast_hash<hash<indirect<_Tp, _Alloc>>> + : __is_fast_hash<hash<_Tp>> + { }; +#endif // __glibcxx_indirect + + _GLIBCXX_END_NAMESPACE_VERSION +} // namespace +#endif // C++26 __glibcxx_indirect || __glibcxx_polymorphic + +#endif // _GLIBCXX_INDIRECT_H diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 6ca148f0488..913ab1d3274 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1961,6 +1961,15 @@ ftms = { }; }; +ftms = { + name = indirect; + 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 48a090c14a3..67bda6a8c63 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -2193,4 +2193,14 @@ #endif /* !defined(__cpp_lib_modules) && defined(__glibcxx_want_modules) */ #undef __glibcxx_want_modules +#if !defined(__cpp_lib_indirect) +# if (__cplusplus > 202302L) && _GLIBCXX_HOSTED +# define __glibcxx_indirect 202502L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_indirect) +# define __cpp_lib_indirect 202502L +# endif +# endif +#endif /* !defined(__cpp_lib_indirect) && defined(__glibcxx_want_indirect) */ +#undef __glibcxx_want_indirect + #undef __glibcxx_want_all diff --git a/libstdc++-v3/include/std/memory b/libstdc++-v3/include/std/memory index 78a1250d29a..d64e65cb6ce 100644 --- a/libstdc++-v3/include/std/memory +++ b/libstdc++-v3/include/std/memory @@ -97,6 +97,10 @@ # include <bits/out_ptr.h> #endif +#if __cplusplus > 202302L +# include <bits/indirect.h> +#endif + #define __glibcxx_want_addressof_constexpr #define __glibcxx_want_allocator_traits_is_always_equal #define __glibcxx_want_assume_aligned @@ -105,6 +109,7 @@ #define __glibcxx_want_constexpr_dynamic_alloc #define __glibcxx_want_constexpr_memory #define __glibcxx_want_enable_shared_from_this +#define __glibcxx_want_indirect #define __glibcxx_want_make_unique #define __glibcxx_want_out_ptr #define __glibcxx_want_parallel_algorithm diff --git a/libstdc++-v3/testsuite/std/memory/indirect/copy.cc b/libstdc++-v3/testsuite/std/memory/indirect/copy.cc new file mode 100644 index 00000000000..0ac6e92a921 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/copy.cc @@ -0,0 +1,121 @@ +// { dg-do run { target c++26 } } + +#include <memory> +#include <scoped_allocator> +#include <utility> +#include <vector> + +#include <testsuite_hooks.h> +#include <testsuite_allocator.h> + +using __gnu_test::tracker_allocator; +using Counter = __gnu_test::tracker_allocator_counter; +using Vector = std::vector<int>; +using Indirect = std::indirect<Vector, tracker_allocator<Vector>>; +const Indirect src(std::in_place, {1, 2, 3}); + +constexpr void +test_ctor() +{ + Counter::reset(); + Indirect i1(src); + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Counter::reset(); + Indirect i2(std::allocator_arg, {}, src); + VERIFY( *i2 == *src ); + VERIFY( &*i2 != &*src ); + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +constexpr void +test_assign() +{ + Indirect i1; + Counter::reset(); + + i1 = src; + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + auto(std::move(i1)); + Counter::reset(); + + i1 = src; + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +constexpr void +test_valueless() +{ + Indirect e; + auto(std::move(e)); + VERIFY( e.valueless_after_move() ); + + Counter::reset(); + Indirect 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 ); + + Indirect 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 ); + + Indirect i3(src); + Counter::reset(); + i3 = e; + VERIFY( i3.valueless_after_move() ); + 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(); + 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/indirect/copy_alloc.cc b/libstdc++-v3/testsuite/std/memory/indirect/copy_alloc.cc new file mode 100644 index 00000000000..d5865b9a580 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/copy_alloc.cc @@ -0,0 +1,228 @@ +// { dg-do run { target c++26 } } + +#include <memory> +#include <scoped_allocator> +#include <utility> +#include <vector> + +#include <testsuite_hooks.h> +#include <testsuite_allocator.h> + +using __gnu_test::propagating_allocator; +using __gnu_test::tracker_allocator; +using Counter = __gnu_test::tracker_allocator_counter; + +template<bool Propagate> +constexpr void +test_ctor() +{ + using PropAlloc = propagating_allocator<int, Propagate>; + using Vector = std::vector<int, PropAlloc>; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator<Vector, Propagate, tracker_allocator<Vector>>, + PropAlloc>; + using Indirect = std::indirect<Vector, ScopedAlloc>; + + const Indirect src(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place, {1, 2, 3}); + + Counter::reset(); + Indirect i1(src); + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + if (Propagate) + { + VERIFY( i1->get_allocator().get_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + } + else + { + VERIFY( i1->get_allocator().get_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() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + + Counter::reset(); + Indirect i2(std::allocator_arg, ScopedAlloc{33, 44}, src); + VERIFY( *i2 == *src ); + VERIFY( &*i2 != &*src ); + VERIFY( i2->get_allocator().get_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() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template<bool Propagate> +constexpr void +test_assign() +{ + using PropAlloc = propagating_allocator<int, Propagate>; + using Vector = std::vector<int, PropAlloc>; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator<Vector, Propagate, tracker_allocator<Vector>>, + PropAlloc>; + using Indirect = std::indirect<Vector, ScopedAlloc>; + + const Indirect src(std::allocator_arg, ScopedAlloc{11, 22}, + std::in_place, {1, 2, 3}); + + Indirect i1(std::allocator_arg, ScopedAlloc{11, 22}); + Counter::reset(); + + i1 = src; + VERIFY( *i1 == *src ); + VERIFY( &*i1 != &*src ); + VERIFY( i1->get_allocator().get_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + VERIFY( Counter::get_allocation_count() == 0 ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Indirect i2(std::allocator_arg, ScopedAlloc{33, 44}); + Counter::reset(); + + i2 = src; + VERIFY( *i2 == *src ); + VERIFY( &*i2 != &*src ); + if (Propagate) + { + VERIFY( i2->get_allocator().get_personality() == 22 ); + VERIFY( i2.get_allocator().get_personality() == 11 ); + } + else + { + VERIFY( i2->get_allocator().get_personality() == 44 ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + } + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_construct_count() == 1 ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Indirect i3(std::allocator_arg, ScopedAlloc{11, 22}); + auto(std::move(i3)); + Counter::reset(); + + i3 = src; + VERIFY( *i3 == *src ); + VERIFY( &*i3 != &*src ); + VERIFY( i3->get_allocator().get_personality() == 22 ); + VERIFY( i3.get_allocator().get_personality() == 11 ); + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); + + Indirect 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_allocator().get_personality() == 22 ); + VERIFY( i4.get_allocator().get_personality() == 11 ); + } + else + { + VERIFY( i4->get_allocator().get_personality() == 44 ); + VERIFY( i4.get_allocator().get_personality() == 33 ); + } + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_construct_count() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template<bool Propagate> +constexpr void +test_valueless() +{ + using PropAlloc = propagating_allocator<int, Propagate>; + using Vector = std::vector<int, PropAlloc>; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator<Vector, Propagate, tracker_allocator<Vector>>, + PropAlloc>; + using Indirect = std::indirect<Vector, ScopedAlloc>; + + Indirect e(std::allocator_arg, ScopedAlloc{11, 22}); + auto(std::move(e)); + VERIFY( e.valueless_after_move() ); + + Counter::reset(); + Indirect 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(); + Indirect 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 ); + + Indirect 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<bool Propagate> +constexpr void +test_all() +{ + test_ctor<Propagate>(); + test_assign<Propagate>(); + test_valueless<Propagate>(); +} + +int main() +{ + test_all<true>(); + test_all<false>(); + + static_assert([] { + test_all<true>(); + test_all<false>(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/indirect/ctor.cc b/libstdc++-v3/testsuite/std/memory/indirect/ctor.cc new file mode 100644 index 00000000000..67e7a8aba03 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/ctor.cc @@ -0,0 +1,203 @@ +// { dg-do run { target c++26 } } + +#include <memory> +#include <scoped_allocator> +#include <utility> +#include <vector> + +#ifndef __cpp_lib_indirect +# error __cpp_lib_indirect feature test macro missing in <memory> +#elif __cpp_lib_indirect != 202502 +# error __cpp_lib_indirect feature test macro has wrong value in <memory> +#endif + +#include <testsuite_hooks.h> +#include <testsuite_allocator.h> + +using __gnu_test::uneq_allocator; +using UneqAlloc = uneq_allocator<int>; +using ScopedAlloc = std::scoped_allocator_adaptor< + uneq_allocator<std::vector<int, UneqAlloc>>, + UneqAlloc>; + +struct Obj +{ + int i; + char c[2]; +}; + +constexpr void +test_deduction_guides() +{ + const Obj o{}; + std::indirect i1(o); + static_assert(std::is_same_v<decltype(i1), std::indirect<Obj>>); + + using Alloc = __gnu_test::SimpleAllocator<Obj>; + Alloc a; + std::indirect i2(std::allocator_arg, a, o); + static_assert(std::is_same_v<decltype(i2), std::indirect<Obj, Alloc>>); +} + +constexpr void +test_default_ctor() +{ + using __gnu_test::default_init_allocator; + + std::indirect<Obj, default_init_allocator<Obj>> i1; + default_init_allocator<int> 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::indirect<Obj, default_init_allocator<Obj>> i2(std::allocator_arg, a); + VERIFY( i2.get_allocator() == a ); + + // Object is constructed using allocator-aware constructor. + std::indirect<std::vector<int, UneqAlloc>, 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::indirect<Obj> const& i) + { + VERIFY( i->i == 1 ); + VERIFY( i->c[0] == '2' ); + VERIFY( i->c[1] == '3' ); + }; + + std::indirect<Obj> i1(std::as_const(obj)); + verify(i1); + std::indirect<Obj> i2(std::move(std::as_const(obj))); + verify(i2); + std::indirect<Obj> i3(obj); + verify(i3); + std::indirect<Obj> i4(std::move(obj)); + verify(i4); + + std::indirect<Obj> i5({1, {'2', '3'}}); + verify(i5); + + // Aggregate parens init + std::indirect<Obj> i6(7); + VERIFY( i6->i == 7 ); + + std::vector<int, UneqAlloc> v{1, 2, 3, 4, 5}; + // Object is constructed using allocator-aware constructor. + std::indirect<std::vector<int, UneqAlloc>, 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::indirect<std::vector<int, UneqAlloc>, 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::indirect<Obj> i1(std::in_place); + VERIFY( i1->i == 0 ); + VERIFY( i1->c[0] == 0 ); + VERIFY( i1->c[1] == 0 ); + + std::indirect<Obj> i2(std::in_place, 10); + VERIFY( i2->i == 10 ); + VERIFY( i2->c[0] == 0 ); + VERIFY( i2->c[1] == 0 ); + + std::indirect<Obj, uneq_allocator<Obj>> + i3(std::allocator_arg, 42, std::in_place); + VERIFY( i3->i == 0 ); + VERIFY( i3->c[0] == 0 ); + VERIFY( i3->c[1] == 0 ); + VERIFY( i3.get_allocator().get_personality() == 42 ); + + std::indirect<Obj, uneq_allocator<Obj>> + i4(std::allocator_arg, 42, std::in_place, 10); + VERIFY( i4->i == 10 ); + VERIFY( i4->c[0] == 0 ); + VERIFY( i4->c[1] == 0 ); + VERIFY( i4.get_allocator().get_personality() == 42 ); + + std::indirect<std::vector<int>> i5(std::in_place); + VERIFY( i5->size() == 0 ); + VERIFY( i5->at(0) == 13 ); + + std::indirect<std::vector<int>> i6(std::in_place, 5, 13); + VERIFY( i6->size() == 5 ); + VERIFY( i6->at(0) == 13 ); + + std::indirect<std::vector<int>> i7(std::in_place, {1, 2, 3, 4}); + VERIFY( i7->size() == 4 ); + VERIFY( i7->at(2) == 3 ); + + std::indirect<std::vector<int, UneqAlloc>> + i8(std::in_place, UneqAlloc{42}); + VERIFY( i8->size() == 0 ); + VERIFY( i8->get_allocator().get_personality() == 42 ); + + std::indirect<std::vector<int, UneqAlloc>> + i9(std::in_place, 5, 13, UneqAlloc{42}); + VERIFY( i9->size() == 5 ); + VERIFY( i9->at(0) == 13 ); + VERIFY( i9->get_allocator().get_personality() == 42 ); + + std::indirect<std::vector<int, UneqAlloc>> + i10(std::in_place, {1, 2, 3, 4}, UneqAlloc{42}); + VERIFY( i10->size() == 4 ); + VERIFY( i10->at(2) == 3 ); + VERIFY( i10->get_allocator().get_personality() == 42 ); + + std::indirect<std::vector<int, UneqAlloc>, ScopedAlloc> + i14(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place); + VERIFY( i14->size() == 0 ); + VERIFY( i14->get_allocator().get_personality() == 22 ); + VERIFY( i14.get_allocator().get_personality() == 11 ); + + std::indirect<std::vector<int, UneqAlloc>, ScopedAlloc> + i15(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place, 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::indirect<std::vector<int, UneqAlloc>, ScopedAlloc> + i16(std::allocator_arg, ScopedAlloc(11, 22), + std::in_place, {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(); + + static_assert([] { + test_default_ctor(); + test_forwarding_ctor(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/indirect/incomplete.cc b/libstdc++-v3/testsuite/std/memory/indirect/incomplete.cc new file mode 100644 index 00000000000..1faf13d1bcb --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/incomplete.cc @@ -0,0 +1,38 @@ +// { dg-do compile { target c++26 } } + +#include <memory> + +struct Incomplete; +bool operator==(const Incomplete&, const Incomplete&); +std::strong_ordering operator<=>(const Incomplete&, const Incomplete&); + +template<> +struct std::hash<Incomplete> +{ + static std::size_t operator()(const Incomplete& c); +}; + +std::indirect<Incomplete>* +test_move(std::indirect<Incomplete>& i1, std::indirect<Incomplete>& i2) +{ + i2.swap(i2); + return new std::indirect<Incomplete>(std::move(i1)); +} + +void +test_relops(std::indirect<Incomplete> const& i1, Incomplete const& o) +{ + void(i1 == i1); + void(i1 < i1); + void(i1 >= i1); + + void(i1 != o); + void(i1 < o); +} + +void +test_hash(std::indirect<Incomplete> const& i1) +{ + std::hash<std::indirect<Incomplete>> h; + h(i1); +} diff --git a/libstdc++-v3/testsuite/std/memory/indirect/invalid_neg.cc b/libstdc++-v3/testsuite/std/memory/indirect/invalid_neg.cc new file mode 100644 index 00000000000..82e7e844d17 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/invalid_neg.cc @@ -0,0 +1,28 @@ +// { dg-do compile { target c++26 } } + +#include <memory> + +// In every specialization indirect<T, Allocator>, if the type +// allocator_traits<Allocator>::value_type is not the same type as T, +// the program is ill-formed. +using T1 = std::indirect<int, std::allocator<long>>::value_type; // { dg-error "here" } + +// A program that instantiates the definition of the template +// indirect<T, Allocator> 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::indirect<int&>::value_type; // { dg-error "here" } + +using T3 = std::indirect<int[1]>::value_type; // { dg-error "here" } + +using T4 = std::indirect<std::in_place_t>::value_type; // { dg-error "here" } + +using T5 = std::indirect<std::in_place_type_t<int>>::value_type; // { dg-error "here" } + +using T6 = std::indirect<const int>::value_type; // { dg-error "here" } + +using T7 = std::indirect<volatile int>::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/indirect/move.cc b/libstdc++-v3/testsuite/std/memory/indirect/move.cc new file mode 100644 index 00000000000..6e87c60adb0 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/move.cc @@ -0,0 +1,144 @@ +// { dg-do run { target c++26 } } + +#include <memory> +#include <scoped_allocator> +#include <utility> +#include <vector> +#include <optional> + +#include <testsuite_hooks.h> +#include <testsuite_allocator.h> + +using __gnu_test::tracker_allocator; +using Counter = __gnu_test::tracker_allocator_counter; +using Vector = std::vector<int>; +using Indirect = std::indirect<Vector, tracker_allocator<Vector>>; +const Indirect val(std::in_place, {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<Indirect> src; + auto make = [&src] -> Indirect&& { + src.emplace(val); + Counter::reset(); + return std::move(*src); + }; + + Indirect i1(make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + verifyNoAllocations(); + + Indirect i2(std::allocator_arg, {}, make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i2 == *val ); + verifyNoAllocations(); +} + +constexpr void +test_assign() +{ + std::optional<Indirect> src; + auto make = [&src] -> Indirect&& { + src.emplace(val); + Counter::reset(); + return std::move(*src); + }; + + Indirect i1; + + i1 = make(); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + 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 ); + + auto(std::move(i1)); + i1 = make(); + VERIFY( *i1 == *val ); + VERIFY( src->valueless_after_move() ); + verifyNoAllocations(); +} + +constexpr void +test_swap() +{ + const Indirect val1(std::in_place, {1, 2, 3}); + const Indirect val2(std::in_place, {2, 4, 6}); + + Indirect i1(val1); + Indirect 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 = [] { + Indirect res; + auto(std::move(res)); + Counter::reset(); + return res; + }; + + Indirect i1(e()); + VERIFY( i1.valueless_after_move() ); + verifyNoAllocations(); + + Indirect i2(std::allocator_arg, {}, e()); + VERIFY( i2.valueless_after_move() ); + verifyNoAllocations(); + + Indirect i3(val); + i3 = e(); + 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 ); + + 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/indirect/move_alloc.cc b/libstdc++-v3/testsuite/std/memory/indirect/move_alloc.cc new file mode 100644 index 00000000000..cd6f90dcdc5 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/move_alloc.cc @@ -0,0 +1,296 @@ +// { dg-do run { target c++26 } } + +#include <memory> +#include <scoped_allocator> +#include <utility> +#include <vector> +#include <optional> + +#include <testsuite_hooks.h> +#include <testsuite_allocator.h> + +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<bool Propagate> +constexpr void +test_ctor() +{ + using PropAlloc = propagating_allocator<int, Propagate>; + using Vector = std::vector<int, PropAlloc>; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator<Vector, Propagate, tracker_allocator<Vector>>, + PropAlloc>; + using Indirect = std::indirect<Vector, ScopedAlloc>; + + const Indirect val(std::in_place, {1, 2, 3}); + std::optional<Indirect> src; + auto make = [&val, &src] -> Indirect&& { + src.emplace(std::allocator_arg, ScopedAlloc{11, 22}, val); + Counter::reset(); + return std::move(*src); + }; + + Indirect i1(make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + VERIFY( i1->get_allocator().get_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Indirect i2(std::allocator_arg, ScopedAlloc{11, 22}, make()); + VERIFY( src->valueless_after_move() ); + VERIFY( *i2 == *val ); + VERIFY( i2->get_allocator().get_personality() == 22 ); + VERIFY( i2.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Indirect i3(std::allocator_arg, ScopedAlloc{33, 44}, make()); + // We move-from contained object + VERIFY( !src->valueless_after_move() ); + VERIFY( *i3 == *val ); + VERIFY( i3->get_allocator().get_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() == 1 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template<bool Propagate> +constexpr void +test_assign() +{ + using PropAlloc = propagating_allocator<int, Propagate>; + using Vector = std::vector<int, PropAlloc>; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator<Vector, Propagate, tracker_allocator<Vector>>, + PropAlloc>; + using Indirect = std::indirect<Vector, ScopedAlloc>; + + const Indirect val(std::in_place, {1, 2, 3}); + std::optional<Indirect> src; + auto make = [&val, &src] -> Indirect&& { + src.emplace(std::allocator_arg, ScopedAlloc{11, 22}, val); + Counter::reset(); + return std::move(*src); + }; + + Indirect i1(std::allocator_arg, ScopedAlloc{11, 22}); + + i1 = make(); + VERIFY( src->valueless_after_move() ); + VERIFY( *i1 == *val ); + VERIFY( i1->get_allocator().get_personality() == 22 ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + 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 ); + + Indirect i2(std::allocator_arg, ScopedAlloc{33, 44}); + + i2 = make(); + VERIFY( *i2 == *val ); + if (Propagate) + { + VERIFY( src->valueless_after_move() ); + VERIFY( i2->get_allocator().get_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_allocator().get_personality() == 44 ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_construct_count() == 1 ); + } + VERIFY( Counter::get_deallocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_destruct_count() == 1 ); + + Indirect i3(std::allocator_arg, ScopedAlloc{11, 22}); + auto(std::move(i3)); + + i3 = make(); + VERIFY( *i3 == *val ); + VERIFY( src->valueless_after_move() ); + VERIFY( i3->get_allocator().get_personality() == 22 ); + VERIFY( i3.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Indirect i4(std::allocator_arg, ScopedAlloc{33, 44}); + auto(std::move(i4)); + + i4 = make(); + VERIFY( *i4 == *val ); + if (Propagate) + { + VERIFY( src->valueless_after_move() ); + VERIFY( i4->get_allocator().get_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_allocator().get_personality() == 44 ); + VERIFY( i4.get_allocator().get_personality() == 33 ); + VERIFY( Counter::get_allocation_count() == sizeof(Vector) ); + VERIFY( Counter::get_construct_count() == 1 ); + } + VERIFY( Counter::get_deallocation_count() == 0 ); + VERIFY( Counter::get_destruct_count() == 0 ); +} + +template<bool Propagate> +constexpr void +test_swap() +{ + using PropAlloc = propagating_allocator<int, Propagate>; + using Vector = std::vector<int, PropAlloc>; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator<Vector, Propagate, tracker_allocator<Vector>>, + PropAlloc>; + using Indirect = std::indirect<Vector, ScopedAlloc>; + + const Indirect val1(std::in_place, {1, 2, 3}); + const Indirect val2(std::in_place, {2, 4, 6}); + + Indirect i1(std::allocator_arg, ScopedAlloc{11, 22}, val1); + Indirect 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; + + Indirect i3(std::allocator_arg, ScopedAlloc{33, 44}, val2); + Counter::reset(); + i1.swap(i3); + VERIFY( *i1 == *val2 ); + VERIFY( i1->get_allocator().get_personality() == 44 ); + VERIFY( i1.get_allocator().get_personality() == 33 ); + VERIFY( *i3 == *val1 ); + VERIFY( i3->get_allocator().get_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_allocator().get_personality() == 44 ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + verifyNoAllocations(); +} + +template<bool Propagate> +constexpr void +test_valueless() +{ + using PropAlloc = propagating_allocator<int, Propagate>; + using Vector = std::vector<int, PropAlloc>; + using ScopedAlloc = std::scoped_allocator_adaptor< + propagating_allocator<Vector, Propagate, tracker_allocator<Vector>>, + PropAlloc>; + using Indirect = std::indirect<Vector, ScopedAlloc>; + + auto e = [] { + Indirect res(std::allocator_arg, ScopedAlloc{11, 22}); + auto(std::move(res)); + Counter::reset(); + return res; + }; + + Indirect i1(e()); + VERIFY( i1.valueless_after_move() ); + VERIFY( i1.get_allocator().get_personality() == 11 ); + verifyNoAllocations(); + + Indirect i2(std::allocator_arg, ScopedAlloc{33, 44}, e()); + VERIFY( i2.valueless_after_move() ); + VERIFY( i2.get_allocator().get_personality() == 33 ); + verifyNoAllocations(); + + Indirect 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; + + Indirect i4(std::allocator_arg, ScopedAlloc{33, 44}, e()); + i4.swap(i1); + verifyNoAllocations(); +} + +template<bool Propagate> +constexpr void +test_all() +{ + test_ctor<Propagate>(); + test_assign<Propagate>(); + test_swap<Propagate>(); + test_valueless<Propagate>(); +} + +int main() +{ + test_all<true>(); + test_all<false>(); + + static_assert([] { + test_all<true>(); + test_all<false>(); + return true; + }); +} diff --git a/libstdc++-v3/testsuite/std/memory/indirect/relops.cc b/libstdc++-v3/testsuite/std/memory/indirect/relops.cc new file mode 100644 index 00000000000..d77fef2a430 --- /dev/null +++ b/libstdc++-v3/testsuite/std/memory/indirect/relops.cc @@ -0,0 +1,82 @@ +// { dg-do run { target c++26 } } + +#include <memory> +#include <testsuite_hooks.h> + +struct Obj +{ + int i; + constexpr auto operator<=>(const Obj&) const = default; +}; + +template<> +struct std::hash<Obj> +{ + static size_t operator()(Obj const& obj) + { return std::hash<int>{}(obj.i); } +}; + +constexpr void +test_relops() +{ + std::indirect<Obj> i1; + VERIFY( i1 == i1 ); + VERIFY( i1 <= i1 ); + VERIFY( i1 >= i1 ); + + std::indirect<Obj> i2 = std::move(i1); // make i1 valueless + VERIFY( i1 == i1 ); + VERIFY( i2 == i2 ); + VERIFY( i2 != i1 ); + VERIFY( i1 < i2 ); + VERIFY( i2 >= i1 ); + + std::indirect<Obj> i3 = std::move(i2); // make i2 valueless + VERIFY( i2 == i1 ); + VERIFY( i2 >= i1 ); + VERIFY( i2 <= i1 ); + VERIFY( i3 > i2 ); +} + +constexpr void +test_comp_with_t() +{ + std::indirect<Obj> i1; + Obj o{2}; + VERIFY( i1 != o ); + VERIFY( i1 < o ); + + std::indirect<Obj> i2(Obj{2}); + VERIFY( i2 == o ); + VERIFY( i2 <= o ); + VERIFY( o <= i2 ); + + std::indirect<Obj> i3 = std::move(i2); // make i2 valueless + VERIFY( i2 != o ); + VERIFY( i2 < o ); +} + +void +test_hash() +{ + Obj o{5}; + std::indirect<Obj> i(o); + VERIFY( std::hash<std::indirect<Obj>>{}(i) + == std::hash<Obj>{}(o) ); + + auto(std::move(i)); // make i valueless + (void)std::hash<std::indirect<Obj>>{}(i); +} + +int main() +{ + test_relops(); + test_comp_with_t(); + test_hash(); + + static_assert([] { + test_relops(); + test_comp_with_t(); + return true; + }); +} -- 2.49.0