[libcxx-commits] [libcxx] [libc++] Implement the `indirect` half of P3019R14: Vocabulary Types for Composite Class Design (PR #166717)

Victor Chernyakin via libcxx-commits libcxx-commits at lists.llvm.org
Fri Nov 7 09:35:31 PST 2025


https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/166717

>From 0cf68e842293a96db80d9859926c1e664ce53f2c Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 05:17:48 +0000
Subject: [PATCH 01/11] [libc++] Implement the `indirect` half of P3019R11:
 Vocabulary Types for Composite Class Design

---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +
 libcxx/docs/Status/Cxx2cPapers.csv            |   2 +-
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__memory/indirect.h            | 332 ++++++++++++++++++
 libcxx/include/memory                         |  90 +++++
 libcxx/include/module.modulemap.in            |   1 +
 libcxx/include/version                        |   2 +
 libcxx/modules/std/memory.inc                 |   9 +
 .../indirect.obs/assert.arrow.pass.cpp        |  35 ++
 .../indirect.obs/assert.deref.pass.cpp        |  38 ++
 .../indirect.obs/deref.nodiscard.verify.cpp   |  25 ++
 .../get_allocator.nodiscard.verify.cpp        |  19 +
 .../valueless_after_move.nodiscard.verify.cpp |  19 +
 .../indirect.swap/assert.swap.pass.cpp        |  32 ++
 .../indirect/no_specializations.verify.cpp    |  23 ++
 .../memory.version.compile.pass.cpp           |  27 ++
 .../version.version.compile.pass.cpp          |  27 ++
 .../indirect/indirect.assign/copy.pass.cpp    | 125 +++++++
 .../indirect/indirect.assign/move.pass.cpp    | 167 +++++++++
 .../perfect_forwarding.pass.cpp               | 107 ++++++
 .../indirect/indirect.comp.with.t/eq.pass.cpp |  76 ++++
 .../indirect.comp.with.t/three_way.pass.cpp   |  72 ++++
 .../indirect/indirect.ctor/copy.pass.cpp      | 117 ++++++
 .../indirect/indirect.ctor/default.pass.cpp   |  91 +++++
 .../indirect.ctor/in_place_t.pass.cpp         | 125 +++++++
 .../indirect/indirect.ctor/init_list.pass.cpp | 113 ++++++
 .../indirect/indirect.ctor/move.pass.cpp      | 112 ++++++
 .../indirect.ctor/perfect_forwarding.pass.cpp | 104 ++++++
 .../indirect_array.verify.cpp                 |  24 ++
 .../indirect_cv_qualified.verify.cpp          |  26 ++
 .../indirect_in_place_t.verify.cpp            |  21 ++
 .../indirect_in_place_type_t.verify.cpp       |  21 ++
 .../indirect_nonobject.verify.cpp             |  24 ++
 .../indirect/indirect.hash/hash.pass.cpp      |  47 +++
 .../indirect/indirect.obs/arrow.pass.cpp      |  43 +++
 .../indirect/indirect.obs/deref.pass.cpp      |  54 +++
 .../indirect.obs/get_allocator.pass.cpp       |  38 ++
 .../valueless_after_move.pass.cpp             |  38 ++
 .../indirect/indirect.relops/eq.pass.cpp      |  86 +++++
 .../indirect.relops/three_way.pass.cpp        |  82 +++++
 .../indirect/indirect.swap/swap.pass.cpp      | 103 ++++++
 libcxx/test/support/test_allocator.h          |  14 +-
 libcxx/test/support/test_convertible.h        |   8 +
 .../generate_feature_test_macro_components.py |   5 +
 44 files changed, 2520 insertions(+), 7 deletions(-)
 create mode 100644 libcxx/include/__memory/indirect.h
 create mode 100644 libcxx/test/libcxx/memory/indirect/indirect.obs/assert.arrow.pass.cpp
 create mode 100644 libcxx/test/libcxx/memory/indirect/indirect.obs/assert.deref.pass.cpp
 create mode 100644 libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp
 create mode 100644 libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp
 create mode 100644 libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp
 create mode 100644 libcxx/test/libcxx/memory/indirect/indirect.swap/assert.swap.pass.cpp
 create mode 100644 libcxx/test/libcxx/memory/indirect/no_specializations.verify.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.assign/copy.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.assign/perfect_forwarding.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/eq.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/three_way.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.ctor/copy.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.ctor/default.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.ctor/in_place_t.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.ctor/init_list.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.ctor/perfect_forwarding.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_array.verify.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_cv_qualified.verify.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_t.verify.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_type_t.verify.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_nonobject.verify.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.hash/hash.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.obs/arrow.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.relops/eq.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.relops/three_way.pass.cpp
 create mode 100644 libcxx/test/std/utilities/memory/indirect/indirect.swap/swap.pass.cpp

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index d5ed9188b1b23..72ad135f61456 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -472,6 +472,8 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_hazard_pointer``                               *unimplemented*
     ---------------------------------------------------------- -----------------
+    ``__cpp_lib_indirect``                                     ``202502L``
+    ---------------------------------------------------------- -----------------
     ``__cpp_lib_inplace_vector``                               *unimplemented*
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_is_sufficiently_aligned``                      ``202411L``
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index e0e47b864d38f..590853b07c096 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -115,7 +115,7 @@
 "`P2846R6 <https://wg21.link/P2846R6>`__","``reserve_hint``: Eagerly reserving memory for not-quite-sized lazy ranges","2025-02 (Hagenberg)","","","`#127884 <https://github.com/llvm/llvm-project/issues/127884>`__",""
 "`P3471R4 <https://wg21.link/P3471R4>`__","Standard Library Hardening","2025-02 (Hagenberg)","","","`#127885 <https://github.com/llvm/llvm-project/issues/127885>`__",""
 "`P0447R28 <https://wg21.link/P0447R28>`__","Introduction of ``std::hive`` to the standard library","2025-02 (Hagenberg)","","","`#127886 <https://github.com/llvm/llvm-project/issues/127886>`__",""
-"`P3019R14 <https://wg21.link/P3019R14>`__","``indirect`` and ``polymorphic``: Vocabulary Types for Composite Class Design","2025-02 (Hagenberg)","","","`#127887 <https://github.com/llvm/llvm-project/issues/127887>`__",""
+"`P3019R14 <https://wg21.link/P3019R14>`__","``indirect`` and ``polymorphic``: Vocabulary Types for Composite Class Design","2025-02 (Hagenberg)","|Partial|","","`#127887 <https://github.com/llvm/llvm-project/issues/127887>`__","``polymorphic`` is not yet implemented."
 "","","","","","",""
 "`P2996R13 <https://wg21.link/P2996R13>`__","Reflection for C++26","2025-06 (Sofia)","","","`#148123 <https://github.com/llvm/llvm-project/issues/148123>`__",""
 "`P3394R4 <https://wg21.link/P3394R4>`__","Annotations for Reflection","2025-06 (Sofia)","","","`#148124 <https://github.com/llvm/llvm-project/issues/148124>`__",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 57032ce26d4fd..37587c5d56c31 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -582,6 +582,7 @@ set(files
   __memory/construct_at.h
   __memory/destroy.h
   __memory/destruct_n.h
+  __memory/indirect.h
   __memory/inout_ptr.h
   __memory/is_sufficiently_aligned.h
   __memory/noexcept_move_assign_container.h
diff --git a/libcxx/include/__memory/indirect.h b/libcxx/include/__memory/indirect.h
new file mode 100644
index 0000000000000..be55d2d0f18ef
--- /dev/null
+++ b/libcxx/include/__memory/indirect.h
@@ -0,0 +1,332 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___MEMORY_INDIRECT_H
+#define _LIBCPP___MEMORY_INDIRECT_H
+
+#include <__config>
+
+#include <__compare/strong_order.h>
+#include <__compare/synth_three_way.h>
+#include <__functional/hash.h>
+#include <__fwd/memory_resource.h>
+#include <__memory/addressof.h>
+#include <__memory/allocation_guard.h>
+#include <__memory/allocator_arg_t.h>
+#include <__memory/allocator_traits.h>
+#include <__memory/swap_allocator.h>
+#include <__type_traits/is_array.h>
+#include <__type_traits/is_object.h>
+#include <__type_traits/is_same.h>
+#include <__type_traits/remove_cv.h>
+#include <__utility/exchange.h>
+#include <__utility/forward.h>
+#include <__utility/in_place.h>
+#include <__utility/move.h>
+#include <__utility/swap.h>
+#include <initializer_list>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 26
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Tp, class _Allocator = allocator<_Tp>>
+class _LIBCPP_NO_SPECIALIZATIONS indirect {
+public:
+  using value_type     = _Tp;
+  using allocator_type = _Allocator;
+  using pointer        = allocator_traits<_Allocator>::pointer;
+  using const_pointer  = allocator_traits<_Allocator>::const_pointer;
+
+  static_assert(__check_valid_allocator<allocator_type>::value);
+  static_assert(is_same_v<typename allocator_type::value_type, value_type>);
+  static_assert(is_object_v<value_type>);
+  static_assert(!is_array_v<value_type>);
+  static_assert(!is_same_v<value_type, in_place_t>);
+  static_assert(!__is_inplace_type<value_type>::value);
+  static_assert(std::is_same_v<value_type, remove_cv_t<value_type>>,
+                "value_type must not be const or volatile qualified");
+
+  // [indirect.ctor], constructors
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect()
+    requires is_default_constructible_v<_Allocator>
+      : __p_(__allocate_owned_object(__alloc_)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a)
+      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect(const indirect& __other)
+      : __alloc_(allocator_traits<_Allocator>::select_on_container_copy_construction(__other.__alloc_)),
+        __p_(__other.valueless_after_move() ? nullptr : __allocate_owned_object(__alloc_, *__other)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, const indirect& __other)
+      : __alloc_(__a), __p_(__other.valueless_after_move() ? nullptr : __allocate_owned_object(__alloc_, *__other)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect(indirect&& __other) noexcept
+      : __alloc_(std::move(__other.__alloc_)), __p_(std::exchange(__other.__p_, nullptr)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, indirect&& __other) noexcept
+    requires allocator_traits<_Allocator>::is_always_equal::value
+      : __alloc_(__a), __p_(std::exchange(__other.__p_, nullptr)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, indirect&& __other) : __alloc_(__a) {
+    if (__other.valueless_after_move()) {
+      __p_ = nullptr;
+    } else if (__alloc_ == __other.__alloc_) {
+      __p_ = std::exchange(__other.__p_, nullptr);
+    } else {
+      __p_ = __allocate_owned_object(__alloc_, *std::move(__other));
+      __other.__destroy_owned_object();
+      __other.__p_ = nullptr;
+    }
+  }
+
+  template <class _U = _Tp>
+    requires(!is_same_v<remove_cvref_t<_U>, indirect> && !is_same_v<remove_cvref_t<_U>, in_place_t> &&
+             is_constructible_v<_Tp, _U> && is_default_constructible_v<_Allocator>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(_U&& __u)
+      : __p_(__allocate_owned_object(__alloc_, std::forward<_U>(__u))) {}
+
+  template <class _U = _Tp>
+    requires(!is_same_v<remove_cvref_t<_U>, indirect> && !is_same_v<remove_cvref_t<_U>, in_place_t> &&
+             is_constructible_v<_Tp, _U>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a, _U&& __u)
+      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, std::forward<_U>(__u))) {}
+
+  template <class... _Us>
+    requires(is_constructible_v<_Tp, _Us...> && is_default_constructible_v<_Allocator>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(in_place_t, _Us&&... __us)
+      : __p_(__allocate_owned_object(__alloc_, std::forward<_Us>(__us)...)) {}
+
+  template <class... _Us>
+    requires is_constructible_v<_Tp, _Us...>
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a, in_place_t, _Us&&... __us)
+      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, std::forward<_Us>(__us)...)) {}
+
+  template <class _I, class... _Us>
+    requires(is_constructible_v<_Tp, initializer_list<_I>&, _Us...> && is_default_constructible_v<_Allocator>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(in_place_t, initializer_list<_I> __ilist, _Us&&... __us)
+      : __p_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
+
+  template <class _I, class... _Us>
+    requires is_constructible_v<_Tp, initializer_list<_I>&, _Us...>
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(
+      allocator_arg_t, const _Allocator& __a, in_place_t, initializer_list<_I> __ilist, _Us&&... __us)
+      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
+
+  // [indirect.dtor], destructor
+  _LIBCPP_HIDE_FROM_ABI constexpr ~indirect() { __destroy_owned_object(); }
+
+  // [indirect.assign], assignment
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect& operator=(const indirect& __other) {
+    if (std::addressof(__other) == this)
+      return *this;
+
+    static constexpr bool __propagate_allocator =
+        allocator_traits<_Allocator>::propagate_on_container_copy_assignment::value;
+    if (__other.valueless_after_move()) {
+      __destroy_owned_object();
+      __p_ = nullptr;
+    } else if (!valueless_after_move() && __alloc_ == __other.__alloc_) {
+      *__p_ = *__other;
+    } else {
+      pointer __new_p;
+      if constexpr (__propagate_allocator) {
+        // We need a mutable instance of the allocator, so make a copy.
+        _Allocator __alloc_copy = __other.__alloc_;
+        __new_p                 = __allocate_owned_object(__alloc_copy, *__other);
+      } else {
+        __new_p = __allocate_owned_object(__alloc_, *__other);
+      }
+      __destroy_owned_object();
+      __p_ = __new_p;
+    }
+
+    if constexpr (__propagate_allocator)
+      __alloc_ = __other.__alloc_;
+
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect& operator=(indirect&& __other) noexcept(
+      allocator_traits<_Allocator>::propagate_on_container_move_assignment::value ||
+      allocator_traits<_Allocator>::is_always_equal::value) {
+    if (std::addressof(__other) == this)
+      return *this;
+
+    static constexpr bool __propagate_allocator =
+        allocator_traits<_Allocator>::propagate_on_container_move_assignment::value;
+
+    pointer __new_p;
+    if constexpr (__propagate_allocator || allocator_traits<_Allocator>::is_always_equal::value) {
+      __new_p = __other.__p_;
+    } else if (__other.valueless_after_move()) {
+      __new_p = nullptr;
+    } else if (__alloc_ == __other.__alloc_) {
+      __new_p = __other.__p_;
+    } else {
+      __new_p = __allocate_owned_object(__alloc_, *std::move(__other));
+      __other.__destroy_owned_object();
+    }
+    __other.__p_ = nullptr;
+    __destroy_owned_object();
+    __p_ = __new_p;
+
+    if constexpr (__propagate_allocator)
+      __alloc_ = __other.__alloc_;
+
+    return *this;
+  }
+
+  template <class _U = _Tp>
+    requires(!is_same_v<remove_cvref_t<_U>, indirect> && is_constructible_v<_Tp, _U> && is_assignable_v<_Tp&, _U>)
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect& operator=(_U&& __u) {
+    if (valueless_after_move())
+      __p_ = __allocate_owned_object(__alloc_, std::forward<_U>(__u));
+    else
+      *__p_ = std::forward<_U>(__u);
+    return *this;
+  }
+
+  // [indirect.obs], observers
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        !valueless_after_move(), "operator* called on a valueless std::indirect object");
+    return *__p_;
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        !valueless_after_move(), "operator* called on a valueless std::indirect object");
+    return *__p_;
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        !valueless_after_move(), "operator* called on a valueless std::indirect object");
+    return std::move(*__p_);
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        !valueless_after_move(), "operator* called on a valueless std::indirect object");
+    return std::move(*__p_);
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const_pointer operator->() const noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        !valueless_after_move(), "operator-> called on a valueless std::indirect object");
+    return __p_;
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr pointer operator->() noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        !valueless_after_move(), "operator-> called on a valueless std::indirect object");
+    return __p_;
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool valueless_after_move() const noexcept { return !__p_; }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr allocator_type get_allocator() const noexcept { return __alloc_; }
+
+  // [indirect.swap], swap
+  _LIBCPP_HIDE_FROM_ABI constexpr void
+  swap(indirect& __other) noexcept(allocator_traits<_Allocator>::propagate_on_container_swap::value ||
+                                   allocator_traits<_Allocator>::is_always_equal::value) {
+    _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
+        allocator_traits<_Allocator>::propagate_on_container_swap::value || get_allocator() == __other.get_allocator(),
+        "swapping std::indirect objects with different allocators");
+    std::swap(__p_, __other.__p_);
+    std::__swap_allocator(__alloc_, __other.__alloc_);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr void
+  swap(indirect& __lhs, indirect& __rhs) noexcept(noexcept(__lhs.swap(__rhs))) {
+    __lhs.swap(__rhs);
+  }
+
+  // [indirect.relops], relational operators
+  template <class _U, class _AA>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const indirect& __lhs, const indirect<_U, _AA>& __rhs) noexcept(noexcept(*__lhs == *__rhs)) {
+    return (__lhs.valueless_after_move() == __rhs.valueless_after_move()) &&
+           (__lhs.valueless_after_move() || *__lhs == *__rhs);
+  }
+
+  template <class _U, class _AA>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __synth_three_way_result<_Tp, _U>
+  operator<=>(const indirect& __lhs, const indirect<_U, _AA>& __rhs) {
+    if (__lhs.valueless_after_move() || __rhs.valueless_after_move())
+      return !__lhs.valueless_after_move() <=> !__rhs.valueless_after_move();
+    return std::__synth_three_way(*__lhs, *__rhs);
+  }
+
+  // [indirect.comp.with.t], comparison with T
+  template <class _U>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const indirect& __lhs, const _U& __rhs) noexcept(noexcept(*__lhs == __rhs)) {
+    return !__lhs.valueless_after_move() && *__lhs == __rhs;
+  }
+
+  template <class _U>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __synth_three_way_result<_Tp, _U>
+  operator<=>(const indirect& __lhs, const _U& __rhs) {
+    return __lhs.valueless_after_move() ? strong_ordering::less : std::__synth_three_way(*__lhs, __rhs);
+  }
+
+private:
+  template <class... _Us>
+  _LIBCPP_HIDE_FROM_ABI static constexpr pointer __allocate_owned_object(_Allocator& __a, _Us&&... __us) {
+    __allocation_guard<_Allocator> __guard(__a, 1);
+    allocator_traits<_Allocator>::construct(__a, __guard.__get(), std::forward<_Us>(__us)...);
+    return __guard.__release_ptr();
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_owned_object() noexcept {
+    if (!valueless_after_move()) {
+      allocator_traits<_Allocator>::destroy(__alloc_, __p_);
+      allocator_traits<_Allocator>::deallocate(__alloc_, __p_, 1);
+    }
+  }
+
+  _LIBCPP_NO_UNIQUE_ADDRESS _Allocator __alloc_ = _Allocator();
+  pointer __p_;
+};
+
+template <class _Value>
+indirect(_Value) -> indirect<_Value>;
+
+template <class _Allocator, class _Value>
+indirect(allocator_arg_t, _Allocator, _Value) -> indirect<_Value, __rebind_alloc<allocator_traits<_Allocator>, _Value>>;
+
+template <class _T, class _Allocator>
+  requires is_default_constructible_v<hash<_T>>
+struct hash<indirect<_T, _Allocator>> {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t operator()(const indirect<_T, _Allocator>& __i) const {
+    return __i.valueless_after_move() ? 0 : hash<_T>()(*__i);
+  }
+};
+
+namespace pmr {
+
+template <class _Tp>
+using indirect _LIBCPP_AVAILABILITY_PMR = indirect<_Tp, polymorphic_allocator<_Tp>>;
+
+} // namespace pmr
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 26
+
+#endif // _LIBCPP___MEMORY_INDIRECT_H
diff --git a/libcxx/include/memory b/libcxx/include/memory
index ca880c83a544d..a13b3071163c9 100644
--- a/libcxx/include/memory
+++ b/libcxx/include/memory
@@ -931,6 +931,92 @@ template<class Smart, class Pointer, class... Args>
 template<class Pointer = void, class Smart, class... Args>
   auto inout_ptr(Smart& s, Args&&... args);                 // since c++23
 
