Skip to content

Commit 45d493b

Browse files
authored
[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.
1 parent c82e2f5 commit 45d493b

File tree

18 files changed

+440
-12
lines changed

18 files changed

+440
-12
lines changed

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,7 @@ set(files
845845
__type_traits/is_reference.h
846846
__type_traits/is_reference_wrapper.h
847847
__type_traits/is_referenceable.h
848+
__type_traits/is_replaceable.h
848849
__type_traits/is_same.h
849850
__type_traits/is_scalar.h
850851
__type_traits/is_signed.h

libcxx/include/__exception/exception_ptr.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
6565
friend _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT;
6666

6767
public:
68-
// exception_ptr is basically a COW string.
68+
// exception_ptr is basically a COW string so it is trivially relocatable.
69+
// It is also replaceable because assignment has normal value semantics.
6970
using __trivially_relocatable _LIBCPP_NODEBUG = exception_ptr;
71+
using __replaceable _LIBCPP_NODEBUG = exception_ptr;
7072

7173
_LIBCPP_HIDE_FROM_ABI exception_ptr() _NOEXCEPT : __ptr_() {}
7274
_LIBCPP_HIDE_FROM_ABI exception_ptr(nullptr_t) _NOEXCEPT : __ptr_() {}

libcxx/include/__expected/expected.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <__type_traits/is_nothrow_assignable.h>
3131
#include <__type_traits/is_nothrow_constructible.h>
3232
#include <__type_traits/is_reference.h>
33+
#include <__type_traits/is_replaceable.h>
3334
#include <__type_traits/is_same.h>
3435
#include <__type_traits/is_swappable.h>
3536
#include <__type_traits/is_trivially_constructible.h>
@@ -471,6 +472,8 @@ class expected : private __expected_base<_Tp, _Err> {
471472
__conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value && __libcpp_is_trivially_relocatable<_Err>::value,
472473
expected,
473474
void>;
475+
using __replaceable _LIBCPP_NODEBUG =
476+
__conditional_t<__is_replaceable_v<_Tp> && __is_replaceable_v<_Err>, expected, void>;
474477

475478
template <class _Up>
476479
using rebind = expected<_Up, error_type>;

libcxx/include/__locale

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ _LIBCPP_HIDE_FROM_ABI const _Facet& use_facet(const locale&);
5656

5757
class _LIBCPP_EXPORTED_FROM_ABI locale {
5858
public:
59-
// locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor.
59+
// locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor,
60+
// so it is trivially relocatable. Like shared_ptr, it is also replaceable.
6061
using __trivially_relocatable _LIBCPP_NODEBUG = locale;
62+
using __replaceable _LIBCPP_NODEBUG = locale;
6163

6264
// types:
6365
class _LIBCPP_EXPORTED_FROM_ABI facet;

libcxx/include/__memory/shared_ptr.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,10 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
316316
#endif
317317

318318
// A shared_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
319-
// any bookkeeping, so it's always trivially relocatable.
319+
// any bookkeeping, so it's always trivially relocatable. It is also replaceable because assignment just rebinds the
320+
// shared_ptr to manage a different object.
320321
using __trivially_relocatable _LIBCPP_NODEBUG = shared_ptr;
322+
using __replaceable _LIBCPP_NODEBUG = shared_ptr;
321323

322324
private:
323325
element_type* __ptr_;
@@ -1211,8 +1213,9 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI weak_ptr {
12111213
#endif
12121214

12131215
// A weak_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
1214-
// any bookkeeping, so it's always trivially relocatable.
1216+
// any bookkeeping, so it's always trivially relocatable. It's also replaceable for the same reason.
12151217
using __trivially_relocatable _LIBCPP_NODEBUG = weak_ptr;
1218+
using __replaceable _LIBCPP_NODEBUG = weak_ptr;
12161219

12171220
private:
12181221
element_type* __ptr_;

libcxx/include/__memory/unique_ptr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <__type_traits/is_function.h>
4040
#include <__type_traits/is_pointer.h>
4141
#include <__type_traits/is_reference.h>
42+
#include <__type_traits/is_replaceable.h>
4243
#include <__type_traits/is_same.h>
4344
#include <__type_traits/is_swappable.h>
4445
#include <__type_traits/is_trivially_relocatable.h>
@@ -144,6 +145,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI unique_ptr {
144145
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
145146
unique_ptr,
146147
void>;
148+
using __replaceable _LIBCPP_NODEBUG =
149+
__conditional_t<__is_replaceable_v<pointer> && __is_replaceable_v<deleter_type>, unique_ptr, void>;
147150

148151
private:
149152
_LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
@@ -410,6 +413,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI unique_ptr<_Tp[], _Dp> {
410413
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
411414
unique_ptr,
412415
void>;
416+
using __replaceable _LIBCPP_NODEBUG =
417+
__conditional_t<__is_replaceable_v<pointer> && __is_replaceable_v<deleter_type>, unique_ptr, void>;
413418

414419
private:
415420
template <class _Up, class _OtherDeleter>

libcxx/include/__split_buffer

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <__type_traits/integral_constant.h>
2929
#include <__type_traits/is_nothrow_assignable.h>
3030
#include <__type_traits/is_nothrow_constructible.h>
31+
#include <__type_traits/is_replaceable.h>
3132
#include <__type_traits/is_swappable.h>
3233
#include <__type_traits/is_trivially_destructible.h>
3334
#include <__type_traits/is_trivially_relocatable.h>
@@ -72,6 +73,10 @@ public:
7273
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
7374
__split_buffer,
7475
void>;
76+
using __replaceable _LIBCPP_NODEBUG =
77+
__conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
78+
__split_buffer,
79+
void>;
7580

7681
pointer __first_;
7782
pointer __begin_;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H
10+
#define _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H
11+
12+
#include <__config>
13+
#include <__type_traits/enable_if.h>
14+
#include <__type_traits/integral_constant.h>
15+
#include <__type_traits/is_same.h>
16+
#include <__type_traits/is_trivially_copyable.h>
17+
18+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
19+
# pragma GCC system_header
20+
#endif
21+
22+
_LIBCPP_BEGIN_NAMESPACE_STD
23+
24+
// A type is replaceable if, with `x` and `y` being different objects, `x = std::move(y)` is equivalent to:
25+
//
26+
// std::destroy_at(&x)
27+
// std::construct_at(&x, std::move(y))
28+
//
29+
// This allows turning a move-assignment into a sequence of destroy + move-construct, which
30+
// is often more efficient. This is especially relevant when the move-construct is in fact
31+
// part of a trivial relocation from somewhere else, in which case there is a huge win.
32+
//
33+
// Note that this requires language support in order to be really effective, but we
34+
// currently emulate the base template with something very conservative.
35+
template <class _Tp, class = void>
36+
struct __is_replaceable : is_trivially_copyable<_Tp> {};
37+
38+
template <class _Tp>
39+
struct __is_replaceable<_Tp, __enable_if_t<is_same<_Tp, typename _Tp::__replaceable>::value> > : true_type {};
40+
41+
template <class _Tp>
42+
inline const bool __is_replaceable_v = __is_replaceable<_Tp>::value;
43+
44+
// Determines whether an allocator member of a container is replaceable.
45+
//
46+
// First, we require the allocator type to be considered replaceable. If not, then something fishy might be
47+
// happening. Assuming the allocator type is replaceable, we conclude replaceability of the allocator as a
48+
// member of the container if the allocator always compares equal (in which case propagation doesn't matter),
49+
// or if the allocator always propagates on assignment, which is required in order for move construction and
50+
// assignment to be equivalent.
51+
template <class _AllocatorTraits>
52+
struct __container_allocator_is_replaceable
53+
: integral_constant<bool,
54+
__is_replaceable_v<typename _AllocatorTraits::allocator_type> &&
55+
(_AllocatorTraits::is_always_equal::value ||
56+
(_AllocatorTraits::propagate_on_container_move_assignment::value &&
57+
_AllocatorTraits::propagate_on_container_copy_assignment::value))> {};
58+
59+
_LIBCPP_END_NAMESPACE_STD
60+
61+
#endif // _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H

libcxx/include/__utility/pair.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
#include <__type_traits/is_implicitly_default_constructible.h>
3333
#include <__type_traits/is_nothrow_assignable.h>
3434
#include <__type_traits/is_nothrow_constructible.h>
35+
#include <__type_traits/is_replaceable.h>
36+
#include <__type_traits/is_same.h>
3537
#include <__type_traits/is_swappable.h>
3638
#include <__type_traits/is_trivially_relocatable.h>
3739
#include <__type_traits/nat.h>
@@ -100,6 +102,7 @@ struct pair
100102
__conditional_t<__libcpp_is_trivially_relocatable<_T1>::value && __libcpp_is_trivially_relocatable<_T2>::value,
101103
pair,
102104
void>;
105+
using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable_v<_T1> && __is_replaceable_v<_T2>, pair, void>;
103106

104107
_LIBCPP_HIDE_FROM_ABI pair(pair const&) = default;
105108
_LIBCPP_HIDE_FROM_ABI pair(pair&&) = default;

libcxx/include/__vector/vector.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include <__type_traits/is_nothrow_assignable.h>
5656
#include <__type_traits/is_nothrow_constructible.h>
5757
#include <__type_traits/is_pointer.h>
58+
#include <__type_traits/is_replaceable.h>
5859
#include <__type_traits/is_same.h>
5960
#include <__type_traits/is_trivially_relocatable.h>
6061
#include <__type_traits/type_identity.h>
@@ -120,6 +121,10 @@ class vector {
120121
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
121122
vector,
122123
void>;
124+
using __replaceable _LIBCPP_NODEBUG =
125+
__conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
126+
vector,
127+
void>;
123128

124129
static_assert(__check_valid_allocator<allocator_type>::value, "");
125130
static_assert(is_same<typename allocator_type::value_type, value_type>::value,

0 commit comments

Comments
 (0)