[libcxx-commits] [libcxx] [libc++] Add the __is_replaceable type trait (PR #132408)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Mon Mar 24 12:09:16 PDT 2025


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/132408

>From b83e91aa8d69028b15f59fa1a3c4ebf74f2e595e Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 18 Nov 2024 23:08:08 +0100
Subject: [PATCH 1/3] [libc++] Add the __is_replaceable type trait

That type trait represents whether move-assigning an object is
equivalent to destroying it and then move-constructing a new one
from the same argument. This will be useful in a few places where
we may want to destroy + construct instead of doing an assignment,
in particular when implementing some container operations in terms
of relocation.

Eventually, the library "emulation" added by this patch can be
replaced once the compiler implements the trivial relocation
proposals.
---
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__exception/exception_ptr.h    |   5 +-
 libcxx/include/__expected/expected.h          |   3 +
 libcxx/include/__locale                       |   5 +-
 libcxx/include/__memory/shared_ptr.h          |   7 +
 libcxx/include/__memory/unique_ptr.h          |   5 +
 libcxx/include/__split_buffer                 |   5 +
 libcxx/include/__type_traits/is_replaceable.h |  59 ++++
 libcxx/include/__utility/pair.h               |   4 +
 libcxx/include/__vector/vector.h              |   5 +
 libcxx/include/array                          |   2 +
 libcxx/include/deque                          |   5 +
 libcxx/include/module.modulemap               |   4 +
 libcxx/include/optional                       |   2 +
 libcxx/include/string                         |   8 +
 libcxx/include/tuple                          |   6 +-
 libcxx/include/variant                        |  30 +-
 .../is_replaceable.compile.pass.cpp           | 269 ++++++++++++++++++
 18 files changed, 407 insertions(+), 18 deletions(-)
 create mode 100644 libcxx/include/__type_traits/is_replaceable.h
 create mode 100644 libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index a021b9bb44d67..74b2fda01fc60 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -840,6 +840,7 @@ set(files
   __type_traits/is_reference.h
   __type_traits/is_reference_wrapper.h
   __type_traits/is_referenceable.h
+  __type_traits/is_replaceable.h
   __type_traits/is_same.h
   __type_traits/is_scalar.h
   __type_traits/is_signed.h
diff --git a/libcxx/include/__exception/exception_ptr.h b/libcxx/include/__exception/exception_ptr.h
index 6257e6f729bf3..e93971cf7fc48 100644
--- a/libcxx/include/__exception/exception_ptr.h
+++ b/libcxx/include/__exception/exception_ptr.h
@@ -65,8 +65,11 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
   friend _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT;
 
 public:
-  // exception_ptr is basically a COW string.
+  // exception_ptr is basically a COW string so it is trivially relocatable.
+  // However, it's not replaceable because destroying and move-constructing could cause
+  // the underlying refcount to hit 0 if we're self-assigning.
   using __trivially_relocatable _LIBCPP_NODEBUG = exception_ptr;
+  using __replaceable _LIBCPP_NODEBUG           = void;
 
   _LIBCPP_HIDE_FROM_ABI exception_ptr() _NOEXCEPT : __ptr_() {}
   _LIBCPP_HIDE_FROM_ABI exception_ptr(nullptr_t) _NOEXCEPT : __ptr_() {}
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 03bbd1623ed5c..73d2845efa548 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -29,6 +29,7 @@
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/is_replaceable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_constructible.h>
@@ -470,6 +471,8 @@ class expected : private __expected_base<_Tp, _Err> {
       __conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value && __libcpp_is_trivially_relocatable<_Err>::value,
                       expected,
                       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<_Tp>::value && __is_replaceable<_Err>::value, expected, void>;
 
   template <class _Up>
   using rebind = expected<_Up, error_type>;
diff --git a/libcxx/include/__locale b/libcxx/include/__locale
index 5ae3228989749..429395c740a05 100644
--- a/libcxx/include/__locale
+++ b/libcxx/include/__locale
@@ -50,8 +50,11 @@ _LIBCPP_HIDE_FROM_ABI const _Facet& use_facet(const locale&);
 
 class _LIBCPP_EXPORTED_FROM_ABI locale {
 public:
-  // locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor.
+  // locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor,
+  // so it is trivially relocatable. However, it is not replaceable because self-assignment must prevent
+  // the refcount from hitting 0.
   using __trivially_relocatable _LIBCPP_NODEBUG = locale;
+  using __replaceable _LIBCPP_NODEBUG           = void;
 
   // types:
   class _LIBCPP_EXPORTED_FROM_ABI facet;
diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index e1d49c4594866..752773e2cf934 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -317,7 +317,11 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS shared_ptr {
 
   // A shared_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
   // any bookkeeping, so it's always trivially relocatable.
+  //
+  // However, it's not replaceable because of self-assignment, which must prevent the refcount from
+  // hitting 0.
   using __trivially_relocatable _LIBCPP_NODEBUG = shared_ptr;
+  using __replaceable _LIBCPP_NODEBUG           = void;
 
 private:
   element_type* __ptr_;
@@ -1212,7 +1216,10 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS weak_ptr {
 
   // A weak_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
   // any bookkeeping, so it's always trivially relocatable.
+  //
+  // However, it's not replaceable because we must preserve a non-zero refcount through self-assignment.
   using __trivially_relocatable _LIBCPP_NODEBUG = weak_ptr;
+  using __replaceable _LIBCPP_NODEBUG           = void;
 
 private:
   element_type* __ptr_;
diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index 6f1dc98db5a9f..b3219b8ed214e 100644
--- a/libcxx/include/__memory/unique_ptr.h
+++ b/libcxx/include/__memory/unique_ptr.h
@@ -39,6 +39,7 @@
 #include <__type_traits/is_function.h>
 #include <__type_traits/is_pointer.h>
 #include <__type_traits/is_reference.h>
+#include <__type_traits/is_replaceable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_relocatable.h>
@@ -144,6 +145,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
       __libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
       unique_ptr,
       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;
 
 private:
   _LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
@@ -410,6 +413,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
       __libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
       unique_ptr,
       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;
 
 private:
   template <class _Up, class _OtherDeleter>
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index 721d4d497f2a5..1d75657baa4f8 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -28,6 +28,7 @@
 #include <__type_traits/integral_constant.h>
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_replaceable.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_destructible.h>
 #include <__type_traits/is_trivially_relocatable.h>
@@ -72,6 +73,10 @@ public:
       __libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
       __split_buffer,
       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+                      __split_buffer,
+                      void>;
 
   pointer __first_;
   pointer __begin_;
diff --git a/libcxx/include/__type_traits/is_replaceable.h b/libcxx/include/__type_traits/is_replaceable.h
new file mode 100644
index 0000000000000..46eb6f79ecb69
--- /dev/null
+++ b/libcxx/include/__type_traits/is_replaceable.h
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___TYPE_TRAITS_IS_REPLACEABLE_H
+#define _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H
+
+#include <__config>
+#include <__type_traits/enable_if.h>
+#include <__type_traits/integral_constant.h>
+#include <__type_traits/is_same.h>
+#include <__type_traits/is_trivially_copyable.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+// A type is replaceable if `x = std::move(y)` is equivalent to:
+//
+//  std::destroy_at(&x)
+//  std::construct_at(&x, std::move(y))
+//
+// This allows turning a move-assignment into a sequence of destroy + move-construct, which
+// is often more efficient. This is especially relevant when the move-construct is in fact
+// part of a trivial relocation from somewhere else, in which case there is a huge win.
+//
+// Note that this requires language support in order to be really effective, but we
+// currently emulate the base template with something very conservative.
+template <class _Tp, class = void>
+struct __is_replaceable : is_trivially_copyable<_Tp> {};
+
+template <class _Tp>
+struct __is_replaceable<_Tp, __enable_if_t<is_same<_Tp, typename _Tp::__replaceable>::value> > : true_type {};
+
+// Determines whether an allocator member of a container is replaceable.
+//
+// We take into account whether the allocator is propagated on assignments. If the allocator
+// always compares equal, then it doesn't matter whether we propagate it or not on assignments,
+// the result will be the same and we can just as much move-construct it instead.
+//
+// If the allocator does not always compare equal, we check whether it propagates on assignment
+// and it is replaceable.
+template <class _AllocatorTraits>
+struct __container_allocator_is_replaceable
+    : integral_constant<bool,
+                        _AllocatorTraits::is_always_equal::value ||
+                            (_AllocatorTraits::propagate_on_container_move_assignment::value &&
+                             _AllocatorTraits::propagate_on_container_copy_assignment::value &&
+                             __is_replaceable<typename _AllocatorTraits::allocator_type>::value)> {};
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index 1f596a87f7cc7..58b91c4dd1379 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -31,6 +31,8 @@
 #include <__type_traits/is_implicitly_default_constructible.h>
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_replaceable.h>
+#include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
 #include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/nat.h>
@@ -72,6 +74,8 @@ struct _LIBCPP_TEMPLATE_VIS pair
       __conditional_t<__libcpp_is_trivially_relocatable<_T1>::value && __libcpp_is_trivially_relocatable<_T2>::value,
                       pair,
                       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<_T1>::value && __is_replaceable<_T2>::value, pair, void>;
 
   _LIBCPP_HIDE_FROM_ABI pair(pair const&) = default;
   _LIBCPP_HIDE_FROM_ABI pair(pair&&)      = default;
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 9155fb52a69b1..535359ff6831f 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -55,6 +55,7 @@
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/is_pointer.h>
+#include <__type_traits/is_replaceable.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/type_identity.h>
@@ -120,6 +121,10 @@ class _LIBCPP_TEMPLATE_VIS vector {
       __libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
       vector,
       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+                      vector,
+                      void>;
 
   static_assert(__check_valid_allocator<allocator_type>::value, "");
   static_assert(is_same<typename allocator_type::value_type, value_type>::value,
diff --git a/libcxx/include/array b/libcxx/include/array
index d536575d41680..227c11467ed67 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -134,6 +134,7 @@ template <size_t I, class T, size_t N> const T&& get(const array<T, N>&&) noexce
 #  include <__type_traits/is_const.h>
 #  include <__type_traits/is_constructible.h>
 #  include <__type_traits/is_nothrow_constructible.h>
+#  include <__type_traits/is_replaceable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
 #  include <__type_traits/is_trivially_relocatable.h>
@@ -175,6 +176,7 @@ template <class _Tp, size_t _Size>
 struct _LIBCPP_TEMPLATE_VIS array {
   using __trivially_relocatable _LIBCPP_NODEBUG =
       __conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, array, void>;
+  using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable<_Tp>::value, array, void>;
 
   // types:
   using __self _LIBCPP_NODEBUG = array;
diff --git a/libcxx/include/deque b/libcxx/include/deque
index 04788c277e428..ef0dda802f2f1 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -230,6 +230,7 @@ template <class T, class Allocator, class Predicate>
 #  include <__type_traits/is_convertible.h>
 #  include <__type_traits/is_nothrow_assignable.h>
 #  include <__type_traits/is_nothrow_constructible.h>
+#  include <__type_traits/is_replaceable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
 #  include <__type_traits/is_trivially_relocatable.h>
@@ -530,6 +531,10 @@ public:
       __libcpp_is_trivially_relocatable<__map>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
       deque,
       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<__map>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+                      deque,
+                      void>;
 
   static_assert(is_nothrow_default_constructible<allocator_type>::value ==
                     is_nothrow_default_constructible<__pointer_allocator>::value,
diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index b42f945a6832c..52a4eded2c2f1 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -275,6 +275,10 @@ module std_core [system] {
       header "__type_traits/is_referenceable.h"
       export std_core.type_traits.integral_constant
     }
+    module is_replaceable {
+      header "__type_traits/is_replaceable.h"
+      export std_core.type_traits.integral_constant
+    }
     module is_same {
       header "__type_traits/is_same.h"
       export std_core.type_traits.integral_constant
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 294c3ddf993fa..b4d7127c1ea99 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -210,6 +210,7 @@ namespace std {
 #  include <__type_traits/is_nothrow_constructible.h>
 #  include <__type_traits/is_object.h>
 #  include <__type_traits/is_reference.h>
+#  include <__type_traits/is_replaceable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_scalar.h>
 #  include <__type_traits/is_swappable.h>
@@ -590,6 +591,7 @@ public:
 
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
+  using __replaceable _LIBCPP_NODEBUG = conditional_t<__is_replaceable<_Tp>::value, optional, void>;
 
 private:
   // Disable the reference extension using this static assert.
diff --git a/libcxx/include/string b/libcxx/include/string
index fa87dc2fddb59..c7e33a83fd758 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -630,6 +630,7 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
 #  include <__type_traits/is_convertible.h>
 #  include <__type_traits/is_nothrow_assignable.h>
 #  include <__type_traits/is_nothrow_constructible.h>
+#  include <__type_traits/is_replaceable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_standard_layout.h>
 #  include <__type_traits/is_trivial.h>
@@ -754,6 +755,9 @@ public:
   // external memory. In such cases, the destructor is responsible for unpoisoning
   // the memory to avoid triggering false positives.
   // Therefore it's crucial to ensure the destructor is called.
+  //
+  // However, it is replaceable since implementing move-assignment as a destroy + move-construct
+  // will maintain the right ASAN state.
   using __trivially_relocatable = void;
 #  else
   using __trivially_relocatable _LIBCPP_NODEBUG = __conditional_t<
@@ -761,6 +765,10 @@ public:
       basic_string,
       void>;
 #  endif
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+                      basic_string,
+                      void>;
 
 #  if _LIBCPP_HAS_ASAN && _LIBCPP_INSTRUMENTED_WITH_ASAN
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pointer __asan_volatile_wrapper(pointer const& __ptr) const {
diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index e284f71200492..76d6bd7811132 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -250,6 +250,7 @@ template <class... Types>
 #  include <__type_traits/is_nothrow_assignable.h>
 #  include <__type_traits/is_nothrow_constructible.h>
 #  include <__type_traits/is_reference.h>
+#  include <__type_traits/is_replaceable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
 #  include <__type_traits/is_trivially_relocatable.h>
@@ -462,8 +463,8 @@ template <class _Indx, class... _Tp>
 struct __tuple_impl;
 
 template <size_t... _Indx, class... _Tp>
-struct _LIBCPP_DECLSPEC_EMPTY_BASES __tuple_impl<__tuple_indices<_Indx...>, _Tp...>
-    : public __tuple_leaf<_Indx, _Tp>... {
+struct _LIBCPP_DECLSPEC_EMPTY_BASES
+    __tuple_impl<__tuple_indices<_Indx...>, _Tp...> : public __tuple_leaf<_Indx, _Tp>... {
   _LIBCPP_HIDE_FROM_ABI constexpr __tuple_impl() noexcept(
       __all<is_nothrow_default_constructible<_Tp>::value...>::value) {}
 
@@ -555,6 +556,7 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_NO_SPECIALIZATIONS tuple {
 public:
   using __trivially_relocatable _LIBCPP_NODEBUG =
       __conditional_t<_And<__libcpp_is_trivially_relocatable<_Tp>...>::value, tuple, void>;
+  using __replaceable _LIBCPP_NODEBUG = __conditional_t<_And<__is_replaceable<_Tp>...>::value, tuple, void>;
 
   // [tuple.cnstr]
 
diff --git a/libcxx/include/variant b/libcxx/include/variant
index 38c34725d5e03..7989274a6a4d7 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -246,6 +246,7 @@ namespace std {
 #  include <__type_traits/is_nothrow_assignable.h>
 #  include <__type_traits/is_nothrow_constructible.h>
 #  include <__type_traits/is_reference.h>
+#  include <__type_traits/is_replaceable.h>
 #  include <__type_traits/is_same.h>
 #  include <__type_traits/is_swappable.h>
 #  include <__type_traits/is_trivially_assignable.h>
@@ -752,8 +753,8 @@ class _LIBCPP_TEMPLATE_VIS __dtor;
 
 #    define _LIBCPP_VARIANT_DESTRUCTOR(destructible_trait, destructor_definition, destroy)                             \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS __dtor<__traits<_Types...>, destructible_trait>                                       \
-          : public __base<destructible_trait, _Types...> {                                                             \
+      class _LIBCPP_TEMPLATE_VIS                                                                                       \
+      __dtor<__traits<_Types...>, destructible_trait> : public __base<destructible_trait, _Types...> {                 \
         using __base_type _LIBCPP_NODEBUG = __base<destructible_trait, _Types...>;                                     \
         using __index_t _LIBCPP_NODEBUG   = typename __base_type::__index_t;                                           \
                                                                                                                        \
@@ -830,8 +831,8 @@ class _LIBCPP_TEMPLATE_VIS __move_constructor;
 
 #    define _LIBCPP_VARIANT_MOVE_CONSTRUCTOR(move_constructible_trait, move_constructor_definition)                    \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS __move_constructor<__traits<_Types...>, move_constructible_trait>                     \
-          : public __ctor<__traits<_Types...>> {                                                                       \
+      class _LIBCPP_TEMPLATE_VIS                                                                                       \
+      __move_constructor<__traits<_Types...>, move_constructible_trait> : public __ctor<__traits<_Types...>> {         \
         using __base_type _LIBCPP_NODEBUG = __ctor<__traits<_Types...>>;                                               \
                                                                                                                        \
       public:                                                                                                          \
@@ -852,8 +853,7 @@ _LIBCPP_VARIANT_MOVE_CONSTRUCTOR(
 _LIBCPP_VARIANT_MOVE_CONSTRUCTOR(
     _Trait::_Available,
     _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __move_constructor(__move_constructor&& __that) noexcept(
-        __all<is_nothrow_move_constructible_v<_Types>...>::value)
-    : __move_constructor(__valueless_t{}) {
+        __all<is_nothrow_move_constructible_v<_Types>...>::value) : __move_constructor(__valueless_t{}) {
       this->__generic_construct(*this, std::move(__that));
     } _LIBCPP_EAT_SEMICOLON);
 
@@ -889,8 +889,9 @@ _LIBCPP_VARIANT_COPY_CONSTRUCTOR(
 
 _LIBCPP_VARIANT_COPY_CONSTRUCTOR(
     _Trait::_Available,
-    _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __copy_constructor(const __copy_constructor& __that)
-    : __copy_constructor(__valueless_t{}) { this->__generic_construct(*this, __that); } _LIBCPP_EAT_SEMICOLON);
+    _LIBCPP_HIDE_FROM_ABI
+    _LIBCPP_CONSTEXPR_SINCE_CXX20 __copy_constructor(const __copy_constructor& __that) : __copy_constructor(
+        __valueless_t{}) { this->__generic_construct(*this, __that); } _LIBCPP_EAT_SEMICOLON);
 
 _LIBCPP_VARIANT_COPY_CONSTRUCTOR(
     _Trait::_Unavailable,
@@ -957,8 +958,8 @@ class _LIBCPP_TEMPLATE_VIS __move_assignment;
 
 #    define _LIBCPP_VARIANT_MOVE_ASSIGNMENT(move_assignable_trait, move_assignment_definition)                         \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS __move_assignment<__traits<_Types...>, move_assignable_trait>                         \
-          : public __assignment<__traits<_Types...>> {                                                                 \
+      class _LIBCPP_TEMPLATE_VIS                                                                                       \
+      __move_assignment<__traits<_Types...>, move_assignable_trait> : public __assignment<__traits<_Types...>> {       \
         using __base_type _LIBCPP_NODEBUG = __assignment<__traits<_Types...>>;                                         \
                                                                                                                        \
       public:                                                                                                          \
@@ -996,8 +997,8 @@ class _LIBCPP_TEMPLATE_VIS __copy_assignment;
 
 #    define _LIBCPP_VARIANT_COPY_ASSIGNMENT(copy_assignable_trait, copy_assignment_definition)                         \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS __copy_assignment<__traits<_Types...>, copy_assignable_trait>                         \
-          : public __move_assignment<__traits<_Types...>> {                                                            \
+      class _LIBCPP_TEMPLATE_VIS                                                                                       \
+      __copy_assignment<__traits<_Types...>, copy_assignable_trait> : public __move_assignment<__traits<_Types...>> {  \
         using __base_type _LIBCPP_NODEBUG = __move_assignment<__traits<_Types...>>;                                    \
                                                                                                                        \
       public:                                                                                                          \
@@ -1176,6 +1177,7 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_DECLSPEC_EMPTY_BASES _LIBCPP_NO_SPECIALIZATIO
 public:
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<_And<__libcpp_is_trivially_relocatable<_Types>...>::value, variant, void>;
+  using __replaceable = conditional_t<_And<__is_replaceable<_Types>...>::value, variant, void>;
 
   template <bool _Dummy                                                                               = true,
             enable_if_t<__dependent_type<is_default_constructible<__first_type>, _Dummy>::value, int> = 0>
@@ -1579,8 +1581,8 @@ visit(_Visitor&& __visitor, _Vs&&... __vs) {
 
 template <class... _Types>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 auto
-swap(variant<_Types...>& __lhs,
-     variant<_Types...>& __rhs) noexcept(noexcept(__lhs.swap(__rhs))) -> decltype(__lhs.swap(__rhs)) {
+swap(variant<_Types...>& __lhs, variant<_Types...>& __rhs) noexcept(noexcept(__lhs.swap(__rhs)))
+    -> decltype(__lhs.swap(__rhs)) {
   return __lhs.swap(__rhs);
 }
 
diff --git a/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp b/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
new file mode 100644
index 0000000000000..c52f5462f8bb3
--- /dev/null
+++ b/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
@@ -0,0 +1,269 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include <__type_traits/is_replaceable.h>
+#include <array>
+#include <deque>
+#include <exception>
+#include <expected>
+#include <memory>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <variant>
+#include <vector>
+
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+#include "test_macros.h"
+
+#ifndef TEST_HAS_NO_LOCALIZATION
+#  include <locale>
+#endif
+
+template <class T>
+struct NonPropagatingStatefulMoveAssignAlloc : std::allocator<T> {
+  using propagate_on_container_move_assignment = std::false_type;
+  using is_always_equal                        = std::false_type;
+};
+
+template <class T>
+struct NonPropagatingStatefulCopyAssignAlloc : std::allocator<T> {
+  using propagate_on_container_copy_assignment = std::false_type;
+  using is_always_equal                        = std::false_type;
+};
+
+template <class T>
+struct NonPropagatingStatelessMoveAssignAlloc : std::allocator<T> {
+  using propagate_on_container_move_assignment = std::false_type;
+  using is_always_equal                        = std::true_type;
+};
+
+template <class T>
+struct NonPropagatingStatelessCopyAssignAlloc : std::allocator<T> {
+  using propagate_on_container_copy_assignment = std::false_type;
+  using is_always_equal                        = std::true_type;
+};
+
+struct Empty {};
+static_assert(std::__is_replaceable<char>::value, "");
+static_assert(std::__is_replaceable<int>::value, "");
+static_assert(std::__is_replaceable<double>::value, "");
+static_assert(std::__is_replaceable<Empty>::value, "");
+
+struct TriviallyCopyable {
+  char c;
+  int i;
+  Empty s;
+};
+static_assert(std::__is_replaceable<TriviallyCopyable>::value, "");
+
+struct NotTriviallyCopyable {
+  NotTriviallyCopyable(const NotTriviallyCopyable&);
+  ~NotTriviallyCopyable();
+};
+static_assert(!std::__is_replaceable<NotTriviallyCopyable>::value, "");
+
+struct MoveOnlyTriviallyCopyable {
+  MoveOnlyTriviallyCopyable(const MoveOnlyTriviallyCopyable&)            = delete;
+  MoveOnlyTriviallyCopyable& operator=(const MoveOnlyTriviallyCopyable&) = delete;
+  MoveOnlyTriviallyCopyable(MoveOnlyTriviallyCopyable&&)                 = default;
+  MoveOnlyTriviallyCopyable& operator=(MoveOnlyTriviallyCopyable&&)      = default;
+};
+static_assert(std::__is_replaceable<MoveOnlyTriviallyCopyable>::value, "");
+
+struct CustomCopyAssignment {
+  CustomCopyAssignment(const CustomCopyAssignment&) = default;
+  CustomCopyAssignment(CustomCopyAssignment&&)      = default;
+  CustomCopyAssignment& operator=(const CustomCopyAssignment&);
+  CustomCopyAssignment& operator=(CustomCopyAssignment&&) = default;
+};
+static_assert(!std::__is_replaceable<CustomCopyAssignment>::value, "");
+
+struct CustomMoveAssignment {
+  CustomMoveAssignment(const CustomMoveAssignment&)            = default;
+  CustomMoveAssignment(CustomMoveAssignment&&)                 = default;
+  CustomMoveAssignment& operator=(const CustomMoveAssignment&) = default;
+  CustomMoveAssignment& operator=(CustomMoveAssignment&&);
+};
+static_assert(!std::__is_replaceable<CustomMoveAssignment>::value, "");
+
+// library-internal types
+// ----------------------
+
+// __split_buffer
+static_assert(std::__is_replaceable<std::__split_buffer<int> >::value, "");
+static_assert(std::__is_replaceable<std::__split_buffer<NotTriviallyCopyable> >::value, "");
+static_assert(!std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatefulCopyAssignAlloc<int> > >::value,
+              "");
+static_assert(!std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatefulMoveAssignAlloc<int> > >::value,
+              "");
+static_assert(std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatelessCopyAssignAlloc<int> > >::value,
+              "");
+static_assert(std::__is_replaceable<std::__split_buffer<int, NonPropagatingStatelessMoveAssignAlloc<int> > >::value,
+              "");
+
+// standard library types
+// ----------------------
+
+// array
+static_assert(std::__is_replaceable<std::array<int, 0> >::value, "");
+static_assert(std::__is_replaceable<std::array<NotTriviallyCopyable, 0> >::value, "");
+static_assert(std::__is_replaceable<std::array<std::unique_ptr<int>, 0> >::value, "");
+
+static_assert(std::__is_replaceable<std::array<int, 1> >::value, "");
+static_assert(!std::__is_replaceable<std::array<NotTriviallyCopyable, 1> >::value, "");
+static_assert(std::__is_replaceable<std::array<std::unique_ptr<int>, 1> >::value, "");
+
+// basic_string
+struct MyChar {
+  char c;
+};
+template <class T>
+struct NotReplaceableCharTraits : constexpr_char_traits<T> {
+  NotReplaceableCharTraits(const NotReplaceableCharTraits&);
+  NotReplaceableCharTraits& operator=(const NotReplaceableCharTraits&);
+  ~NotReplaceableCharTraits();
+};
+
+static_assert(std::__is_replaceable<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >::value,
+              "");
+static_assert(
+    std::__is_replaceable<std::basic_string<char, NotReplaceableCharTraits<char>, std::allocator<char> > >::value, "");
+static_assert(
+    std::__is_replaceable<std::basic_string<MyChar, constexpr_char_traits<MyChar>, std::allocator<MyChar> > >::value,
+    "");
+static_assert(!std::__is_replaceable<std::basic_string<char, std::char_traits<char>, test_allocator<char> > >::value,
+              "");
+static_assert(std::__is_replaceable<
+                  std::basic_string<MyChar, NotReplaceableCharTraits<MyChar>, std::allocator<MyChar> > >::value,
+              "");
+static_assert(
+    !std::__is_replaceable<
+        std::basic_string<char, std::char_traits<char>, NonPropagatingStatefulCopyAssignAlloc<char> > >::value,
+    "");
+static_assert(
+    !std::__is_replaceable<
+        std::basic_string<char, std::char_traits<char>, NonPropagatingStatefulMoveAssignAlloc<char> > >::value,
+    "");
+static_assert(
+    std::__is_replaceable<
+        std::basic_string<char, std::char_traits<char>, NonPropagatingStatelessCopyAssignAlloc<char> > >::value,
+    "");
+static_assert(
+    std::__is_replaceable<
+        std::basic_string<char, std::char_traits<char>, NonPropagatingStatelessMoveAssignAlloc<char> > >::value,
+    "");
+
+// deque
+static_assert(std::__is_replaceable<std::deque<int> >::value, "");
+static_assert(std::__is_replaceable<std::deque<NotTriviallyCopyable> >::value, "");
+static_assert(!std::__is_replaceable<std::deque<int, test_allocator<int> > >::value, "");
+static_assert(!std::__is_replaceable<std::deque<int, NonPropagatingStatefulCopyAssignAlloc<int> > >::value, "");
+static_assert(!std::__is_replaceable<std::deque<int, NonPropagatingStatefulMoveAssignAlloc<int> > >::value, "");
+static_assert(std::__is_replaceable<std::deque<int, NonPropagatingStatelessCopyAssignAlloc<int> > >::value, "");
+static_assert(std::__is_replaceable<std::deque<int, NonPropagatingStatelessMoveAssignAlloc<int> > >::value, "");
+
+// exception_ptr
+static_assert(!std::__is_replaceable<std::exception_ptr>::value, "");
+
+// expected
+#if TEST_STD_VER >= 23
+static_assert(std::__is_replaceable<std::expected<int, int> >::value);
+static_assert(!std::__is_replaceable<std::expected<CustomCopyAssignment, int>>::value);
+static_assert(!std::__is_replaceable<std::expected<int, CustomCopyAssignment>>::value);
+static_assert(!std::__is_replaceable<std::expected<CustomCopyAssignment, CustomCopyAssignment>>::value);
+#endif
+
+// locale
+#ifndef TEST_HAS_NO_LOCALIZATION
+static_assert(!std::__is_replaceable<std::locale>::value, "");
+#endif
+
+// optional
+#if TEST_STD_VER >= 17
+static_assert(std::__is_replaceable<std::optional<int>>::value, "");
+static_assert(!std::__is_replaceable<std::optional<CustomCopyAssignment>>::value, "");
+#endif
+
+// pair
+static_assert(std::__is_replaceable<std::pair<int, int> >::value, "");
+static_assert(!std::__is_replaceable<std::pair<CustomCopyAssignment, int> >::value, "");
+static_assert(!std::__is_replaceable<std::pair<int, CustomCopyAssignment> >::value, "");
+static_assert(!std::__is_replaceable<std::pair<CustomCopyAssignment, CustomCopyAssignment> >::value, "");
+
+// shared_ptr
+static_assert(!std::__is_replaceable<std::shared_ptr<int> >::value, "");
+
+// tuple
+#if TEST_STD_VER >= 11
+static_assert(std::__is_replaceable<std::tuple<> >::value, "");
+
+static_assert(std::__is_replaceable<std::tuple<int> >::value, "");
+static_assert(!std::__is_replaceable<std::tuple<CustomCopyAssignment> >::value, "");
+
+static_assert(std::__is_replaceable<std::tuple<int, int> >::value, "");
+static_assert(!std::__is_replaceable<std::tuple<CustomCopyAssignment, int> >::value, "");
+static_assert(!std::__is_replaceable<std::tuple<int, CustomCopyAssignment> >::value, "");
+static_assert(!std::__is_replaceable<std::tuple<CustomCopyAssignment, CustomCopyAssignment> >::value, "");
+#endif // TEST_STD_VER >= 11
+
+// unique_ptr
+struct NonReplaceableDeleter {
+  NonReplaceableDeleter(const NonReplaceableDeleter&);
+  NonReplaceableDeleter& operator=(const NonReplaceableDeleter&);
+  ~NonReplaceableDeleter();
+
+  template <class T>
+  void operator()(T*);
+};
+
+struct NonReplaceablePointer {
+  struct pointer {
+    pointer(const pointer&);
+    pointer& operator=(const pointer&);
+    ~pointer();
+  };
+
+  template <class T>
+  void operator()(T*);
+};
+
+static_assert(std::__is_replaceable<std::unique_ptr<int> >::value, "");
+static_assert(std::__is_replaceable<std::unique_ptr<CustomCopyAssignment> >::value, "");
+static_assert(std::__is_replaceable<std::unique_ptr<int[]> >::value, "");
+static_assert(!std::__is_replaceable<std::unique_ptr<int, NonReplaceableDeleter> >::value, "");
+static_assert(!std::__is_replaceable<std::unique_ptr<int[], NonReplaceableDeleter> >::value, "");
+static_assert(!std::__is_replaceable<std::unique_ptr<int, NonReplaceablePointer> >::value, "");
+static_assert(!std::__is_replaceable<std::unique_ptr<int[], NonReplaceablePointer> >::value, "");
+
+// variant
+#if TEST_STD_VER >= 17
+static_assert(std::__is_replaceable<std::variant<int> >::value, "");
+static_assert(!std::__is_replaceable<std::variant<CustomCopyAssignment> >::value, "");
+
+static_assert(std::__is_replaceable<std::variant<int, int> >::value, "");
+static_assert(!std::__is_replaceable<std::variant<CustomCopyAssignment, int> >::value, "");
+static_assert(!std::__is_replaceable<std::variant<int, CustomCopyAssignment> >::value, "");
+static_assert(!std::__is_replaceable<std::variant<CustomCopyAssignment, CustomCopyAssignment> >::value, "");
+#endif // TEST_STD_VER >= 17
+
+// vector
+static_assert(std::__is_replaceable<std::vector<int> >::value, "");
+static_assert(std::__is_replaceable<std::vector<CustomCopyAssignment> >::value, "");
+static_assert(!std::__is_replaceable<std::vector<int, test_allocator<int> > >::value, "");
+static_assert(!std::__is_replaceable<std::vector<int, NonPropagatingStatefulCopyAssignAlloc<int> > >::value, "");
+static_assert(!std::__is_replaceable<std::vector<int, NonPropagatingStatefulMoveAssignAlloc<int> > >::value, "");
+static_assert(std::__is_replaceable<std::vector<int, NonPropagatingStatelessCopyAssignAlloc<int> > >::value, "");
+static_assert(std::__is_replaceable<std::vector<int, NonPropagatingStatelessMoveAssignAlloc<int> > >::value, "");
+
+// weak_ptr
+static_assert(!std::__is_replaceable<std::weak_ptr<CustomCopyAssignment> >::value, "");
+
+// TODO: Mark all the replaceable STL types as such

>From be5f6203ee644aa132d9acc0bb32adae9f6995e0 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 24 Mar 2025 15:02:47 -0400
Subject: [PATCH 2/3] Review comments

---
 libcxx/include/__expected/expected.h          |  2 +-
 libcxx/include/__memory/unique_ptr.h          |  4 ++--
 libcxx/include/__split_buffer                 |  2 +-
 libcxx/include/__type_traits/is_replaceable.h |  5 ++++-
 libcxx/include/__utility/pair.h               |  3 +--
 libcxx/include/__vector/vector.h              |  2 +-
 libcxx/include/array                          |  2 +-
 libcxx/include/deque                          |  2 +-
 libcxx/include/optional                       |  2 +-
 libcxx/include/string                         |  2 +-
 libcxx/include/variant                        |  2 +-
 .../is_replaceable.compile.pass.cpp           | 20 +++++++++++++++++++
 12 files changed, 35 insertions(+), 13 deletions(-)

diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 73d2845efa548..a1971cbb36a26 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -472,7 +472,7 @@ class expected : private __expected_base<_Tp, _Err> {
                       expected,
                       void>;
   using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<_Tp>::value && __is_replaceable<_Err>::value, expected, void>;
+      __conditional_t<__is_replaceable_v<_Tp> && __is_replaceable_v<_Err>, expected, void>;
 
   template <class _Up>
   using rebind = expected<_Up, error_type>;
diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index b3219b8ed214e..4fe17e102105f 100644
--- a/libcxx/include/__memory/unique_ptr.h
+++ b/libcxx/include/__memory/unique_ptr.h
@@ -146,7 +146,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
       unique_ptr,
       void>;
   using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;
+      __conditional_t<__is_replaceable_v<pointer> && __is_replaceable_v<deleter_type>, unique_ptr, void>;
 
 private:
   _LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
@@ -414,7 +414,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
       unique_ptr,
       void>;
   using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<pointer>::value && __is_replaceable<deleter_type>::value, unique_ptr, void>;
+      __conditional_t<__is_replaceable_v<pointer> && __is_replaceable_v<deleter_type>, unique_ptr, void>;
 
 private:
   template <class _Up, class _OtherDeleter>
diff --git a/libcxx/include/__split_buffer b/libcxx/include/__split_buffer
index 1d75657baa4f8..21e58f4abc6b3 100644
--- a/libcxx/include/__split_buffer
+++ b/libcxx/include/__split_buffer
@@ -74,7 +74,7 @@ public:
       __split_buffer,
       void>;
   using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+      __conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
                       __split_buffer,
                       void>;
 
diff --git a/libcxx/include/__type_traits/is_replaceable.h b/libcxx/include/__type_traits/is_replaceable.h
index 46eb6f79ecb69..a7f87da8a3092 100644
--- a/libcxx/include/__type_traits/is_replaceable.h
+++ b/libcxx/include/__type_traits/is_replaceable.h
@@ -38,6 +38,9 @@ struct __is_replaceable : is_trivially_copyable<_Tp> {};
 template <class _Tp>
 struct __is_replaceable<_Tp, __enable_if_t<is_same<_Tp, typename _Tp::__replaceable>::value> > : true_type {};
 
+template <class _Tp>
+inline const bool __is_replaceable_v = __is_replaceable<_Tp>::value;
+
 // Determines whether an allocator member of a container is replaceable.
 //
 // We take into account whether the allocator is propagated on assignments. If the allocator
@@ -52,7 +55,7 @@ struct __container_allocator_is_replaceable
                         _AllocatorTraits::is_always_equal::value ||
                             (_AllocatorTraits::propagate_on_container_move_assignment::value &&
                              _AllocatorTraits::propagate_on_container_copy_assignment::value &&
-                             __is_replaceable<typename _AllocatorTraits::allocator_type>::value)> {};
+                             __is_replaceable_v<typename _AllocatorTraits::allocator_type>)> {};
 
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index 58b91c4dd1379..8fe1ec935da7b 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -74,8 +74,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
       __conditional_t<__libcpp_is_trivially_relocatable<_T1>::value && __libcpp_is_trivially_relocatable<_T2>::value,
                       pair,
                       void>;
-  using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<_T1>::value && __is_replaceable<_T2>::value, pair, void>;
+  using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable_v<_T1> && __is_replaceable_v<_T2>, pair, void>;
 
   _LIBCPP_HIDE_FROM_ABI pair(pair const&) = default;
   _LIBCPP_HIDE_FROM_ABI pair(pair&&)      = default;
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 535359ff6831f..5a38b95384a4f 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -122,7 +122,7 @@ class _LIBCPP_TEMPLATE_VIS vector {
       vector,
       void>;
   using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+      __conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
                       vector,
                       void>;
 
diff --git a/libcxx/include/array b/libcxx/include/array
index 227c11467ed67..8793c60669594 100644
--- a/libcxx/include/array
+++ b/libcxx/include/array
@@ -176,7 +176,7 @@ template <class _Tp, size_t _Size>
 struct _LIBCPP_TEMPLATE_VIS array {
   using __trivially_relocatable _LIBCPP_NODEBUG =
       __conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, array, void>;
-  using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable<_Tp>::value, array, void>;
+  using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable_v<_Tp>, array, void>;
 
   // types:
   using __self _LIBCPP_NODEBUG = array;
diff --git a/libcxx/include/deque b/libcxx/include/deque
index ef0dda802f2f1..20fdccbbb5b8a 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -532,7 +532,7 @@ public:
       deque,
       void>;
   using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<__map>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+      __conditional_t<__is_replaceable_v<__map> && __container_allocator_is_replaceable<__alloc_traits>::value,
                       deque,
                       void>;
 
diff --git a/libcxx/include/optional b/libcxx/include/optional
index b4d7127c1ea99..9de5452ee3503 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -591,7 +591,7 @@ public:
 
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
-  using __replaceable _LIBCPP_NODEBUG = conditional_t<__is_replaceable<_Tp>::value, optional, void>;
+  using __replaceable _LIBCPP_NODEBUG = conditional_t<__is_replaceable_v<_Tp>, optional, void>;
 
 private:
   // Disable the reference extension using this static assert.
diff --git a/libcxx/include/string b/libcxx/include/string
index c7e33a83fd758..08b3d82e555f8 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -766,7 +766,7 @@ public:
       void>;
 #  endif
   using __replaceable _LIBCPP_NODEBUG =
-      __conditional_t<__is_replaceable<pointer>::value && __container_allocator_is_replaceable<__alloc_traits>::value,
+      __conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
                       basic_string,
                       void>;
 
diff --git a/libcxx/include/variant b/libcxx/include/variant
index 7989274a6a4d7..c26c34aff1d61 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -1177,7 +1177,7 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_DECLSPEC_EMPTY_BASES _LIBCPP_NO_SPECIALIZATIO
 public:
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<_And<__libcpp_is_trivially_relocatable<_Types>...>::value, variant, void>;
-  using __replaceable = conditional_t<_And<__is_replaceable<_Types>...>::value, variant, void>;
+  using __replaceable _LIBCPP_NODEBUG = conditional_t<_And<__is_replaceable<_Types>...>::value, variant, void>;
 
   template <bool _Dummy                                                                               = true,
             enable_if_t<__dependent_type<is_default_constructible<__first_type>, _Dummy>::value, int> = 0>
diff --git a/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp b/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
index c52f5462f8bb3..65a330e63a3d3 100644
--- a/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
+++ b/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
@@ -6,6 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
 #include <__type_traits/is_replaceable.h>
 #include <array>
 #include <deque>
@@ -31,26 +33,44 @@ template <class T>
 struct NonPropagatingStatefulMoveAssignAlloc : std::allocator<T> {
   using propagate_on_container_move_assignment = std::false_type;
   using is_always_equal                        = std::false_type;
+  template <class U>
+  struct rebind {
+    using other = NonPropagatingStatefulMoveAssignAlloc<U>;
+  };
 };
 
 template <class T>
 struct NonPropagatingStatefulCopyAssignAlloc : std::allocator<T> {
   using propagate_on_container_copy_assignment = std::false_type;
   using is_always_equal                        = std::false_type;
+  template <class U>
+  struct rebind {
+    using other = NonPropagatingStatefulCopyAssignAlloc<U>;
+  };
 };
 
 template <class T>
 struct NonPropagatingStatelessMoveAssignAlloc : std::allocator<T> {
   using propagate_on_container_move_assignment = std::false_type;
   using is_always_equal                        = std::true_type;
+  template <class U>
+  struct rebind {
+    using other = NonPropagatingStatelessMoveAssignAlloc<U>;
+  };
 };
 
 template <class T>
 struct NonPropagatingStatelessCopyAssignAlloc : std::allocator<T> {
   using propagate_on_container_copy_assignment = std::false_type;
   using is_always_equal                        = std::true_type;
+  template <class U>
+  struct rebind {
+    using other = NonPropagatingStatelessCopyAssignAlloc<U>;
+  };
 };
 
+static_assert(!std::__is_replaceable<test_allocator<char> >::value, ""); // we use that property below
+
 struct Empty {};
 static_assert(std::__is_replaceable<char>::value, "");
 static_assert(std::__is_replaceable<int>::value, "");

>From ef0afab2e48179eaecb92fc7e34c0e3fa18414bd Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 24 Mar 2025 15:08:55 -0400
Subject: [PATCH 3/3] Undo stray formatting

---
 libcxx/include/tuple   |  4 ++--
 libcxx/include/variant | 16 ++++++++--------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/libcxx/include/tuple b/libcxx/include/tuple
index 76d6bd7811132..1e283f0e10066 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -463,8 +463,8 @@ template <class _Indx, class... _Tp>
 struct __tuple_impl;
 
 template <size_t... _Indx, class... _Tp>
-struct _LIBCPP_DECLSPEC_EMPTY_BASES
-    __tuple_impl<__tuple_indices<_Indx...>, _Tp...> : public __tuple_leaf<_Indx, _Tp>... {
+struct _LIBCPP_DECLSPEC_EMPTY_BASES __tuple_impl<__tuple_indices<_Indx...>, _Tp...>
+    : public __tuple_leaf<_Indx, _Tp>... {
   _LIBCPP_HIDE_FROM_ABI constexpr __tuple_impl() noexcept(
       __all<is_nothrow_default_constructible<_Tp>::value...>::value) {}
 
diff --git a/libcxx/include/variant b/libcxx/include/variant
index c26c34aff1d61..6aeb5e1248173 100644
--- a/libcxx/include/variant
+++ b/libcxx/include/variant
@@ -753,8 +753,8 @@ class _LIBCPP_TEMPLATE_VIS __dtor;
 
 #    define _LIBCPP_VARIANT_DESTRUCTOR(destructible_trait, destructor_definition, destroy)                             \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS                                                                                       \
-      __dtor<__traits<_Types...>, destructible_trait> : public __base<destructible_trait, _Types...> {                 \
+      class _LIBCPP_TEMPLATE_VIS __dtor<__traits<_Types...>, destructible_trait>                                       \
+          : public __base<destructible_trait, _Types...> {                                                             \
         using __base_type _LIBCPP_NODEBUG = __base<destructible_trait, _Types...>;                                     \
         using __index_t _LIBCPP_NODEBUG   = typename __base_type::__index_t;                                           \
                                                                                                                        \
@@ -831,8 +831,8 @@ class _LIBCPP_TEMPLATE_VIS __move_constructor;
 
 #    define _LIBCPP_VARIANT_MOVE_CONSTRUCTOR(move_constructible_trait, move_constructor_definition)                    \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS                                                                                       \
-      __move_constructor<__traits<_Types...>, move_constructible_trait> : public __ctor<__traits<_Types...>> {         \
+      class _LIBCPP_TEMPLATE_VIS __move_constructor<__traits<_Types...>, move_constructible_trait>                     \
+          : public __ctor<__traits<_Types...>> {                                                                       \
         using __base_type _LIBCPP_NODEBUG = __ctor<__traits<_Types...>>;                                               \
                                                                                                                        \
       public:                                                                                                          \
@@ -958,8 +958,8 @@ class _LIBCPP_TEMPLATE_VIS __move_assignment;
 
 #    define _LIBCPP_VARIANT_MOVE_ASSIGNMENT(move_assignable_trait, move_assignment_definition)                         \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS                                                                                       \
-      __move_assignment<__traits<_Types...>, move_assignable_trait> : public __assignment<__traits<_Types...>> {       \
+      class _LIBCPP_TEMPLATE_VIS __move_assignment<__traits<_Types...>, move_assignable_trait>                         \
+          : public __assignment<__traits<_Types...>> {                                                                 \
         using __base_type _LIBCPP_NODEBUG = __assignment<__traits<_Types...>>;                                         \
                                                                                                                        \
       public:                                                                                                          \
@@ -997,8 +997,8 @@ class _LIBCPP_TEMPLATE_VIS __copy_assignment;
 
 #    define _LIBCPP_VARIANT_COPY_ASSIGNMENT(copy_assignable_trait, copy_assignment_definition)                         \
       template <class... _Types>                                                                                       \
-      class _LIBCPP_TEMPLATE_VIS                                                                                       \
-      __copy_assignment<__traits<_Types...>, copy_assignable_trait> : public __move_assignment<__traits<_Types...>> {  \
+      class _LIBCPP_TEMPLATE_VIS __copy_assignment<__traits<_Types...>, copy_assignable_trait>                         \
+          : public __move_assignment<__traits<_Types...>> {                                                            \
         using __base_type _LIBCPP_NODEBUG = __move_assignment<__traits<_Types...>>;                                    \
                                                                                                                        \
       public:                                                                                                          \



More information about the libcxx-commits mailing list