+// [indirect], class template indirect
+template<class T, class Allocator = allocator<T>>
+class indirect // since C++26
+{
+public:
+    using value_type = T;
+    using allocator_type = Allocator;
+    using pointer = allocator_traits<Allocator>::pointer;
+    using const_pointer = allocator_traits<Allocator>::const_pointer;
+
+    // [indirect.ctor], constructors
+    constexpr explicit indirect();
+    constexpr explicit indirect(allocator_arg_t, const Allocator& a);
+    constexpr indirect(const indirect& other);
+    constexpr indirect(allocator_arg_t, const Allocator& a, const indirect& other);
+    constexpr indirect(indirect&& other) noexcept;
+    constexpr indirect(allocator_arg_t, const Allocator& a, indirect&& other)
+      noexcept(see below);
+    template<class U = T>
+      constexpr explicit indirect(U&& u);
+    template<class U = T>
+      constexpr explicit indirect(allocator_arg_t, const Allocator& a, U&& u);
+    template<class... Us>
+      constexpr explicit indirect(in_place_t, Us&&... us);
+    template<class... Us>
+      constexpr explicit indirect(allocator_arg_t, const Allocator& a,
+                                  in_place_t, Us&&... us);
+    template<class I, class... Us>
+      constexpr explicit indirect(in_place_t, initializer_list<I> ilist, Us&&... us);
+    template<class I, class... Us>
+      constexpr explicit indirect(allocator_arg_t, const Allocator& a,
+                                  in_place_t, initializer_list<I> ilist, Us&&... us);
+
+    // [indirect.dtor], destructor
+    constexpr ~indirect();
+
+    // [indirect.assign], assignment
+    constexpr indirect& operator=(const indirect& other);
+    constexpr indirect& operator=(indirect&& other) noexcept(see below);
+    template<class U = T>
+      constexpr indirect& operator=(U&& u);
+
+    // [indirect.obs], observers
+    constexpr const T& operator*() const & noexcept;
+    constexpr T& operator*() & noexcept;
+    constexpr const T&& operator*() const && noexcept;
+    constexpr T&& operator*() && noexcept;
+    constexpr const_pointer operator->() const noexcept;
+    constexpr pointer operator->() noexcept;
+    constexpr bool valueless_after_move() const noexcept;
+    constexpr allocator_type get_allocator() const noexcept;
+
+    // [indirect.swap], swap
+    constexpr void swap(indirect& other) noexcept(see below);
+    friend constexpr void swap(indirect& lhs, indirect& rhs) noexcept(see below);
+
+    // [indirect.relops], relational operators
+    template<class U, class AA>
+      friend constexpr bool operator==(const indirect& lhs, const indirect<U, AA>& rhs)
+        noexcept(see below);
+    template<class U, class AA>
+      friend constexpr auto operator<=>(const indirect& lhs, const indirect<U, AA>& rhs)
+        -> synth-three-way-result<T, U>;
+
+    // [indirect.comp.with.t], comparison with T
+    template<class U>
+      friend constexpr bool operator==(const indirect& lhs, const U& rhs) noexcept(see below);
+    template<class U>
+      friend constexpr auto operator<=>(const indirect& lhs, const U& rhs)
+        -> synth-three-way-result<T, U>;
+};
+
+template<class Value>
+  indirect(Value) -> indirect<Value>;
+
+template<class Allocator, class Value>
+  indirect(allocator_arg_t, Allocator, Value)
+    -> indirect<Value, typename allocator_traits<Allocator>::template rebind_alloc<Value>>;
+
+// [indirect.hash], hash support
+template<class T, class Alloc> struct hash<indirect<T, Alloc>>; // since C++26
+
+namespace pmr {
+  template<class T> using indirect    = indirect<T, polymorphic_allocator<T>>;    // since C++26
+}
+
 }  // std
 
 */
@@ -978,6 +1064,10 @@ template<class Pointer = void, class Smart, class... Args>
 #    include <__memory/allocate_at_least.h>
 #  endif
 
