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

via libcxx-commits libcxx-commits at lists.llvm.org
Thu May 8 13:35:04 PDT 2025


Author: Louis Dionne
Date: 2025-05-08T16:35:00-04:00
New Revision: 45d493b680e3e79e4e9c19d665df83823c52a73a

URL: https://github.com/llvm/llvm-project/commit/45d493b680e3e79e4e9c19d665df83823c52a73a
DIFF: https://github.com/llvm/llvm-project/commit/45d493b680e3e79e4e9c19d665df83823c52a73a.diff

LOG: [libc++] Add the __is_replaceable type trait (#132408)

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.

This is effectively adding a library emulation of P2786R12's
is_replaceable trait, similarly to what we do for trivial relocation.
Eventually, we can replace this library emulation by the real
compiler-backed trait.

This is building towards #129328.

Added: 
    libcxx/include/__type_traits/is_replaceable.h
    libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp

Modified: 
    libcxx/include/CMakeLists.txt
    libcxx/include/__exception/exception_ptr.h
    libcxx/include/__expected/expected.h
    libcxx/include/__locale
    libcxx/include/__memory/shared_ptr.h
    libcxx/include/__memory/unique_ptr.h
    libcxx/include/__split_buffer
    libcxx/include/__utility/pair.h
    libcxx/include/__vector/vector.h
    libcxx/include/array
    libcxx/include/deque
    libcxx/include/module.modulemap.in
    libcxx/include/optional
    libcxx/include/string
    libcxx/include/tuple
    libcxx/include/variant

Removed: 
    


################################################################################
diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 0277382cc84dd..8cb05857ea8d7 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -845,6 +845,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 dac5b00b57fe3..b1fe9a1299ec7 100644
--- a/libcxx/include/__exception/exception_ptr.h
+++ b/libcxx/include/__exception/exception_ptr.h
@@ -65,8 +65,10 @@ 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.
+  // It is also replaceable because assignment has normal value semantics.
   using __trivially_relocatable _LIBCPP_NODEBUG = exception_ptr;
+  using __replaceable _LIBCPP_NODEBUG           = exception_ptr;
 
   _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 6b3d335f2151c..0f446b870723b 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -30,6 +30,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>
@@ -471,6 +472,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_v<_Tp> && __is_replaceable_v<_Err>, expected, void>;
 
   template <class _Up>
   using rebind = expected<_Up, error_type>;

diff  --git a/libcxx/include/__locale b/libcxx/include/__locale
index 3c290e27c062b..92e45e2531c2a 100644
--- a/libcxx/include/__locale
+++ b/libcxx/include/__locale
@@ -56,8 +56,10 @@ _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. Like shared_ptr, it is also replaceable.
   using __trivially_relocatable _LIBCPP_NODEBUG = locale;
+  using __replaceable _LIBCPP_NODEBUG           = locale;
 
   // types:
   class _LIBCPP_EXPORTED_FROM_ABI facet;

diff  --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 6d3da9778f5f3..f1b2e2dbfc0cd 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -316,8 +316,10 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
 #endif
 
   // 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.
+  // any bookkeeping, so it's always trivially relocatable. It is also replaceable because assignment just rebinds the
+  // shared_ptr to manage a 
diff erent object.
   using __trivially_relocatable _LIBCPP_NODEBUG = shared_ptr;
+  using __replaceable _LIBCPP_NODEBUG           = shared_ptr;
 
 private:
   element_type* __ptr_;
@@ -1211,8 +1213,9 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI weak_ptr {
 #endif
 
   // 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.
+  // any bookkeeping, so it's always trivially relocatable. It's also replaceable for the same reason.
   using __trivially_relocatable _LIBCPP_NODEBUG = weak_ptr;
+  using __replaceable _LIBCPP_NODEBUG           = weak_ptr;
 
 private:
   element_type* __ptr_;

diff  --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index fa02926bd5e5a..3e8d3cd6a4097 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 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_v<pointer> && __is_replaceable_v<deleter_type>, unique_ptr, void>;
 
 private:
   _LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
@@ -410,6 +413,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI 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_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 721d4d497f2a5..21e58f4abc6b3 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_v<pointer> && __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..e1d17c099cd3a
--- /dev/null
+++ b/libcxx/include/__type_traits/is_replaceable.h
@@ -0,0 +1,61 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, with `x` and `y` being 
diff erent objects, `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 {};
+
+template <class _Tp>
+inline const bool __is_replaceable_v = __is_replaceable<_Tp>::value;
+
+// Determines whether an allocator member of a container is replaceable.
+//
+// First, we require the allocator type to be considered replaceable. If not, then something fishy might be
+// happening. Assuming the allocator type is replaceable, we conclude replaceability of the allocator as a
+// member of the container if the allocator always compares equal (in which case propagation doesn't matter),
+// or if the allocator always propagates on assignment, which is required in order for move construction and
+// assignment to be equivalent.
+template <class _AllocatorTraits>
+struct __container_allocator_is_replaceable
+    : integral_constant<bool,
+                        __is_replaceable_v<typename _AllocatorTraits::allocator_type> &&
+                            (_AllocatorTraits::is_always_equal::value ||
+                             (_AllocatorTraits::propagate_on_container_move_assignment::value &&
+                              _AllocatorTraits::propagate_on_container_copy_assignment::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 4296caac1040a..99b0eb955b767 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -32,6 +32,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>
@@ -100,6 +102,7 @@ struct 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_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 af3f7b974cdcf..c6e350a71b855 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 vector {
       __libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
       vector,
       void>;
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable_v<pointer> && __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 ff46838e2e8e2..9643fc1dd9dca 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 array {
   using __trivially_relocatable _LIBCPP_NODEBUG =
       __conditional_t<__libcpp_is_trivially_relocatable<_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 e9846af5e5848..d8645d06ae59e 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_v<__map> && __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.in b/libcxx/include/module.modulemap.in
index 7260c3a5d51f3..b9cb43ebd999a 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -272,6 +272,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 78aa34727c147..2153efb2ab899 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_v<_Tp>, optional, void>;
 
 private:
   // Disable the reference extension using this static assert.

diff  --git a/libcxx/include/string b/libcxx/include/string
index 33f2598f85449..4f05e211919f3 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_trivially_constructible.h>
@@ -755,6 +756,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<
@@ -762,6 +766,10 @@ public:
       basic_string,
       void>;
 #  endif
+  using __replaceable _LIBCPP_NODEBUG =
+      __conditional_t<__is_replaceable_v<pointer> && __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 f6062891823d6..8dd62ae624f5e 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_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 74a464d27ead4..23d876f9a60af 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>
@@ -850,8 +851,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);
 
@@ -887,8 +887,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,
@@ -1173,6 +1174,7 @@ class _LIBCPP_DECLSPEC_EMPTY_BASES _LIBCPP_NO_SPECIALIZATIONS variant
 public:
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<_And<__libcpp_is_trivially_relocatable<_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>
@@ -1576,8 +1578,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..7735538cccae4
--- /dev/null
+++ b/libcxx/test/libcxx/type_traits/is_replaceable.compile.pass.cpp
@@ -0,0 +1,313 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
+#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 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>;
+  };
+};
+
+template <class T>
+struct NonReplaceableStatelessAlloc : std::allocator<T> {
+  // Ensure that we don't consider an allocator that is a member of a container to be
+  // replaceable if it's not replaceable, even if it always compares equal and always propagates.
+  using propagate_on_container_move_assignment = std::true_type;
+  using propagate_on_container_copy_assignment = std::true_type;
+  using is_always_equal                        = std::true_type;
+  NonReplaceableStatelessAlloc()               = default;
+  NonReplaceableStatelessAlloc(NonReplaceableStatelessAlloc const&) {}
+  NonReplaceableStatelessAlloc(NonReplaceableStatelessAlloc&&) = default;
+  template <class U>
+  struct rebind {
+    using other = NonReplaceableStatelessAlloc<U>;
+  };
+};
+static_assert(!std::__is_replaceable<NonReplaceableStatelessAlloc<int> >::value, "");
+
+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, "");
+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<char, std::char_traits<char>, NonReplaceableStatelessAlloc<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, NonReplaceableStatelessAlloc<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
+#ifndef _LIBCPP_ABI_MICROSOFT
+static_assert(std::__is_replaceable<std::exception_ptr>::value, "");
+#endif
+
+// 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, NonReplaceableStatelessAlloc<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


        


More information about the libcxx-commits mailing list