libstdc++: Implement C++26 std::indirect [PR119152]

This patch implements C++26 std::indirect as specified
in P3019 with amendment to move assignment from LWG 4251.

	PR libstdc++/119152

libstdc++-v3/ChangeLog:

	* doc/doxygen/stdheader.cc: Added indirect.h file.
	* 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 <tkaminsk@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
This commit is contained in:
Jonathan Wakely
2024-03-21 23:07:56 +00:00
committed by Tomasz Kamiński
parent 545433e9bd
commit caf804b179
15 changed files with 1626 additions and 0 deletions

View File

@@ -106,6 +106,7 @@ void init_map()
headers["uses_allocator.h"] = "memory";
headers["uses_allocator_args.h"] = "memory";
headers["out_ptr.h"] = "memory";
headers["indirect.h"] = "memory";
headers["memory_resource.h"] = "memory_resource";
headers["unique_lock.h"] = "mutex";
headers["sat_arith.h"] = "numeric";

View File

@@ -211,6 +211,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 \

View File

@@ -564,6 +564,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 \

View File

@@ -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>
#if __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

View File

@@ -1969,6 +1969,15 @@ ftms = {
};
};
ftms = {
name = indirect;
values = {
v = 202502;
cxxmin = 26;
hosted = yes;
};
};
// Standard test specifications.
stds[97] = ">= 199711L";
stds[03] = ">= 199711L";

View File

@@ -2203,4 +2203,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

View File

@@ -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

View File

@@ -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;
});
}

View File

@@ -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;
});
}

View File

@@ -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;
});
}

View File

@@ -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);
}

View File

@@ -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" }

View File

@@ -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;
});
}

View File

@@ -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;
});
}

View File

@@ -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;
});
}