+#  if _LIBCPP_STD_VER >= 26
+#    include <__memory/indirect.h>
+#  endif
+
 #  include <version>
 
 // [memory.syn]
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 24a2fe761943a..50a0cbde37a1e 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1654,6 +1654,7 @@ module std [system] {
     module destroy                            { header "__memory/destroy.h" }
     module destruct_n                         { header "__memory/destruct_n.h" }
     module fwd                                { header "__fwd/memory.h" }
+    module indirect                           { header "__memory/indirect.h" }
     module inout_ptr                          { header "__memory/inout_ptr.h" }
     module is_sufficiently_aligned            { header "__memory/is_sufficiently_aligned.h" }
     module noexcept_move_assign_container     { header "__memory/noexcept_move_assign_container.h" }
diff --git a/libcxx/include/version b/libcxx/include/version
index b0030602f854a..c7f34d3be3dd0 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -138,6 +138,7 @@ __cpp_lib_has_unique_object_representations             201606L <type_traits>
 __cpp_lib_hazard_pointer                                202306L <hazard_pointer>
 __cpp_lib_hypot                                         201603L <cmath>
 __cpp_lib_incomplete_container_elements                 201505L <forward_list> <list> <vector>
+__cpp_lib_indirect                                      202502L <memory>
 __cpp_lib_inplace_vector                                202406L <inplace_vector>
 __cpp_lib_int_pow2                                      202002L <bit>
 __cpp_lib_integer_comparison_functions                  202002L <utility>
@@ -581,6 +582,7 @@ __cpp_lib_void_t                                        201411L <type_traits>
 // # define __cpp_lib_function_ref                         202306L
 // # define __cpp_lib_generate_random                      202403L
 // # define __cpp_lib_hazard_pointer                       202306L
+# define __cpp_lib_indirect                             202502L
 // # define __cpp_lib_inplace_vector                       202406L
 # define __cpp_lib_is_sufficiently_aligned              202411L
 # if __has_builtin(__builtin_is_virtual_base_of)
diff --git a/libcxx/modules/std/memory.inc b/libcxx/modules/std/memory.inc
index c25e9e3443e9c..41829b2c2c29d 100644
--- a/libcxx/modules/std/memory.inc
+++ b/libcxx/modules/std/memory.inc
@@ -194,6 +194,15 @@ export namespace std {
   using std::inout_ptr;
 #endif // _LIBCPP_STD_VER >= 23
 
+#if _LIBCPP_STD_VER >= 26
+  // [indirect], class template indirect
+  using std::indirect;
+
+  namespace pmr {
+    using std::pmr::indirect;
+  }
+#endif // _LIBCPP_STD_VER >= 26
+
 #if _LIBCPP_HAS_THREADS
   // [depr.util.smartptr.shared.atomic]
   using std::atomic_is_lock_free;
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/assert.arrow.pass.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/assert.arrow.pass.cpp
new file mode 100644
index 0000000000000..bf81ee6cc829e
--- /dev/null
+++ b/libcxx/test/libcxx/memory/indirect/indirect.obs/assert.arrow.pass.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <memory>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  struct S {
+    int n = 0;
+  };
+  std::indirect<S> i;
+  auto(std::move(i));
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(i->n, "operator-> called on a valueless std::indirect object");
+  }
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(std::as_const(i)->n, "operator-> called on a valueless std::indirect object");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/assert.deref.pass.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/assert.deref.pass.cpp
new file mode 100644
index 0000000000000..061ea62d8ad4a
--- /dev/null
+++ b/libcxx/test/libcxx/memory/indirect/indirect.obs/assert.deref.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <memory>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  std::indirect<int> i;
+  auto(std::move(i));
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(*i, "operator* called on a valueless std::indirect object");
+  }
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(*std::move(i), "operator* called on a valueless std::indirect object");
+  }
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(*std::as_const(i), "operator* called on a valueless std::indirect object");
+  }
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(*std::move(std::as_const(i)), "operator* called on a valueless std::indirect object");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..4413237b7918e
--- /dev/null
+++ b/libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// Test the libc++ extension that std::indirect<T>::operator* is marked as [[nodiscard]].
+
+#include <memory>
+#include <utility>
+
+void test(std::indirect<int>& i) {
+  // clang-format off
+  *i; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  *std::move(i); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  *std::as_const(i); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  *std::move(std::as_const(i)); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  // clang-format on
+}
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..dbe0f30778593
--- /dev/null
+++ b/libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp
@@ -0,0 +1,19 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// Test the libc++ extension that std::indirect<T>::get_allocator is marked as [[nodiscard]].
+
+#include <memory>
+
+void test(std::indirect<int>& i) {
+  i.get_allocator(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp
new file mode 100644
index 0000000000000..6dded10bcde65
--- /dev/null
+++ b/libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp
@@ -0,0 +1,19 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// Test the libc++ extension that std::indirect<T>::valueless_after_move is marked as [[nodiscard]].
+
+#include <memory>
+
+void test(std::indirect<int>& i) {
+  i.valueless_after_move(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.swap/assert.swap.pass.cpp b/libcxx/test/libcxx/memory/indirect/indirect.swap/assert.swap.pass.cpp
new file mode 100644
index 0000000000000..d0c5a2fb6db62
--- /dev/null
+++ b/libcxx/test/libcxx/memory/indirect/indirect.swap/assert.swap.pass.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <memory>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  std::indirect<int, test_allocator<int>> i1(std::allocator_arg, test_allocator<int>(1));
+  std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(2));
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(swap(i1, i2), "swapping std::indirect objects with different allocators");
+  }
+  {
+    TEST_LIBCPP_ASSERT_FAILURE(i1.swap(i2), "swapping std::indirect objects with different allocators");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/memory/indirect/no_specializations.verify.cpp b/libcxx/test/libcxx/memory/indirect/no_specializations.verify.cpp
new file mode 100644
index 0000000000000..cb6e3dfdf2f53
--- /dev/null
+++ b/libcxx/test/libcxx/memory/indirect/no_specializations.verify.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// Check that user-specializations are diagnosed
+// See [indirect.general]/8
+
+#include <memory>
+
+#if !__has_warning("-Winvalid-specialization")
+// expected-no-diagnostics
+#else
+struct S {};
+
+template <>
+class std::indirect<S>; // expected-error {{cannot be specialized}}
+#endif
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.compile.pass.cpp
index f287e1ad9b3ad..518a1f37e1f51 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.compile.pass.cpp
@@ -52,6 +52,10 @@
 #    error "__cpp_lib_enable_shared_from_this should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_is_sufficiently_aligned
 #    error "__cpp_lib_is_sufficiently_aligned should not be defined before c++26"
 #  endif
@@ -130,6 +134,10 @@
 #    error "__cpp_lib_enable_shared_from_this should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_is_sufficiently_aligned
 #    error "__cpp_lib_is_sufficiently_aligned should not be defined before c++26"
 #  endif
@@ -223,6 +231,10 @@
 #    error "__cpp_lib_enable_shared_from_this should have the value 201603L in c++17"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_is_sufficiently_aligned
 #    error "__cpp_lib_is_sufficiently_aligned should not be defined before c++26"
 #  endif
@@ -337,6 +349,10 @@
 #    error "__cpp_lib_enable_shared_from_this should have the value 201603L in c++20"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_is_sufficiently_aligned
 #    error "__cpp_lib_is_sufficiently_aligned should not be defined before c++26"
 #  endif
@@ -463,6 +479,10 @@
 #    error "__cpp_lib_enable_shared_from_this should have the value 201603L in c++23"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_is_sufficiently_aligned
 #    error "__cpp_lib_is_sufficiently_aligned should not be defined before c++26"
 #  endif
@@ -592,6 +612,13 @@
 #    error "__cpp_lib_enable_shared_from_this should have the value 201603L in c++26"
 #  endif
 
+#  ifndef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should be defined in c++26"
+#  endif
+#  if __cpp_lib_indirect != 202502L
+#    error "__cpp_lib_indirect should have the value 202502L in c++26"
+#  endif
+
 #  ifndef __cpp_lib_is_sufficiently_aligned
 #    error "__cpp_lib_is_sufficiently_aligned should be defined in c++26"
 #  endif
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 8189c5c4e5985..4bb383a2df16b 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -432,6 +432,10 @@
 #    error "__cpp_lib_incomplete_container_elements should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_inplace_vector
 #    error "__cpp_lib_inplace_vector should not be defined before c++26"
 #  endif
@@ -1358,6 +1362,10 @@
 #    error "__cpp_lib_incomplete_container_elements should not be defined before c++17"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_inplace_vector
 #    error "__cpp_lib_inplace_vector should not be defined before c++26"
 #  endif
@@ -2422,6 +2430,10 @@
 #    error "__cpp_lib_incomplete_container_elements should have the value 201505L in c++17"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_inplace_vector
 #    error "__cpp_lib_inplace_vector should not be defined before c++26"
 #  endif
@@ -3744,6 +3756,10 @@
 #    error "__cpp_lib_incomplete_container_elements should have the value 201505L in c++20"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_inplace_vector
 #    error "__cpp_lib_inplace_vector should not be defined before c++26"
 #  endif
@@ -5258,6 +5274,10 @@
 #    error "__cpp_lib_incomplete_container_elements should have the value 201505L in c++23"
 #  endif
 
+#  ifdef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should not be defined before c++26"
+#  endif
+
 #  ifdef __cpp_lib_inplace_vector
 #    error "__cpp_lib_inplace_vector should not be defined before c++26"
 #  endif
@@ -7126,6 +7146,13 @@
 #    error "__cpp_lib_incomplete_container_elements should have the value 201505L in c++26"
 #  endif
 
+#  ifndef __cpp_lib_indirect
+#    error "__cpp_lib_indirect should be defined in c++26"
+#  endif
+#  if __cpp_lib_indirect != 202502L
+#    error "__cpp_lib_indirect should have the value 202502L in c++26"
+#  endif
+
 #  if !defined(_LIBCPP_VERSION)
 #    ifndef __cpp_lib_inplace_vector
 #      error "__cpp_lib_inplace_vector should be defined in c++26"
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.assign/copy.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.assign/copy.pass.cpp
new file mode 100644
index 0000000000000..2ac554fefbe42
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.assign/copy.pass.cpp
@@ -0,0 +1,125 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr indirect& operator=(const indirect& other);
+
+#include <cassert>
+#include <type_traits>
+#include <memory>
+
+#include "test_convertible.h"
+#include "test_allocator.h"
+#include "min_allocator.h"
+#include "archetypes.h"
+
+constexpr void test_assignment() {
+  { // Assigning from a valueless indirect destroys the owned object, if any.
+    std::indirect<int> i1;
+    std::indirect<int> i2;
+
+    auto(std::move(i2));
+    i1 = i2;
+    assert(i1.valueless_after_move());
+    assert(i2.valueless_after_move());
+    i1 = i2;
+    assert(i1.valueless_after_move());
+    assert(i2.valueless_after_move());
+  }
+  { // Assigning to an indirect that already owns an object doesn't allocate a new object.
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i1(std::allocator_arg, test_allocator<int>(&stats), 1);
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(&stats), 2);
+    assert(stats.construct_count == 2);
+    auto* addr_before = &*i1;
+    i1                = i2;
+    assert(addr_before == &*i1);
+    assert(stats.construct_count == 2);
+    assert(*i1 == 2);
+    assert(*i2 == 2);
+  }
+  { // Assigning to an indirect with a different allocator allocates a new owned object.
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i1(std::allocator_arg, test_allocator<int>(1, &stats), 1);
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(2, &stats), 2);
+    assert(stats.construct_count == 2);
+    auto* addr_before = &*i1;
+    i1                = i2;
+    assert(addr_before != &*i1);
+    assert(stats.construct_count == 3);
+    assert(*i1 == 2);
+    assert(*i2 == 2);
+  }
+  { // Assigning to a valueless indirect allocates a new owned object.
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i1(std::allocator_arg, test_allocator<int>(&stats), 1);
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(&stats), 2);
+    assert(stats.construct_count == 2);
+    auto(std::move(i1));
+    i1 = i2;
+    assert(*i1 == 2);
+    assert(*i2 == 2);
+    assert(stats.construct_count == 3);
+  }
+  { // Assignment returns *this.
+    std::indirect<int> i1;
+    const std::indirect<int> i2;
+    std::same_as<std::indirect<int>&> decltype(auto) addr = (i1 = i2);
+    assert(&addr == &i1);
+  }
+}
+
+void test_assignment_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct CopyingThrows {
+    int i = 0;
+    CopyingThrows(int n) : i(n) {}
+    CopyingThrows(const CopyingThrows&) { throw 42; }
+    CopyingThrows& operator=(const CopyingThrows&) { throw 42; }
+  };
+
+  std::indirect<CopyingThrows, test_allocator<CopyingThrows>> i1(
+      std::allocator_arg, test_allocator<CopyingThrows>(1), 1);
+  std::indirect<CopyingThrows, test_allocator<CopyingThrows>> i2(
+      std::allocator_arg, test_allocator<CopyingThrows>(2), 2);
+  auto* addr1 = &*i1;
+  auto* addr2 = &*i2;
+  try {
+    i1 = i2;
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+  assert(addr1 == &*i1);
+  assert(addr2 == &*i2);
+  assert(i1->i == 1);
+  assert(i2->i == 2);
+  assert(i1.get_allocator().get_data() == 1);
+  assert(i2.get_allocator().get_data() == 2);
+#endif
+}
+
+constexpr bool test() {
+  test_assignment();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_assignment_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp
new file mode 100644
index 0000000000000..5a9c4b2823431
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp
@@ -0,0 +1,167 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr indirect& operator=(indirect&& other)
+//   noexcept(allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||
+//            allocator_traits<Allocator>::is_always_equal::value);
+
+#include <cassert>
+#include <type_traits>
+#include <memory>
+
+#include "test_convertible.h"
+#include "test_allocator.h"
+#include "min_allocator.h"
+#include "archetypes.h"
+
+constexpr void test_assignment() {
+  { // Move-assigning from an indirect leaves it valueless.
+    std::indirect<int> i1(1);
+    std::indirect<int> i2(2);
+
+    i1 = std::move(i2); // Both RHS and LHS hold values.
+    assert(*i1 == 2);
+    assert(i2.valueless_after_move());
+    i1 = std::move(i2); // LHS holds value, RHS is valueless.
+    assert(i1.valueless_after_move());
+    assert(i2.valueless_after_move());
+    i1 = std::move(i2); // Both RHS and LHS are valueless.
+    assert(i1.valueless_after_move());
+    assert(i2.valueless_after_move());
+  }
+  { // Move assigning to an indirect simply transfers ownership of the held object.
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i1(std::allocator_arg, test_allocator<int>(&stats), 1);
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(&stats), 2);
+    assert(stats.construct_count == 2);
+    auto* addr2 = &*i2;
+    i1          = std::move(i2);
+    assert(i2.valueless_after_move());
+    assert(&*i1 == addr2);
+    assert(stats.construct_count == 2);
+    assert(*i1 == 2);
+  }
+  { // Assigning to an indirect with a different, non-POCMA allocator allocates a new owned object.
+    std::indirect<int, test_allocator<int>> i1(std::allocator_arg, test_allocator<int>(1), 1);
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(2), 2);
+    auto* addr1 = &*i1;
+    auto* addr2 = &*i2;
+    i1          = std::move(i2);
+    static_assert(!noexcept(i1 = std::move(i2)));
+    assert(i2.valueless_after_move());
+    assert(&*i1 != addr1);
+    assert(&*i1 != addr2);
+  }
+  { // Assignment returns *this.
+    std::indirect<int> i1;
+    std::indirect<int> i2;
+    std::same_as<std::indirect<int>&> decltype(auto) addr = (i1 = std::move(i2));
+    assert(&addr == &i1);
+  }
+}
+
+void test_assignment_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct MoveThrows {
+    int i = 0;
+    MoveThrows(int n) : i(n) {}
+    MoveThrows(MoveThrows&&) { throw 42; }
+    MoveThrows& operator=(MoveThrows&&) { throw 42; }
+  };
+
+  std::indirect<MoveThrows, test_allocator<MoveThrows>> i1(std::allocator_arg, test_allocator<MoveThrows>(1), 1);
+  std::indirect<MoveThrows, test_allocator<MoveThrows>> i2(std::allocator_arg, test_allocator<MoveThrows>(2), 2);
+  auto* addr1 = &*i1;
+  auto* addr2 = &*i2;
+  try {
+    i1 = std::move(i2);
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+  assert(addr1 == &*i1);
+  assert(addr2 == &*i2);
+  assert(i1->i == 1);
+  assert(i2->i == 2);
+  assert(i1.get_allocator().get_data() == 1);
+  assert(i2.get_allocator().get_data() == 2);
+#endif
+}
+
+template <class T>
+struct pocma_test_allocator : test_allocator<T> {
+  using test_allocator<T>::test_allocator;
+  using propagate_on_container_move_assignment = std::true_type;
+};
+
+struct Immovable {
+  Immovable()                            = default;
+  Immovable(const Immovable&)            = delete;
+  Immovable(Immovable&&)                 = delete;
+  Immovable& operator=(const Immovable&) = delete;
+  Immovable& operator=(Immovable&&)      = delete;
+};
+
+// https://cplusplus.github.io/LWG/issue4251
+constexpr void test_lwg4251() {
+  { // Move assigning indirect<T> doesn't require T to be copy constructible.
+    struct NotCopyConstructible {
+      constexpr NotCopyConstructible() = default;
+      constexpr NotCopyConstructible(NotCopyConstructible&&) {}
+    };
+    static_assert(!std::is_copy_constructible_v<NotCopyConstructible>);
+    std::indirect<NotCopyConstructible, test_allocator<NotCopyConstructible>> i1;
+    std::indirect<NotCopyConstructible, test_allocator<NotCopyConstructible>> i2;
+    i1 = std::move(i2);
+  }
+  { // T doesn't have to be move constructible, as long as the allocator propagates on move.
+    using A = other_allocator<Immovable>;
+    static_assert(std::allocator_traits<A>::propagate_on_container_move_assignment::value);
+    static_assert(!std::allocator_traits<A>::is_always_equal::value);
+    std::indirect<Immovable, A> i;
+    i = std::move(i);
+  }
+  { // T doesn't have to be move constructible, as long as the allocator is always equal.
+    using A = explicit_allocator<Immovable>;
+    static_assert(!std::allocator_traits<A>::propagate_on_container_move_assignment::value);
+    static_assert(std::allocator_traits<A>::is_always_equal::value);
+    std::indirect<Immovable, A> i;
+    i = std::move(i);
+  }
+  { // Move assignment with a POCMA allocator simply transfers ownership instead of allocating a new object.
+    std::indirect<int, other_allocator<int>> i1(1);
+    std::indirect<int, other_allocator<int>> i2(2);
+    auto* addr2 = &*i2;
+    i1          = std::move(i2);
+    assert(i2.valueless_after_move());
+    assert(*i1 == 2);
+    assert(&*i1 == addr2);
+  }
+}
+
+constexpr bool test() {
+  test_assignment();
+  test_lwg4251();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_assignment_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.assign/perfect_forwarding.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.assign/perfect_forwarding.pass.cpp
new file mode 100644
index 0000000000000..b5697952c2c4a
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.assign/perfect_forwarding.pass.cpp
@@ -0,0 +1,107 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class U = T>
+//   constexpr indirect& operator=(U&& u);
+
+#include <cassert>
+#include <type_traits>
+#include <memory>
+
+#include "test_convertible.h"
+#include "test_allocator.h"
+#include "min_allocator.h"
+#include "archetypes.h"
+
+struct MoveConstructibleOnly {
+  MoveConstructibleOnly(MoveConstructibleOnly&&) {}
+};
+
+constexpr void test_assignment_sfinae() {
+  { // Assignment isn't enabled if T is constructible but not assignable from the RHS type.
+    using I = std::indirect<MoveConstructibleOnly>;
+    static_assert(!std::is_assignable_v<I&, MoveConstructibleOnly&>);
+    static_assert(!std::is_assignable_v<I&, MoveConstructibleOnly&&>);
+  }
+  { // Assignment isn't enabled if T is assignable but not constructible from the RHS type.
+    using I = std::indirect<TestTypes::MoveAssignOnly>;
+    static_assert(!std::is_assignable_v<I&, TestTypes::MoveAssignOnly&>);
+    static_assert(!std::is_assignable_v<I&, TestTypes::MoveAssignOnly&&>);
+  }
+  {
+    using I = std::indirect<TestTypes::MoveOnly>;
+    static_assert(!std::is_assignable_v<I&, TestTypes::MoveOnly&>);
+    static_assert(std::is_assignable_v<I&, TestTypes::MoveOnly&&>);
+  }
+}
+
+constexpr void test_assignment() {
+  { // Assigning to an indirect that holds a value doesn't allocate a new object.
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i(std::allocator_arg, test_allocator<int>(&stats), 42);
+    assert(stats.construct_count == 1);
+    i = 10;
+    assert(stats.construct_count == 1);
+    assert(*i == 10);
+  }
+  { // Assigning to a valueless indirect allocates a new owned object.
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i(std::allocator_arg, test_allocator<int>(&stats), 42);
+    auto(std::move(i));
+    assert(stats.construct_count == 1);
+    i = 10;
+    assert(stats.construct_count == 2);
+    assert(*i == 10);
+  }
+  { // Assignment returns *this.
+    std::indirect<int> i;
+    std::same_as<std::indirect<int>&> decltype(auto) ret = (i = 10);
+    assert(&ret == &i);
+  }
+}
+
+void test_assignment_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct CopyingThrows {
+    CopyingThrows() = default;
+    CopyingThrows(const CopyingThrows&) { throw 42; }
+    CopyingThrows& operator=(const CopyingThrows&) { throw 42; }
+  };
+
+  std::indirect<CopyingThrows> i;
+  try {
+    i = CopyingThrows();
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+
+#endif
+}
+
+constexpr bool test() {
+  test_assignment_sfinae();
+  test_assignment();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_assignment_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/eq.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/eq.pass.cpp
new file mode 100644
index 0000000000000..31f1bfc4b1253
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/eq.pass.cpp
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class U>
+//   constexpr bool operator==(const indirect& lhs, const U& rhs) noexcept(noexcept(*lhs == rhs));
+
+#include <cassert>
+#include <memory>
+#include <utility>
+
+#include "test_comparisons.h"
+
+constexpr bool test() {
+  {
+    const std::indirect<int> i1(1);
+    const int i2 = 2;
+    assert(testEquality(i1, 2, false));
+    static_assert(noexcept(i1 == i2));
+    static_assert(noexcept(i2 == i1));
+    static_assert(noexcept(i1 != i2));
+    static_assert(noexcept(i2 != i1));
+  }
+  { // A valueless indirect always compares false.
+    std::indirect<int> i1;
+    const int i2 = 0;
+    assert(testEquality(i1, i2, true));
+    auto(std::move(i1));
+    assert(testEquality(i1, i2, false));
+  }
+
+  return true;
+}
+
+void test_comparison_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct ComparisonThrows {
+    int i = 0;
+    bool operator==(ComparisonThrows) const { throw 42; }
+  };
+
+  std::indirect<ComparisonThrows> i1(1);
+  ComparisonThrows i2(2);
+  static_assert(!noexcept(i1 == i2));
+  static_assert(!noexcept(i1 != i2));
+
+  try {
+    (void)(i1 == i2);
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+
+  auto(std::move(i1));
+  assert(testEquality(i1, i2, false));
+#endif
+}
+
+int main(int, char**) {
+  test_comparison_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/three_way.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/three_way.pass.cpp
new file mode 100644
index 0000000000000..68588c5850bc8
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.comp.with.t/three_way.pass.cpp
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class U>
+//   constexpr synth-three-way-result<T, U>
+//     operator<=>(const indirect& lhs, const U& rhs);
+
+#include <cassert>
+#include <compare>
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "test_comparisons.h"
+
+constexpr bool test() {
+  {
+    const std::indirect<int> i1;
+    assert(testOrder(i1, -1, std::strong_ordering::greater));
+    assert(testOrder(i1, 0, std::strong_ordering::equal));
+    assert(testOrder(i1, 1, std::strong_ordering::less));
+  }
+  { // A valueless indirect always compares less than the object it is compared with.
+    std::indirect<int> i1;
+    auto(std::move(i1));
+    assert(testOrder(i1, std::numeric_limits<int>::min(), std::strong_ordering::less));
+  }
+
+  return true;
+}
+
+void test_comparison_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct ComparisonThrows {
+    int i = 0;
+    bool operator==(ComparisonThrows) const { throw 42; }
+    std::strong_ordering operator<=>(ComparisonThrows) const { throw 42; }
+  };
+
+  std::indirect<ComparisonThrows> i1(1);
+  ComparisonThrows i2(2);
+  try {
+    (void)(i1 <=> i2);
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+
+  auto(std::move(i1));
+  assert(testOrder(i1, i2, std::strong_ordering::less));
+#endif
+}
+
+int main(int, char**) {
+  test_comparison_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/copy.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/copy.pass.cpp
new file mode 100644
index 0000000000000..0cbad0aca7b32
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/copy.pass.cpp
@@ -0,0 +1,117 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr indirect(const indirect& other)
+
+// constexpr indirect(allocator_arg_t, const Allocator& a, const indirect& other);
+
+#include <cassert>
+#include <memory>
+
+#include "test_allocator.h"
+#include "test_convertible.h"
+
+constexpr void test_copy_ctor_not_explicit() {
+  static_assert(test_convertible<std::indirect<int>, const std::indirect<int>&>());
+  static_assert(test_convertible<std::indirect<int>,
+                                 std::allocator_arg_t,
+                                 const std::allocator<int>&,
+                                 const std::indirect<int>&>());
+}
+
+constexpr void test_copy_ctor() {
+  {
+    const std::indirect<int> i1(42);
+    std::indirect<int> i2(i1);
+    assert(!i1.valueless_after_move());
+    assert(!i2.valueless_after_move());
+    assert(*i1 == 42);
+    assert(*i2 == 42);
+    assert(&*i1 != &*i2);
+  }
+  {
+    std::indirect<int> i1;
+    auto(std::move(i1));
+    assert(i1.valueless_after_move());
+    std::indirect<int> i2(i1);
+    assert(i2.valueless_after_move());
+  }
+  {
+    std::indirect<int, SocccAllocator<int>> i1;
+    assert(i1.get_allocator().count_ == 0);
+    std::indirect<int, SocccAllocator<int>> i2(i1);
+    assert(i2.get_allocator().count_ == 1);
+  }
+  {
+    const std::indirect<int> i1(42);
+    std::indirect<int> i2(std::allocator_arg, std::allocator<int>(), i1);
+    assert(!i1.valueless_after_move());
+    assert(!i2.valueless_after_move());
+    assert(*i1 == 42);
+    assert(*i2 == 42);
+    assert(&*i1 != &*i2);
+  }
+  {
+    const std::indirect<int, test_allocator<int>> i1;
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(42), i1);
+    assert(i1.get_allocator().get_data() == 0);
+    assert(i2.get_allocator().get_data() == 42);
+  }
+}
+
+void test_copy_ctor_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct CopyCtorThrows {
+    CopyCtorThrows() = default;
+    CopyCtorThrows(const CopyCtorThrows&) { throw 42; }
+  };
+
+  {
+    const std::indirect<CopyCtorThrows> i1;
+    try {
+      std::indirect<CopyCtorThrows> i2(i1);
+      assert(false);
+    } catch (const int& e) {
+      assert(e == 42);
+    } catch (...) {
+      assert(false);
+    }
+  }
+  {
+    const std::indirect<CopyCtorThrows> i1;
+    try {
+      std::indirect<CopyCtorThrows> i2(std::allocator_arg, std::allocator<int>(), i1);
+      assert(false);
+    } catch (const int& e) {
+      assert(e == 42);
+    } catch (...) {
+      assert(false);
+    }
+  }
+#endif
+}
+
+constexpr bool test() {
+  test_copy_ctor_not_explicit();
+  test_copy_ctor();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_copy_ctor_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/default.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/default.pass.cpp
new file mode 100644
index 0000000000000..02988da3ec369
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/default.pass.cpp
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr explicit indirect();
+
+// constexpr explicit indirect(allocator_arg_t, const Allocator& a);
+
+#include <cassert>
+#include <type_traits>
+#include <memory>
+
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_convertible.h"
+
+constexpr void test_default_ctor_sfinae() {
+  static_assert(std::is_default_constructible_v<std::indirect<int>>);
+  static_assert(!std::is_default_constructible_v<std::indirect<int, no_default_allocator<int>>>);
+}
+
+constexpr void test_default_ctor_explicit() {
+  static_assert(only_explicitly_constructible_from<std::indirect<int>>);
+  static_assert(
+      only_explicitly_constructible_from<std::indirect<int>, std::allocator_arg_t, const std::allocator<int>&>);
+}
+
+constexpr void test_default_ctor() {
+  {
+    std::indirect<int> i;
+    assert(!i.valueless_after_move());
+    assert(*i == 0);
+  }
+  {
+    std::indirect<int, test_allocator<int>> i(std::allocator_arg, test_allocator<int>(42));
+    assert(!i.valueless_after_move());
+    assert(*i == 0);
+    assert(i.get_allocator().get_data() == 42);
+  }
+}
+
+void test_default_ctor_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct DefaultCtorThrows {
+    DefaultCtorThrows() { throw 42; }
+  };
+
+  try {
+    std::indirect<DefaultCtorThrows> i;
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+
+  try {
+    std::indirect<DefaultCtorThrows> i(std::allocator_arg, std::allocator<int>());
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+#endif
+}
+
+constexpr bool test() {
+  test_default_ctor_sfinae();
+  test_default_ctor_explicit();
+  test_default_ctor();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_default_ctor_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/in_place_t.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/in_place_t.pass.cpp
new file mode 100644
index 0000000000000..1ce18dd25097c
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/in_place_t.pass.cpp
@@ -0,0 +1,125 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class... Us>
+//   constexpr explicit indirect(in_place_t, Us&&... us);
+
+// template<class... Us>
+//   constexpr explicit indirect(allocator_arg_t, const Allocator& a,
+//                               in_place_t, Us&& ...us);
+
+#include <cassert>
+#include <type_traits>
+#include <memory>
+
+#include "test_convertible.h"
+#include "test_allocator.h"
+#include "min_allocator.h"
+#include "archetypes.h"
+
+constexpr void test_in_place_t_ctor_sfinae() {
+  {
+    static_assert(
+        !std::is_constructible_v<std::indirect<TestTypes::MoveOnly>, std::in_place_t, const TestTypes::MoveOnly&>);
+    static_assert(std::is_constructible_v<std::indirect<TestTypes::MoveOnly>, std::in_place_t, TestTypes::MoveOnly&&>);
+  }
+  {
+    static_assert(std::is_constructible_v<std::indirect<int, no_default_allocator<int>>,
+                                          std::allocator_arg_t,
+                                          const no_default_allocator<int>&,
+                                          std::in_place_t,
+                                          int&>);
+  }
+  {
+    static_assert(!std::is_constructible_v<std::indirect<TestTypes::MoveOnly>,
+                                           std::allocator_arg_t,
+                                           const std::allocator<TestTypes::MoveOnly>&,
+                                           std::in_place_t,
+                                           const TestTypes::MoveOnly&>);
+    static_assert(std::is_constructible_v<std::indirect<TestTypes::MoveOnly>,
+                                          std::allocator_arg_t,
+                                          const std::allocator<TestTypes::MoveOnly>&,
+                                          std::in_place_t,
+                                          TestTypes::MoveOnly&&>);
+  }
+}
+
+constexpr void test_in_place_t_ctor_explicit() {
+  static_assert(only_explicitly_constructible_from<std::indirect<int>, std::in_place_t, int&>);
+  static_assert(only_explicitly_constructible_from<std::indirect<int>,
+                                                   std::allocator_arg_t,
+                                                   const std::allocator<TestTypes::MoveOnly>&,
+                                                   std::in_place_t,
+                                                   int&>);
+}
+
+constexpr void test_in_place_t_ctor() {
+  {
+    std::indirect<int> i(std::in_place, 42);
+    assert(!i.valueless_after_move());
+    assert(*i == 42);
+  }
+  {
+    std::indirect<std::pair<int, int>> i(std::in_place, 1, 2);
+    assert(!i.valueless_after_move());
+    assert((*i == std::pair{1, 2}));
+  }
+  {
+    std::indirect<int, test_allocator<int>> i(std::allocator_arg, test_allocator<int>(67), std::in_place, 42);
+    assert(!i.valueless_after_move());
+    assert(i.get_allocator().get_data() == 67);
+    assert(*i == 42);
+  }
+  {
+    std::indirect<std::pair<int, int>, test_allocator<std::pair<int, int>>> i(
+        std::allocator_arg, test_allocator<int>(67), std::in_place, 1, 2);
+    assert(!i.valueless_after_move());
+    assert(i.get_allocator().get_data() == 67);
+    assert((*i == std::pair{1, 2}));
+  }
+}
+
+void test_in_place_t_ctor_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct CopyCtorThrows {
+    CopyCtorThrows() = default;
+    CopyCtorThrows(const CopyCtorThrows&) { throw 42; }
+  };
+
+  CopyCtorThrows c;
+  try {
+    std::indirect<CopyCtorThrows> i(std::in_place, c);
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+#endif
+}
+
+constexpr bool test() {
+  test_in_place_t_ctor_sfinae();
+  test_in_place_t_ctor_explicit();
+  test_in_place_t_ctor();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_in_place_t_ctor_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/init_list.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/init_list.pass.cpp
new file mode 100644
index 0000000000000..55026f8b4303d
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/init_list.pass.cpp
@@ -0,0 +1,113 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class I, class... Us>
+//   constexpr explicit indirect(in_place_t, initializer_list<I> ilist, Us&&... us);
+
+// template<class I, class... Us>
+//   constexpr explicit indirect(allocator_arg_t, const Allocator& a,
+//                               in_place_t, initializer_list<I> ilist, Us&&... us);
+
+#include <cassert>
+#include <concepts>
+#include <initializer_list>
+#include <type_traits>
+#include <memory>
+
+#include "test_convertible.h"
+#include "test_allocator.h"
+#include "min_allocator.h"
+
+struct S {
+  constexpr S(std::same_as<std::initializer_list<int>&> auto&& ilist_arg, std::same_as<int> auto&& i)
+      : ilist(ilist_arg), int_addr(&i) {}
+
+  std::initializer_list<int> ilist;
+  int* int_addr;
+};
+
+constexpr void test_init_list_ctor_sfinae() {
+  {
+    using I = std::indirect<S>;
+    static_assert(std::is_constructible_v<I, std::in_place_t, std::initializer_list<int>, int&&>);
+    static_assert(std::is_constructible_v<I, std::in_place_t, const std::initializer_list<int>&, int&&>);
+    static_assert(!std::is_constructible_v<I, std::in_place_t, std::initializer_list<int>&, int&>);
+  }
+  {
+    using I = std::indirect<int, no_default_allocator<int>>;
+    static_assert(std::is_constructible_v<I, std::allocator_arg_t, const no_default_allocator<int>&, int&>);
+  }
+}
+
+constexpr void test_init_list_ctor_explicit() {
+  static_assert(
+      only_explicitly_constructible_from<std::indirect<int>, std::allocator_arg_t, const std::allocator<int>&, int&>);
+  static_assert(
+      only_explicitly_constructible_from<std::indirect<int>, std::allocator_arg_t, const std::allocator<int>&, int&&>);
+}
+
+constexpr void test_init_list_ctor() {
+  {
+    const std::initializer_list<int> ilist{1, 2};
+    int n = 0;
+    std::indirect<S> i(std::in_place, ilist, std::move(n));
+    assert(!i.valueless_after_move());
+    assert(i->ilist.begin() == ilist.begin());
+    assert(i->ilist.end() == ilist.end());
+    assert(i->int_addr == &n);
+  }
+  {
+    const std::initializer_list<int> ilist{1, 2};
+    int n = 0;
+    std::indirect<S, test_allocator<S>> i(
+        std::allocator_arg, test_allocator<S>(42), std::in_place, ilist, std::move(n));
+    assert(!i.valueless_after_move());
+    assert(i.get_allocator().get_data() == 42);
+    assert(i->ilist.begin() == ilist.begin());
+    assert(i->ilist.end() == ilist.end());
+    assert(i->int_addr == &n);
+  }
+}
+
+void test_init_list_ctor_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct CtorThrows {
+    CtorThrows(std::initializer_list<int>) { throw 42; }
+  };
+
+  try {
+    std::indirect<CtorThrows> i(std::in_place, std::initializer_list<int>{});
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+#endif
+}
+
+constexpr bool test() {
+  test_init_list_ctor_sfinae();
+  test_init_list_ctor_explicit();
+  test_init_list_ctor();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_init_list_ctor_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp
new file mode 100644
index 0000000000000..a5d6059bf049c
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp
@@ -0,0 +1,112 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr indirect(indirect&& other) noexcept;
+
+// constexpr indirect(allocator_arg_t, const Allocator& a, indirect&& other)
+//   noexcept(allocator_traits<Allocator>::is_always_equal::value);
+
+#include <cassert>
+#include <memory>
+#include <type_traits>
+
+#include "test_allocator.h"
+#include "test_convertible.h"
+
+constexpr void test_ctor_not_explicit() {
+  static_assert(test_convertible<std::indirect<int>, std::indirect<int>&&>());
+  static_assert(
+      test_convertible<std::indirect<int>, std::allocator_arg_t, const std::allocator<int>&, std::indirect<int>&&>());
+}
+
+constexpr void test_ctor_noexcept() {
+  static_assert(std::is_nothrow_constructible_v<std::indirect<int, test_allocator<int>>,
+                                                std::indirect<int, test_allocator<int>>&&>);
+  static_assert(std::is_nothrow_constructible_v<std::indirect<int>,
+                                                std::allocator_arg_t,
+                                                const std::allocator<int>&,
+                                                std::indirect<int>&&>);
+}
+
+constexpr void test_ctor() {
+  {
+    std::indirect<int> i1(42);
+
+    // Moving from an indirect leaves it valueless.
+    std::indirect<int> i2(std::move(i1));
+    assert(i1.valueless_after_move());
+    assert(!i2.valueless_after_move());
+    assert(*i2 == 42);
+
+    // Move constructing from a valueless indirect creates a new valueless indirect.
+    std::indirect<int> i3(std::move(i1));
+    assert(i1.valueless_after_move());
+    assert(i3.valueless_after_move());
+  }
+  {
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i1(10);
+
+    // If the allocators are equal, no memory is allocated.
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(&stats), std::move(i1));
+    assert(i1.valueless_after_move());
+    assert(!i2.valueless_after_move());
+    assert(*i2 == 10);
+    assert(stats.construct_count == 0);
+  }
+  {
+    test_allocator_statistics stats;
+    std::indirect<int, test_allocator<int>> i1(10);
+
+    // If the allocators aren't equal, a new owned object is constructed.
+    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(42, &stats), std::move(i1));
+    assert(i1.valueless_after_move());
+    assert(!i2.valueless_after_move());
+    assert(*i2 == 10);
+    assert(i1.get_allocator().get_data() == 0);
+    assert(i2.get_allocator().get_data() == 42);
+    assert(stats.construct_count == 1);
+
+    // If the source object is valueless, no memory is allocated, even if the allocators aren't equal.
+    std::indirect<int, test_allocator<int>> i3(std::allocator_arg, test_allocator<int>(67, &stats), std::move(i1));
+    assert(i1.valueless_after_move());
+    assert(i3.valueless_after_move());
+    assert(i1.get_allocator().get_data() == 0);
+    assert(i3.get_allocator().get_data() == 67);
+    assert(stats.construct_count == 1);
+  }
+  struct Incomplete;
+  { // Move construction doesn't require T to be complete.
+    (void)([](std::indirect<Incomplete>&& i) -> std::indirect<Incomplete> { return {std::move(i)}; });
+  }
+  { // Uses-allocator move construction doesn't require T to be complete as long as the allocator is always equal.
+    (void)([](std::indirect<Incomplete>&& i) -> std::indirect<Incomplete> {
+      return {std::allocator_arg, std::allocator<Incomplete>(), std::move(i)};
+    });
+  }
+}
+
+constexpr bool test() {
+  test_ctor_not_explicit();
+  test_ctor_noexcept();
+  test_ctor();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/perfect_forwarding.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/perfect_forwarding.pass.cpp
new file mode 100644
index 0000000000000..316180ae8a60b
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/perfect_forwarding.pass.cpp
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class U = T>
+//  constexpr explicit indirect(U&& u);
+
+// template<class U = T>
+//   constexpr explicit indirect(allocator_arg_t, const Allocator& a, U&& u);
+
+#include <cassert>
+#include <type_traits>
+#include <memory>
+
+#include "test_convertible.h"
+#include "min_allocator.h"
+#include "archetypes.h"
+
+constexpr void test_perfect_forwarding_ctor_sfinae() {
+  {
+    using I = std::indirect<TestTypes::MoveOnly>;
+    static_assert(!std::is_constructible_v<I, TestTypes::MoveOnly&>);
+    static_assert(!std::is_constructible_v<I, const TestTypes::MoveOnly&>);
+    static_assert(std::is_constructible_v<I, TestTypes::MoveOnly&&>);
+  }
+  { // If the allocator isn't default-constructible, only the uses-allocator constructor is enabled.
+    using I = std::indirect<int, no_default_allocator<int>>;
+    static_assert(!std::is_constructible_v<I, int&>);
+    static_assert(std::is_constructible_v<I, std::allocator_arg_t, const no_default_allocator<int>&, int&>);
+  }
+}
+
+constexpr void test_perfect_forwarding_ctor_explicit() {
+  static_assert(only_explicitly_constructible_from<std::indirect<int>, int&>);
+  static_assert(only_explicitly_constructible_from<std::indirect<int, no_default_allocator<int>>,
+                                                   std::allocator_arg_t,
+                                                   const no_default_allocator<int>&,
+                                                   int&>);
+}
+
+constexpr void test_perfect_forwarding_ctor() {
+  {
+    std::indirect<int> i(42);
+    assert(!i.valueless_after_move());
+    assert(*i == 42);
+  }
+  {
+    std::indirect<int> i(std::allocator_arg, std::allocator<int>(), 42);
+    assert(!i.valueless_after_move());
+    assert(*i == 42);
+  }
+}
+
+void test_perfect_forwarding_ctor_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct CopyCtorThrows {
+    CopyCtorThrows() = default;
+    CopyCtorThrows(const CopyCtorThrows&) { throw 42; }
+  };
+
+  try {
+    std::indirect<CopyCtorThrows> i(CopyCtorThrows{});
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+
+  try {
+    std::indirect<CopyCtorThrows> i(std::allocator_arg, std::allocator<CopyCtorThrows>(), CopyCtorThrows{});
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+#endif
+}
+
+constexpr bool test() {
+  test_perfect_forwarding_ctor_sfinae();
+  test_perfect_forwarding_ctor_explicit();
+  test_perfect_forwarding_ctor();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_perfect_forwarding_ctor_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_array.verify.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_array.verify.cpp
new file mode 100644
index 0000000000000..2e1a08a3b90bb
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_array.verify.cpp
@@ -0,0 +1,24 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+#include <memory>
+
+#include "min_allocator.h"
+
+// Verify that std::indirect rejects array types.
+void test() {
+  // expected-error@*:* 2 {{static assertion failed}}
+  std::indirect<int[]> i1;   // expected-note {{requested here}}
+  std::indirect<int[10]> i2; // expected-note {{requested here}}
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_cv_qualified.verify.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_cv_qualified.verify.cpp
new file mode 100644
index 0000000000000..a4138776a5754
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_cv_qualified.verify.cpp
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+#include <memory>
+
+#include "min_allocator.h"
+
+// Verify that std::indirect rejects cv-qualified types.
+void test() {
+  // Use bare_allocator, because std::allocator doesn't support cv-qualified types.
+  // expected-error@*:* 3 {{static assertion failed}}
+  std::indirect<const int, bare_allocator<const int>> i1;                   // expected-note {{requested here}}
+  std::indirect<volatile int, bare_allocator<volatile int>> i2;             // expected-note {{requested here}}
+  std::indirect<const volatile int, bare_allocator<const volatile int>> i3; // expected-note {{requested here}}
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_t.verify.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_t.verify.cpp
new file mode 100644
index 0000000000000..5fa9e90540844
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_t.verify.cpp
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+#include <memory>
+
+// Verify that std::indirect rejects in_place_t.
+void test() {
+  // expected-error@*:* 1 {{static assertion failed}}
+  std::indirect<std::in_place_t> i1; // expected-note {{requested here}}
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_type_t.verify.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_type_t.verify.cpp
new file mode 100644
index 0000000000000..348504c71abac
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_in_place_type_t.verify.cpp
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+#include <memory>
+
+// Verify that std::indirect rejects specializations of in_place_type_t.
+void test() {
+  // expected-error@*:* 1 {{static assertion failed}}
+  std::indirect<std::in_place_type_t<int>> i1; // expected-note {{requested here}}
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_nonobject.verify.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_nonobject.verify.cpp
new file mode 100644
index 0000000000000..e0a0719ff7da1
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.general/indirect_nonobject.verify.cpp
@@ -0,0 +1,24 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+#include <memory>
+
+#include "min_allocator.h"
+
+// Verify that std::indirect rejects nonobject types.
+void test() {
+  // expected-error@*:* 2 {{static assertion failed}}
+  std::indirect<void, bare_allocator<void>> i1;   // expected-note {{requested here}}
+  std::indirect<int(), bare_allocator<int()>> i2; // expected-note {{requested here}}
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.hash/hash.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.hash/hash.pass.cpp
new file mode 100644
index 0000000000000..47bc9ad8fed2d
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.hash/hash.pass.cpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class T, class Allocator>
+// struct hash<indirect<T, Allocator>>;
+
+#include <cassert>
+#include <memory>
+#include <type_traits>
+
+#include "test_convertible.h"
+
+constexpr bool test() {
+  { // Hashing an indirect hashes its owned object.
+    std::indirect<int> i1(1);
+    assert(std::hash<std::indirect<int>>()(i1) == std::hash<int>()(1));
+  }
+  { // Hashing a valueless indirect is valid and returns an implementation-defined value.
+    std::indirect<int> i1(1);
+    std::indirect<int> i2(2);
+    auto(std::move(i1));
+    auto(std::move(i2));
+    assert(std::hash<std::indirect<int>>()(i1) == std::hash<std::indirect<int>>()(i2));
+  }
+  { // hash<indirect<T>> is only enabled if hash<T> is.
+    static_assert(std::is_default_constructible_v<std::hash<std::indirect<int>>>);
+    struct S {};
+    static_assert(!std::is_default_constructible_v<std::hash<std::indirect<S>>>);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.obs/arrow.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.obs/arrow.pass.cpp
new file mode 100644
index 0000000000000..dfef1c629f7a3
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.obs/arrow.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr const_pointer operator->() const noexcept;
+// constexpr pointer operator->() noexcept;
+
+#include <cassert>
+#include <memory>
+#include <utility>
+
+constexpr bool test() {
+  struct S {
+    constexpr bool is_const() & noexcept { return false; }
+    constexpr bool is_const() const& noexcept { return true; }
+  };
+
+  std::indirect<S> i;
+
+  assert(!i->is_const());
+  assert(std::as_const(i)->is_const());
+
+  static_assert(noexcept(i->is_const()));
+  static_assert(noexcept(std::as_const(i)->is_const()));
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp
new file mode 100644
index 0000000000000..b204be8845aee
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr const T& operator*() const & noexcept;
+// constexpr T& operator*() & noexcept;
+
+// constexpr const T&& operator*() const && noexcept;
+// constexpr T&& operator*() && noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <memory>
+#include <utility>
+
+constexpr bool test() {
+  std::indirect<int> i;
+
+  std::same_as<int&> decltype(auto) _        = *i;
+  std::same_as<int&&> decltype(auto) _       = *std::move(i);
+  std::same_as<const int&> decltype(auto) _  = *std::as_const(i);
+  std::same_as<const int&&> decltype(auto) _ = *std::move(std::as_const(i));
+
+  static_assert(noexcept(*i));
+  static_assert(noexcept(*std::move(i)));
+  static_assert(noexcept(*std::as_const(i)));
+  static_assert(noexcept(*std::move(std::as_const(i))));
+
+  struct Incomplete;
+  (void)([](std::indirect<Incomplete>& i) {
+    (void)(*i);
+    (void)(*std::move(i));
+    (void)(*std::as_const(i));
+    (void)(*std::move(std::as_const(i)));
+  });
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp
new file mode 100644
index 0000000000000..d0a6b9a87862b
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr allocator_type get_allocator() const noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <memory>
+
+constexpr bool test() {
+  const std::indirect<int> i;
+
+  std::same_as<std::allocator<int>> decltype(auto) _ = i.get_allocator();
+
+  static_assert(noexcept(i.get_allocator()));
+
+  struct Incomplete;
+  (void)([](std::indirect<Incomplete>& i) { return i.get_allocator(); });
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp
new file mode 100644
index 0000000000000..81943181681d5
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// constexpr bool valueless_after_move() const noexcept;
+
+#include <cassert>
+#include <concepts>
+#include <memory>
+
+constexpr bool test() {
+  const std::indirect<int> i;
+
+  std::same_as<bool> decltype(auto) _ = i.valueless_after_move();
+
+  static_assert(noexcept(i.valueless_after_move()));
+
+  struct Incomplete;
+  (void)([](std::indirect<Incomplete>& i) { return i.valueless_after_move(); });
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.relops/eq.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.relops/eq.pass.cpp
new file mode 100644
index 0000000000000..450b9a1dae5c3
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.relops/eq.pass.cpp
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class U>
+//   constexpr bool operator==(const indirect& lhs, const U& rhs) noexcept(noexcept(*lhs == rhs));
+
+#include <cassert>
+#include <memory>
+#include <utility>
+
+#include "test_comparisons.h"
+#include "test_allocator.h"
+
+constexpr bool test() {
+  { // Comparing indirects compares their held objects.
+    const std::indirect<int> i1;
+    const std::indirect<int> i2;
+    assert(testEquality(i1, i2, true));
+    static_assert(noexcept(i1 == i2));
+    static_assert(noexcept(i1 != i2));
+  }
+  { // Indirects can be compared even if they have different allocator types.
+    const std::indirect<int> i1;
+    const std::indirect<int, other_allocator<int>> i2;
+    assert(testEquality(i1, i2, true));
+    static_assert(noexcept(i1 == i2));
+    static_assert(noexcept(i1 != i2));
+  }
+  { // Valueless indirects always compare equal to each other and not equal to ones that hold values.
+    std::indirect<int> i1;
+    std::indirect<int> i2;
+    assert(testEquality(i1, i2, true));
+    auto(std::move(i1));
+    assert(testEquality(i1, i2, false));
+    auto(std::move(i2));
+    assert(testEquality(i1, i2, true));
+  }
+
+  return true;
+}
+
+void test_comparison_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct ComparisonThrows {
+    int i = 0;
+    bool operator==(ComparisonThrows) const { throw 42; }
+  };
+
+  std::indirect<ComparisonThrows> i1(1);
+  std::indirect<ComparisonThrows> i2(2);
+  static_assert(!noexcept(i1 == i2));
+  static_assert(!noexcept(i1 != i2));
+
+  try {
+    (void)(i1 == i2);
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+
+  auto(std::move(i1));
+  assert(testEquality(i1, i2, false));
+  auto(std::move(i2));
+  assert(testEquality(i1, i2, true));
+#endif
+}
+
+int main(int, char**) {
+  test_comparison_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.relops/three_way.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.relops/three_way.pass.cpp
new file mode 100644
index 0000000000000..b5748d27bdfca
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.relops/three_way.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// template <class T, class Allocator = std::allocator<T>> class indirect;
+
+// template<class U, class AA>
+//   constexpr synth-three-way-result<T, U>
+//     operator<=>(const indirect& lhs, const indirect<U, AA>& rhs);
+
+#include <cassert>
+#include <compare>
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "test_comparisons.h"
+#include "test_allocator.h"
+
+constexpr bool test() {
+  { // Comparing indirects compares their held objects.
+    const std::indirect<int> i1;
+    const std::indirect<int> i2;
+    assert(testOrder(i1, i2, std::strong_ordering::equal));
+  }
+  { // Indirects can be compared even if they have different allocator types.
+    const std::indirect<int> i1(1);
+    const std::indirect<int, other_allocator<int>> i2(2);
+    assert(testOrder(i1, i2, std::strong_ordering::less));
+  }
+  { // Valueless indirects always compare equal to each other and less than ones that hold values.
+    std::indirect<int> i1;
+    std::indirect<int> i2(std::numeric_limits<int>::min());
+    auto(std::move(i1));
+    assert(testOrder(i1, i2, std::strong_ordering::less));
+    auto(std::move(i2));
+    assert(testOrder(i1, i2, std::strong_ordering::equal));
+  }
+
+  return true;
+}
+
+void test_comparison_throws() {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  struct ComparisonThrows {
+    int i = 0;
+    bool operator==(ComparisonThrows) const { throw 42; }
+    std::strong_ordering operator<=>(ComparisonThrows) const { throw 42; }
+  };
+
+  std::indirect<ComparisonThrows> i1(1);
+  std::indirect<ComparisonThrows> i2(2);
+  try {
+    (void)(i1 <=> i2);
+    assert(false);
+  } catch (const int& e) {
+    assert(e == 42);
+  } catch (...) {
+    assert(false);
+  }
+
+  auto(std::move(i1));
+  assert(testOrder(i1, i2, std::strong_ordering::less));
+  auto(std::move(i2));
+  assert(testOrder(i1, i2, std::strong_ordering::equal));
+#endif
+}
+
+int main(int, char**) {
+  test_comparison_throws();
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.swap/swap.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.swap/swap.pass.cpp
new file mode 100644
index 0000000000000..0cd5b2fb6a726
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.swap/swap.pass.cpp
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <memory>
+
+// constexpr void swap(indirect& other)
+//   noexcept(allocator_traits<Allocator>::propagate_on_container_swap::value ||
+//            allocator_traits<Allocator>::is_always_equal::value);
+
+// constexpr void swap(indirect& lhs, indirect& rhs) noexcept(noexcept(lhs.swap(rhs)));
+
+#include <cassert>
+#include <memory>
+#include <type_traits>
+
+#include "test_allocator.h"
+#include "test_convertible.h"
+
+template <class T, bool POCS>
+struct pocs_allocator {
+  using value_type                  = T;
+  using propagate_on_container_swap = std::bool_constant<POCS>;
+
+  template <typename U>
+  struct rebind {
+    using other = pocs_allocator<U, POCS>;
+  };
+
+  int data = 0;
+
+  constexpr pocs_allocator(int i) : data(i) {}
+  constexpr T* allocate(size_t n) { return std::allocator<T>().allocate(n); }
+  constexpr void deallocate(T* ptr, size_t n) { return std::allocator<T>().deallocate(ptr, n); }
+
+  friend constexpr bool operator==(pocs_allocator, pocs_allocator) { return true; };
+
+  friend constexpr void swap(pocs_allocator& lhs, pocs_allocator& rhs) { std::swap(lhs.data, rhs.data); }
+};
+
+constexpr void test_swap_noexcept() {
+  std::indirect<int> i;
+  static_assert(noexcept(swap(i, i)));
+  static_assert(noexcept(i.swap(i)));
+}
+
+constexpr void test_swap() {
+  {
+    std::indirect<int> i1(1);
+    std::indirect<int> i2(2);
+    swap(i1, i2);
+    assert(*i1 == 2);
+    assert(*i2 == 1);
+  }
+  {
+    using A = pocs_allocator<int, true>;
+    std::indirect<int, A> i1(std::allocator_arg, A(1), 1);
+    std::indirect<int, A> i2(std::allocator_arg, A(2), 2);
+    swap(i1, i2);
+    assert(*i1 == 2);
+    assert(*i2 == 1);
+    assert(i1.get_allocator().data == 2);
+    assert(i2.get_allocator().data == 1);
+    static_assert(noexcept(swap(i1, i2)));
+  }
+  {
+    using A = pocs_allocator<int, false>;
+    std::indirect<int, A> i1(std::allocator_arg, A(1), 1);
+    std::indirect<int, A> i2(std::allocator_arg, A(2), 2);
+    swap(i1, i2);
+    assert(*i1 == 2);
+    assert(*i2 == 1);
+    assert(i1.get_allocator().data == 1);
+    assert(i2.get_allocator().data == 2);
+    static_assert(!noexcept(swap(i1, i2)));
+  }
+  struct Incomplete;
+  { // Swapping incomplete types is valid.
+    (void)([](std::indirect<Incomplete>& i) {
+      swap(i, i);
+      i.swap(i);
+    });
+  }
+}
+
+constexpr bool test() {
+  test_swap_noexcept();
+  test_swap();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h
index f8b622d7f9520..32c29969e20d2 100644
--- a/libcxx/test/support/test_allocator.h
+++ b/libcxx/test/support/test_allocator.h
@@ -498,17 +498,19 @@ struct SocccAllocator {
   using value_type = T;
 
   int count_ = 0;
-  explicit SocccAllocator(int i) : count_(i) {}
+  TEST_CONSTEXPR_CXX20 explicit SocccAllocator(int i = 0) : count_(i) {}
 
   template <class U>
-  SocccAllocator(const SocccAllocator<U>& a) : count_(a.count_) {}
+  TEST_CONSTEXPR_CXX20 SocccAllocator(const SocccAllocator<U>& a) : count_(a.count_) {}
 
-  T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
-  void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
+  TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
+  TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
 
-  SocccAllocator select_on_container_copy_construction() const { return SocccAllocator(count_ + 1); }
+  TEST_CONSTEXPR_CXX20 SocccAllocator select_on_container_copy_construction() const {
+    return SocccAllocator(count_ + 1);
+  }
 
-  bool operator==(const SocccAllocator&) const { return true; }
+  TEST_CONSTEXPR_CXX20 bool operator==(const SocccAllocator&) const { return true; }
 
   using propagate_on_container_copy_assignment = std::false_type;
   using propagate_on_container_move_assignment = std::false_type;
diff --git a/libcxx/test/support/test_convertible.h b/libcxx/test/support/test_convertible.h
index 805639716d91d..9eac0453cef06 100644
--- a/libcxx/test/support/test_convertible.h
+++ b/libcxx/test/support/test_convertible.h
@@ -14,6 +14,7 @@
 // Unlike 'std::is_convertible' which only allows checking for single argument
 // conversions.
 
+#include <type_traits>
 #include <utility>
 
 #include "test_macros.h"
@@ -38,4 +39,11 @@ template <class Tp, class ...Args>
 constexpr bool test_convertible()
 { return detail::test_convertible_imp<Tp, Args...>(0); }
 
+#if TEST_STD_VER >= 20
+
+template <class Tp, class... Args>
+concept only_explicitly_constructible_from = std::is_constructible_v<Tp, Args...> && !test_convertible<Tp, Args...>();
+
+#endif // TEST_STD_VER >= 20
+
 #endif // SUPPORT_TEST_CONVERTIBLE_H
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 22209f53d50d7..d0d7616a5825f 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -746,6 +746,11 @@ def add_version_header(tc):
             "values": {"c++17": 201505},
             "headers": ["forward_list", "list", "vector"],
         },
+        {
+            "name": "__cpp_lib_indirect",
+            "values": {"c++26": 202502},  # P3019R14 indirect and polymorphic: Vocabulary Types for Composite Class Design
+            "headers": ["memory"],
+        },
         {
             "name": "__cpp_lib_inplace_vector",
             "values": {"c++26": 202406},  # P0843R14 inplace_vector

>From ce7ef100430aea35df1158f49f34c807141da367 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 06:49:21 +0000
Subject: [PATCH 02/11] python formatting

---
 libcxx/utils/generate_feature_test_macro_components.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index d0d7616a5825f..a9eadbdee22fc 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -748,7 +748,9 @@ def add_version_header(tc):
         },
         {
             "name": "__cpp_lib_indirect",
-            "values": {"c++26": 202502},  # P3019R14 indirect and polymorphic: Vocabulary Types for Composite Class Design
+            "values": {
+                "c++26": 202502
+            },  # P3019R14 indirect and polymorphic: Vocabulary Types for Composite Class Design
             "headers": ["memory"],
         },
         {

>From b9d562638b4886c028b3a6d2fb3fd12efb2c93d1 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 07:23:05 +0000
Subject: [PATCH 03/11] Fix modules build

---
 libcxx/include/module.modulemap.in | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 50a0cbde37a1e..2c0723a7b195c 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1654,7 +1654,12 @@ module std [system] {
     module destroy                            { header "__memory/destroy.h" }
     module destruct_n                         { header "__memory/destruct_n.h" }
     module fwd                                { header "__fwd/memory.h" }
-    module indirect                           { header "__memory/indirect.h" }
+    module indirect {
+      header "__memory/indirect.h"
+      export std.memory.allocator
+      export std.compare.ordering
+      export std.utility.in_place
+    }
     module inout_ptr                          { header "__memory/inout_ptr.h" }
     module is_sufficiently_aligned            { header "__memory/is_sufficiently_aligned.h" }
     module noexcept_move_assign_container     { header "__memory/noexcept_move_assign_container.h" }

>From e6b5699b10704e830e162a4b9e3b0839d8267aa6 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 08:08:04 +0000
Subject: [PATCH 04/11] Avoid reserved names, transitive include, and shadowing

---
 libcxx/include/__memory/indirect.h            | 81 ++++++++++---------
 .../indirect/indirect.obs/deref.pass.cpp      | 41 +++++-----
 .../indirect.obs/get_allocator.pass.cpp       | 15 ++--
 .../valueless_after_move.pass.cpp             | 15 ++--
 4 files changed, 84 insertions(+), 68 deletions(-)

diff --git a/libcxx/include/__memory/indirect.h b/libcxx/include/__memory/indirect.h
index be55d2d0f18ef..7c3bbbe86fc77 100644
--- a/libcxx/include/__memory/indirect.h
+++ b/libcxx/include/__memory/indirect.h
@@ -22,21 +22,26 @@
 #include <__memory/allocator_traits.h>
 #include <__memory/swap_allocator.h>
 #include <__type_traits/is_array.h>
+#include <__type_traits/is_assignable.h>
+#include <__type_traits/is_constructible.h>
 #include <__type_traits/is_object.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/remove_cv.h>
+#include <__type_traits/remove_cvref.h>
 #include <__utility/exchange.h>
 #include <__utility/forward.h>
 #include <__utility/in_place.h>
 #include <__utility/move.h>
 #include <__utility/swap.h>
 #include <initializer_list>
-#include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
 #endif
 
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
 #if _LIBCPP_STD_VER >= 26
 
 _LIBCPP_BEGIN_NAMESPACE_STD
@@ -92,17 +97,17 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
     }
   }
 
-  template <class _U = _Tp>
-    requires(!is_same_v<remove_cvref_t<_U>, indirect> && !is_same_v<remove_cvref_t<_U>, in_place_t> &&
-             is_constructible_v<_Tp, _U> && is_default_constructible_v<_Allocator>)
-  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(_U&& __u)
-      : __p_(__allocate_owned_object(__alloc_, std::forward<_U>(__u))) {}
+  template <class _Up = _Tp>
+    requires(!is_same_v<remove_cvref_t<_Up>, indirect> && !is_same_v<remove_cvref_t<_Up>, in_place_t> &&
+             is_constructible_v<_Tp, _Up> && is_default_constructible_v<_Allocator>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(_Up&& __u)
+      : __p_(__allocate_owned_object(__alloc_, std::forward<_Up>(__u))) {}
 
-  template <class _U = _Tp>
-    requires(!is_same_v<remove_cvref_t<_U>, indirect> && !is_same_v<remove_cvref_t<_U>, in_place_t> &&
-             is_constructible_v<_Tp, _U>)
-  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a, _U&& __u)
-      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, std::forward<_U>(__u))) {}
+  template <class _Up = _Tp>
+    requires(!is_same_v<remove_cvref_t<_Up>, indirect> && !is_same_v<remove_cvref_t<_Up>, in_place_t> &&
+             is_constructible_v<_Tp, _Up>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a, _Up&& __u)
+      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, std::forward<_Up>(__u))) {}
 
   template <class... _Us>
     requires(is_constructible_v<_Tp, _Us...> && is_default_constructible_v<_Allocator>)
@@ -114,15 +119,15 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a, in_place_t, _Us&&... __us)
       : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, std::forward<_Us>(__us)...)) {}
 
-  template <class _I, class... _Us>
-    requires(is_constructible_v<_Tp, initializer_list<_I>&, _Us...> && is_default_constructible_v<_Allocator>)
-  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(in_place_t, initializer_list<_I> __ilist, _Us&&... __us)
+  template <class _In, class... _Us>
+    requires(is_constructible_v<_Tp, initializer_list<_In>&, _Us...> && is_default_constructible_v<_Allocator>)
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(in_place_t, initializer_list<_In> __ilist, _Us&&... __us)
       : __p_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
 
-  template <class _I, class... _Us>
-    requires is_constructible_v<_Tp, initializer_list<_I>&, _Us...>
+  template <class _In, class... _Us>
+    requires is_constructible_v<_Tp, initializer_list<_In>&, _Us...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(
-      allocator_arg_t, const _Allocator& __a, in_place_t, initializer_list<_I> __ilist, _Us&&... __us)
+      allocator_arg_t, const _Allocator& __a, in_place_t, initializer_list<_In> __ilist, _Us&&... __us)
       : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
 
   // [indirect.dtor], destructor
@@ -189,13 +194,13 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
     return *this;
   }
 
-  template <class _U = _Tp>
-    requires(!is_same_v<remove_cvref_t<_U>, indirect> && is_constructible_v<_Tp, _U> && is_assignable_v<_Tp&, _U>)
-  _LIBCPP_HIDE_FROM_ABI constexpr indirect& operator=(_U&& __u) {
+  template <class _Up = _Tp>
+    requires(!is_same_v<remove_cvref_t<_Up>, indirect> && is_constructible_v<_Tp, _Up> && is_assignable_v<_Tp&, _Up>)
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect& operator=(_Up&& __u) {
     if (valueless_after_move())
-      __p_ = __allocate_owned_object(__alloc_, std::forward<_U>(__u));
+      __p_ = __allocate_owned_object(__alloc_, std::forward<_Up>(__u));
     else
-      *__p_ = std::forward<_U>(__u);
+      *__p_ = std::forward<_Up>(__u);
     return *this;
   }
 
@@ -257,31 +262,31 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
   }
 
   // [indirect.relops], relational operators
-  template <class _U, class _AA>
+  template <class _Up, class _AA>
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool
-  operator==(const indirect& __lhs, const indirect<_U, _AA>& __rhs) noexcept(noexcept(*__lhs == *__rhs)) {
+  operator==(const indirect& __lhs, const indirect<_Up, _AA>& __rhs) noexcept(noexcept(*__lhs == *__rhs)) {
     return (__lhs.valueless_after_move() == __rhs.valueless_after_move()) &&
            (__lhs.valueless_after_move() || *__lhs == *__rhs);
   }
 
-  template <class _U, class _AA>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __synth_three_way_result<_Tp, _U>
-  operator<=>(const indirect& __lhs, const indirect<_U, _AA>& __rhs) {
+  template <class _Up, class _AA>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __synth_three_way_result<_Tp, _Up>
+  operator<=>(const indirect& __lhs, const indirect<_Up, _AA>& __rhs) {
     if (__lhs.valueless_after_move() || __rhs.valueless_after_move())
       return !__lhs.valueless_after_move() <=> !__rhs.valueless_after_move();
     return std::__synth_three_way(*__lhs, *__rhs);
   }
 
   // [indirect.comp.with.t], comparison with T
-  template <class _U>
+  template <class _Up>
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr bool
-  operator==(const indirect& __lhs, const _U& __rhs) noexcept(noexcept(*__lhs == __rhs)) {
+  operator==(const indirect& __lhs, const _Up& __rhs) noexcept(noexcept(*__lhs == __rhs)) {
     return !__lhs.valueless_after_move() && *__lhs == __rhs;
   }
 
-  template <class _U>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __synth_three_way_result<_Tp, _U>
-  operator<=>(const indirect& __lhs, const _U& __rhs) {
+  template <class _Up>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __synth_three_way_result<_Tp, _Up>
+  operator<=>(const indirect& __lhs, const _Up& __rhs) {
     return __lhs.valueless_after_move() ? strong_ordering::less : std::__synth_three_way(*__lhs, __rhs);
   }
 
@@ -310,11 +315,11 @@ indirect(_Value) -> indirect<_Value>;
 template <class _Allocator, class _Value>
 indirect(allocator_arg_t, _Allocator, _Value) -> indirect<_Value, __rebind_alloc<allocator_traits<_Allocator>, _Value>>;
 
-template <class _T, class _Allocator>
-  requires is_default_constructible_v<hash<_T>>
-struct hash<indirect<_T, _Allocator>> {
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t operator()(const indirect<_T, _Allocator>& __i) const {
-    return __i.valueless_after_move() ? 0 : hash<_T>()(*__i);
+template <class _Tp, class _Allocator>
+  requires is_default_constructible_v<hash<_Tp>>
+struct hash<indirect<_Tp, _Allocator>> {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t operator()(const indirect<_Tp, _Allocator>& __i) const {
+    return __i.valueless_after_move() ? 0 : hash<_Tp>()(*__i);
   }
 };
 
@@ -329,4 +334,6 @@ _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP_STD_VER >= 26
 
+_LIBCPP_POP_MACROS
+
 #endif // _LIBCPP___MEMORY_INDIRECT_H
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp
index b204be8845aee..1e690f46fac40 100644
--- a/libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.obs/deref.pass.cpp
@@ -24,25 +24,28 @@
 #include <utility>
 
 constexpr bool test() {
-  std::indirect<int> i;
-
-  std::same_as<int&> decltype(auto) _        = *i;
-  std::same_as<int&&> decltype(auto) _       = *std::move(i);
-  std::same_as<const int&> decltype(auto) _  = *std::as_const(i);
-  std::same_as<const int&&> decltype(auto) _ = *std::move(std::as_const(i));
-
-  static_assert(noexcept(*i));
-  static_assert(noexcept(*std::move(i)));
-  static_assert(noexcept(*std::as_const(i)));
-  static_assert(noexcept(*std::move(std::as_const(i))));
-
-  struct Incomplete;
-  (void)([](std::indirect<Incomplete>& i) {
-    (void)(*i);
-    (void)(*std::move(i));
-    (void)(*std::as_const(i));
-    (void)(*std::move(std::as_const(i)));
-  });
+  {
+    std::indirect<int> i;
+
+    std::same_as<int&> decltype(auto) _        = *i;
+    std::same_as<int&&> decltype(auto) _       = *std::move(i);
+    std::same_as<const int&> decltype(auto) _  = *std::as_const(i);
+    std::same_as<const int&&> decltype(auto) _ = *std::move(std::as_const(i));
+
+    static_assert(noexcept(*i));
+    static_assert(noexcept(*std::move(i)));
+    static_assert(noexcept(*std::as_const(i)));
+    static_assert(noexcept(*std::move(std::as_const(i))));
+  }
+  {
+    struct Incomplete;
+    (void)([](std::indirect<Incomplete>& i) {
+      (void)(*i);
+      (void)(*std::move(i));
+      (void)(*std::as_const(i));
+      (void)(*std::move(std::as_const(i)));
+    });
+  }
 
   return true;
 }
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp
index d0a6b9a87862b..84c27c0b4695d 100644
--- a/libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.obs/get_allocator.pass.cpp
@@ -19,14 +19,17 @@
 #include <memory>
 
 constexpr bool test() {
-  const std::indirect<int> i;
+  {
+    const std::indirect<int> i;
 
-  std::same_as<std::allocator<int>> decltype(auto) _ = i.get_allocator();
+    std::same_as<std::allocator<int>> decltype(auto) _ = i.get_allocator();
 
-  static_assert(noexcept(i.get_allocator()));
-
-  struct Incomplete;
-  (void)([](std::indirect<Incomplete>& i) { return i.get_allocator(); });
+    static_assert(noexcept(i.get_allocator()));
+  }
+  {
+    struct Incomplete;
+    (void)([](std::indirect<Incomplete>& i) { return i.get_allocator(); });
+  }
 
   return true;
 }
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp
index 81943181681d5..6beadfe5c2864 100644
--- a/libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.obs/valueless_after_move.pass.cpp
@@ -19,14 +19,17 @@
 #include <memory>
 
 constexpr bool test() {
-  const std::indirect<int> i;
+  {
+    const std::indirect<int> i;
 
-  std::same_as<bool> decltype(auto) _ = i.valueless_after_move();
+    std::same_as<bool> decltype(auto) _ = i.valueless_after_move();
 
-  static_assert(noexcept(i.valueless_after_move()));
-
-  struct Incomplete;
-  (void)([](std::indirect<Incomplete>& i) { return i.valueless_after_move(); });
+    static_assert(noexcept(i.valueless_after_move()));
+  }
+  {
+    struct Incomplete;
+    (void)([](std::indirect<Incomplete>& i) { return i.valueless_after_move(); });
+  }
 
   return true;
 }

>From b21fe5293fc266b3fdd2e4f1015d1ffc85025725 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 15:13:26 +0000
Subject: [PATCH 05/11] Add explicit include, temporarily disable some code

---
 libcxx/include/__memory/indirect.h                             | 1 +
 .../std/utilities/memory/indirect/indirect.ctor/move.pass.cpp  | 3 +++
 2 files changed, 4 insertions(+)

diff --git a/libcxx/include/__memory/indirect.h b/libcxx/include/__memory/indirect.h
index 7c3bbbe86fc77..fa0f6b1e23edc 100644
--- a/libcxx/include/__memory/indirect.h
+++ b/libcxx/include/__memory/indirect.h
@@ -12,6 +12,7 @@
 
 #include <__config>
 
+#include <__assert>
 #include <__compare/strong_order.h>
 #include <__compare/synth_three_way.h>
 #include <__functional/hash.h>
diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp
index a5d6059bf049c..d5d688905f27a 100644
--- a/libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.ctor/move.pass.cpp
@@ -86,6 +86,8 @@ constexpr void test_ctor() {
     assert(i3.get_allocator().get_data() == 67);
     assert(stats.construct_count == 1);
   }
+// Temporary hack. Will need to fix before merging.
+#if 0
   struct Incomplete;
   { // Move construction doesn't require T to be complete.
     (void)([](std::indirect<Incomplete>&& i) -> std::indirect<Incomplete> { return {std::move(i)}; });
@@ -95,6 +97,7 @@ constexpr void test_ctor() {
       return {std::allocator_arg, std::allocator<Incomplete>(), std::move(i)};
     });
   }
+#endif
 }
 
 constexpr bool test() {

>From 2cb37e7202859ffeafae92bd11094e39e48c897a Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 15:32:26 +0000
Subject: [PATCH 06/11] Add another module export

---
 libcxx/include/module.modulemap.in | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 2c0723a7b195c..d93f4b4a133ca 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1659,6 +1659,7 @@ module std [system] {
       export std.memory.allocator
       export std.compare.ordering
       export std.utility.in_place
+      export std.functional.hash
     }
     module inout_ptr                          { header "__memory/inout_ptr.h" }
     module is_sufficiently_aligned            { header "__memory/is_sufficiently_aligned.h" }

>From 9474baf652fb889a7afcbd805ea65a89a9e7d0c5 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 18:13:42 +0000
Subject: [PATCH 07/11] __p_ -> __ptr_

---
 libcxx/include/__memory/indirect.h | 84 +++++++++++++++---------------
 1 file changed, 42 insertions(+), 42 deletions(-)

diff --git a/libcxx/include/__memory/indirect.h b/libcxx/include/__memory/indirect.h
index fa0f6b1e23edc..ab16f64a53962 100644
--- a/libcxx/include/__memory/indirect.h
+++ b/libcxx/include/__memory/indirect.h
@@ -67,34 +67,34 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
   // [indirect.ctor], constructors
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect()
     requires is_default_constructible_v<_Allocator>
-      : __p_(__allocate_owned_object(__alloc_)) {}
+      : __ptr_(__allocate_owned_object(__alloc_)) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a)
-      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_)) {}
+      : __alloc_(__a), __ptr_(__allocate_owned_object(__alloc_)) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr indirect(const indirect& __other)
       : __alloc_(allocator_traits<_Allocator>::select_on_container_copy_construction(__other.__alloc_)),
-        __p_(__other.valueless_after_move() ? nullptr : __allocate_owned_object(__alloc_, *__other)) {}
+        __ptr_(__other.valueless_after_move() ? nullptr : __allocate_owned_object(__alloc_, *__other)) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, const indirect& __other)
-      : __alloc_(__a), __p_(__other.valueless_after_move() ? nullptr : __allocate_owned_object(__alloc_, *__other)) {}
+      : __alloc_(__a), __ptr_(__other.valueless_after_move() ? nullptr : __allocate_owned_object(__alloc_, *__other)) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr indirect(indirect&& __other) noexcept
-      : __alloc_(std::move(__other.__alloc_)), __p_(std::exchange(__other.__p_, nullptr)) {}
+      : __alloc_(std::move(__other.__alloc_)), __ptr_(std::exchange(__other.__ptr_, nullptr)) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, indirect&& __other) noexcept
     requires allocator_traits<_Allocator>::is_always_equal::value
-      : __alloc_(__a), __p_(std::exchange(__other.__p_, nullptr)) {}
+      : __alloc_(__a), __ptr_(std::exchange(__other.__ptr_, nullptr)) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, indirect&& __other) : __alloc_(__a) {
     if (__other.valueless_after_move()) {
-      __p_ = nullptr;
+      __ptr_ = nullptr;
     } else if (__alloc_ == __other.__alloc_) {
-      __p_ = std::exchange(__other.__p_, nullptr);
+      __ptr_ = std::exchange(__other.__ptr_, nullptr);
     } else {
-      __p_ = __allocate_owned_object(__alloc_, *std::move(__other));
+      __ptr_ = __allocate_owned_object(__alloc_, *std::move(__other));
       __other.__destroy_owned_object();
-      __other.__p_ = nullptr;
+      __other.__ptr_ = nullptr;
     }
   }
 
@@ -102,34 +102,34 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
     requires(!is_same_v<remove_cvref_t<_Up>, indirect> && !is_same_v<remove_cvref_t<_Up>, in_place_t> &&
              is_constructible_v<_Tp, _Up> && is_default_constructible_v<_Allocator>)
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(_Up&& __u)
-      : __p_(__allocate_owned_object(__alloc_, std::forward<_Up>(__u))) {}
+      : __ptr_(__allocate_owned_object(__alloc_, std::forward<_Up>(__u))) {}
 
   template <class _Up = _Tp>
     requires(!is_same_v<remove_cvref_t<_Up>, indirect> && !is_same_v<remove_cvref_t<_Up>, in_place_t> &&
              is_constructible_v<_Tp, _Up>)
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a, _Up&& __u)
-      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, std::forward<_Up>(__u))) {}
+      : __alloc_(__a), __ptr_(__allocate_owned_object(__alloc_, std::forward<_Up>(__u))) {}
 
   template <class... _Us>
     requires(is_constructible_v<_Tp, _Us...> && is_default_constructible_v<_Allocator>)
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(in_place_t, _Us&&... __us)
-      : __p_(__allocate_owned_object(__alloc_, std::forward<_Us>(__us)...)) {}
+      : __ptr_(__allocate_owned_object(__alloc_, std::forward<_Us>(__us)...)) {}
 
   template <class... _Us>
     requires is_constructible_v<_Tp, _Us...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(allocator_arg_t, const _Allocator& __a, in_place_t, _Us&&... __us)
-      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, std::forward<_Us>(__us)...)) {}
+      : __alloc_(__a), __ptr_(__allocate_owned_object(__alloc_, std::forward<_Us>(__us)...)) {}
 
   template <class _In, class... _Us>
     requires(is_constructible_v<_Tp, initializer_list<_In>&, _Us...> && is_default_constructible_v<_Allocator>)
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(in_place_t, initializer_list<_In> __ilist, _Us&&... __us)
-      : __p_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
+      : __ptr_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
 
   template <class _In, class... _Us>
     requires is_constructible_v<_Tp, initializer_list<_In>&, _Us...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect(
       allocator_arg_t, const _Allocator& __a, in_place_t, initializer_list<_In> __ilist, _Us&&... __us)
-      : __alloc_(__a), __p_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
+      : __alloc_(__a), __ptr_(__allocate_owned_object(__alloc_, __ilist, std::forward<_Us>(__us)...)) {}
 
   // [indirect.dtor], destructor
   _LIBCPP_HIDE_FROM_ABI constexpr ~indirect() { __destroy_owned_object(); }
@@ -143,20 +143,20 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
         allocator_traits<_Allocator>::propagate_on_container_copy_assignment::value;
     if (__other.valueless_after_move()) {
       __destroy_owned_object();
-      __p_ = nullptr;
+      __ptr_ = nullptr;
     } else if (!valueless_after_move() && __alloc_ == __other.__alloc_) {
-      *__p_ = *__other;
+      *__ptr_ = *__other;
     } else {
-      pointer __new_p;
+      pointer __new_ptr;
       if constexpr (__propagate_allocator) {
         // We need a mutable instance of the allocator, so make a copy.
         _Allocator __alloc_copy = __other.__alloc_;
-        __new_p                 = __allocate_owned_object(__alloc_copy, *__other);
+        __new_ptr               = __allocate_owned_object(__alloc_copy, *__other);
       } else {
-        __new_p = __allocate_owned_object(__alloc_, *__other);
+        __new_ptr = __allocate_owned_object(__alloc_, *__other);
       }
       __destroy_owned_object();
-      __p_ = __new_p;
+      __ptr_ = __new_ptr;
     }
 
     if constexpr (__propagate_allocator)
@@ -174,20 +174,20 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
     static constexpr bool __propagate_allocator =
         allocator_traits<_Allocator>::propagate_on_container_move_assignment::value;
 
-    pointer __new_p;
+    pointer __new_ptr;
     if constexpr (__propagate_allocator || allocator_traits<_Allocator>::is_always_equal::value) {
-      __new_p = __other.__p_;
+      __new_ptr = __other.__ptr_;
     } else if (__other.valueless_after_move()) {
-      __new_p = nullptr;
+      __new_ptr = nullptr;
     } else if (__alloc_ == __other.__alloc_) {
-      __new_p = __other.__p_;
+      __new_ptr = __other.__ptr_;
     } else {
-      __new_p = __allocate_owned_object(__alloc_, *std::move(__other));
+      __new_ptr = __allocate_owned_object(__alloc_, *std::move(__other));
       __other.__destroy_owned_object();
     }
-    __other.__p_ = nullptr;
+    __other.__ptr_ = nullptr;
     __destroy_owned_object();
-    __p_ = __new_p;
+    __ptr_ = __new_ptr;
 
     if constexpr (__propagate_allocator)
       __alloc_ = __other.__alloc_;
@@ -199,9 +199,9 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
     requires(!is_same_v<remove_cvref_t<_Up>, indirect> && is_constructible_v<_Tp, _Up> && is_assignable_v<_Tp&, _Up>)
   _LIBCPP_HIDE_FROM_ABI constexpr indirect& operator=(_Up&& __u) {
     if (valueless_after_move())
-      __p_ = __allocate_owned_object(__alloc_, std::forward<_Up>(__u));
+      __ptr_ = __allocate_owned_object(__alloc_, std::forward<_Up>(__u));
     else
-      *__p_ = std::forward<_Up>(__u);
+      *__ptr_ = std::forward<_Up>(__u);
     return *this;
   }
 
@@ -209,40 +209,40 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return *__p_;
+    return *__ptr_;
   }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return *__p_;
+    return *__ptr_;
   }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return std::move(*__p_);
+    return std::move(*__ptr_);
   }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return std::move(*__p_);
+    return std::move(*__ptr_);
   }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const_pointer operator->() const noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         !valueless_after_move(), "operator-> called on a valueless std::indirect object");
-    return __p_;
+    return __ptr_;
   }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr pointer operator->() noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         !valueless_after_move(), "operator-> called on a valueless std::indirect object");
