On 22/05/25 11:19 +0200, Tomasz Kamiński wrote:
From: Jonathan Wakely <jwak...@redhat.com>

This papers implements C++27 std::indirect as specified
in P3019 with ammendment to move assgiment 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>
---
Tested on x86_64-linux. OK for trunk?

Obviously I think it's OK because I wrote most of the code, but let's
wait a little while to see if others have comments.

If nobody else comments by the end of tomorrow then please push to
trunk.

And thanks for finishing this work!

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..32b2af9117d
--- /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}  XXX right header?
+ */
+
+#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



Reply via email to