-    return __p_;
+    return __ptr_;
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool valueless_after_move() const noexcept { return !__p_; }
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool valueless_after_move() const noexcept { return !__ptr_; }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr allocator_type get_allocator() const noexcept { return __alloc_; }
 
@@ -253,7 +253,7 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
     _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
         allocator_traits<_Allocator>::propagate_on_container_swap::value || get_allocator() == __other.get_allocator(),
         "swapping std::indirect objects with different allocators");
-    std::swap(__p_, __other.__p_);
+    std::swap(__ptr_, __other.__ptr_);
     std::__swap_allocator(__alloc_, __other.__alloc_);
   }
 
@@ -301,13 +301,13 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
 
   _LIBCPP_HIDE_FROM_ABI constexpr void __destroy_owned_object() noexcept {
     if (!valueless_after_move()) {
-      allocator_traits<_Allocator>::destroy(__alloc_, __p_);
-      allocator_traits<_Allocator>::deallocate(__alloc_, __p_, 1);
+      allocator_traits<_Allocator>::destroy(__alloc_, __ptr_);
+      allocator_traits<_Allocator>::deallocate(__alloc_, __ptr_, 1);
     }
   }
 
   _LIBCPP_NO_UNIQUE_ADDRESS _Allocator __alloc_ = _Allocator();
-  pointer __p_;
+  pointer __ptr_;
 };
 
 template <class _Value>

>From 71e224f9946a537ed2a9b1bf9100f1089a020025 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 21:03:07 +0000
Subject: [PATCH 08/11] Add error messages, deduplicate operator*

---
 libcxx/include/__memory/indirect.h | 43 +++++++++++-------------------
 1 file changed, 15 insertions(+), 28 deletions(-)

diff --git a/libcxx/include/__memory/indirect.h b/libcxx/include/__memory/indirect.h
index ab16f64a53962..39d86f8c96bfe 100644
--- a/libcxx/include/__memory/indirect.h
+++ b/libcxx/include/__memory/indirect.h
@@ -31,6 +31,7 @@
 #include <__type_traits/remove_cvref.h>
 #include <__utility/exchange.h>
 #include <__utility/forward.h>
+#include <__utility/forward_like.h>
 #include <__utility/in_place.h>
 #include <__utility/move.h>
 #include <__utility/swap.h>
@@ -56,13 +57,15 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
   using const_pointer  = allocator_traits<_Allocator>::const_pointer;
 
   static_assert(__check_valid_allocator<allocator_type>::value);
-  static_assert(is_same_v<typename allocator_type::value_type, value_type>);
-  static_assert(is_object_v<value_type>);
-  static_assert(!is_array_v<value_type>);
-  static_assert(!is_same_v<value_type, in_place_t>);
-  static_assert(!__is_inplace_type<value_type>::value);
+  static_assert(is_same_v<typename allocator_type::value_type, value_type>,
+                "allocator's value_type type must match std::indirect's held type");
+  static_assert(is_object_v<value_type>, "std::indirect cannot hold void or a reference or function type");
+  static_assert(!is_array_v<value_type>, "std::indirect cannot hold an array type");
+  static_assert(!is_same_v<value_type, in_place_t>, "std::indirect cannot hold std::in_place_t");
+  static_assert(!__is_inplace_type<value_type>::value,
+                "std::indirect cannot hold a specialization of std::in_place_type_t");
   static_assert(std::is_same_v<value_type, remove_cv_t<value_type>>,
-                "value_type must not be const or volatile qualified");
+                "std::indirect cannot hold a const or volatile qualified type");
 
   // [indirect.ctor], constructors
   _LIBCPP_HIDE_FROM_ABI constexpr explicit indirect()
@@ -206,28 +209,12 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
   }
 
   // [indirect.obs], observers
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept {
-    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-        !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return *__ptr_;
-  }
-
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept {
-    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-        !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return *__ptr_;
-  }
-
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept {
-    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-        !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return std::move(*__ptr_);
-  }
-
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept {
-    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-        !valueless_after_move(), "operator* called on a valueless std::indirect object");
-    return std::move(*__ptr_);
+  template <class _Self>
+    requires(!is_volatile_v<_Self>)
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto&& operator*(this _Self&& __self) noexcept {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS((!std::__forward_as<_Self, indirect>(__self).valueless_after_move()),
+                                        "operator* called on a valueless std::indirect object");
+    return std::forward_like<_Self>(*__self.__ptr_);
   }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const_pointer operator->() const noexcept {

>From 3bddbee7436d90e5ba8c29094fc54f6ca4c5ee1d Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Thu, 6 Nov 2025 22:38:13 +0000
Subject: [PATCH 09/11] Simplify a test

---
 .../memory/indirect/indirect.assign/move.pass.cpp   | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp b/libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp
index 5a9c4b2823431..a1e5f91232666 100644
--- a/libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp
+++ b/libcxx/test/std/utilities/memory/indirect/indirect.assign/move.pass.cpp
@@ -41,15 +41,12 @@ constexpr void test_assignment() {
     assert(i2.valueless_after_move());
   }
   { // Move assigning to an indirect simply transfers ownership of the held object.
-    test_allocator_statistics stats;
-    std::indirect<int, test_allocator<int>> i1(std::allocator_arg, test_allocator<int>(&stats), 1);
-    std::indirect<int, test_allocator<int>> i2(std::allocator_arg, test_allocator<int>(&stats), 2);
-    assert(stats.construct_count == 2);
+    std::indirect<int, test_allocator<int>> i1(1);
+    std::indirect<int, test_allocator<int>> i2(2);
     auto* addr2 = &*i2;
     i1          = std::move(i2);
     assert(i2.valueless_after_move());
     assert(&*i1 == addr2);
-    assert(stats.construct_count == 2);
     assert(*i1 == 2);
   }
   { // Assigning to an indirect with a different, non-POCMA allocator allocates a new owned object.
@@ -101,12 +98,6 @@ void test_assignment_throws() {
 #endif
 }
 
-template <class T>
-struct pocma_test_allocator : test_allocator<T> {
-  using test_allocator<T>::test_allocator;
-  using propagate_on_container_move_assignment = std::true_type;
-};
-
 struct Immovable {
   Immovable()                            = default;
   Immovable(const Immovable&)            = delete;

>From 73d9fce204a7e7e006a9e727a14bec258515d4e8 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Fri, 7 Nov 2025 09:01:26 -0700
Subject: [PATCH 10/11] Merge nodiscard tests

---
 .../indirect.obs/deref.nodiscard.verify.cpp   |  4 +++-
 .../get_allocator.nodiscard.verify.cpp        | 19 -------------------
 .../valueless_after_move.nodiscard.verify.cpp | 19 -------------------
 3 files changed, 3 insertions(+), 39 deletions(-)
 delete mode 100644 libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp
 delete mode 100644 libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp

diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp
index 4413237b7918e..7e759fcbe6383 100644
--- a/libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/memory/indirect/indirect.obs/deref.nodiscard.verify.cpp
@@ -10,7 +10,7 @@
 
 // <memory>
 
-// Test the libc++ extension that std::indirect<T>::operator* is marked as [[nodiscard]].
+// Test the libc++ extension that std::indirect's observers are marked as [[nodiscard]].
 
 #include <memory>
 #include <utility>
@@ -21,5 +21,7 @@ void test(std::indirect<int>& i) {
   *std::move(i); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
   *std::as_const(i); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
   *std::move(std::as_const(i)); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  i.valueless_after_move(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  i.get_allocator(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
   // clang-format on
 }
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp
deleted file mode 100644
index dbe0f30778593..0000000000000
--- a/libcxx/test/libcxx/memory/indirect/indirect.obs/get_allocator.nodiscard.verify.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// REQUIRES: std-at-least-c++26
-
-// <memory>
-
-// Test the libc++ extension that std::indirect<T>::get_allocator is marked as [[nodiscard]].
-
-#include <memory>
-
-void test(std::indirect<int>& i) {
-  i.get_allocator(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
-}
diff --git a/libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp b/libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp
deleted file mode 100644
index 6dded10bcde65..0000000000000
--- a/libcxx/test/libcxx/memory/indirect/indirect.obs/valueless_after_move.nodiscard.verify.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// REQUIRES: std-at-least-c++26
-
-// <memory>
-
-// Test the libc++ extension that std::indirect<T>::valueless_after_move is marked as [[nodiscard]].
-
-#include <memory>
-
-void test(std::indirect<int>& i) {
-  i.valueless_after_move(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
-}

>From 2b73759af7f53ca51fdf6776f7c5a2fe31105760 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Fri, 7 Nov 2025 10:34:18 -0700
Subject: [PATCH 11/11] Merge constructors

---
 libcxx/include/__memory/indirect.h | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/libcxx/include/__memory/indirect.h b/libcxx/include/__memory/indirect.h
index 39d86f8c96bfe..264bb52d9dbc6 100644
--- a/libcxx/include/__memory/indirect.h
+++ b/libcxx/include/__memory/indirect.h
@@ -85,12 +85,12 @@ class _LIBCPP_NO_SPECIALIZATIONS indirect {
   _LIBCPP_HIDE_FROM_ABI constexpr indirect(indirect&& __other) noexcept
       : __alloc_(std::move(__other.__alloc_)), __ptr_(std::exchange(__other.__ptr_, nullptr)) {}
 
-  _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, indirect&& __other) noexcept
-    requires allocator_traits<_Allocator>::is_always_equal::value
-      : __alloc_(__a), __ptr_(std::exchange(__other.__ptr_, nullptr)) {}
-
-  _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, indirect&& __other) : __alloc_(__a) {
-    if (__other.valueless_after_move()) {
+  _LIBCPP_HIDE_FROM_ABI constexpr indirect(allocator_arg_t, const _Allocator& __a, indirect&& __other) noexcept(
+      allocator_traits<_Allocator>::is_always_equal::value)
+      : __alloc_(__a) {
+    if constexpr (allocator_traits<_Allocator>::is_always_equal::value) {
+      __ptr_ = std::exchange(__other.__ptr_, nullptr);
+    } else if (__other.valueless_after_move()) {
       __ptr_ = nullptr;
     } else if (__alloc_ == __other.__alloc_) {
       __ptr_ = std::exchange(__other.__ptr_, nullptr);



More information about the libcxx-commits mailing list