mpc
Haskell-like feature supports in C++
copyable_box.hpp
Go to the documentation of this file.
1
2#pragma once
3#include <memory>
4#include <optional>
6
7namespace mpc {
8 // copyable_box
9 // https://github.com/llvm/llvm-project/blob/main/libcxx/include/__ranges/copyable_box.h
10
11 // copyable_box allows turning a type that is copy-constructible (but maybe not copy-assignable)
12 // into a type that is both copy-constructible and copy-assignable. It does that by introducing an
13 // empty state and basically doing destroy-then-copy-construct in the assignment operator. The
14 // empty state is necessary to handle the case where the copy construction fails after destroying
15 // the object.
16 //
17 // In some cases, we can completely avoid the use of an empty state; we provide a specialization
18 // of copyable_box that does this, see below for the details.
19
21 template <class T>
22 concept copy_constructible_object = std::copy_constructible<T> and std::is_object_v<T>;
23
24 // Primary template - uses std::optional and introduces an empty state in case assignment fails.
25
27 template <copy_constructible_object T>
29 [[no_unique_address]] std::optional<T> instance_ = std::nullopt;
30
31 public:
32 template <class... Args>
33 requires std::is_constructible_v<T, Args...>
34 constexpr explicit copyable_box(std::in_place_t, Args&&... args) //
35 noexcept(std::is_nothrow_constructible_v<T, Args...>)
36 : instance_(std::in_place, std::forward<Args>(args)...) {}
37
38 constexpr copyable_box() //
39 noexcept(std::is_nothrow_default_constructible_v<T>) //
40 requires std::default_initializable<T> : instance_(std::in_place) {}
41
42 constexpr copyable_box(const copyable_box&) = default;
43 constexpr copyable_box(copyable_box&&) = default;
44
45 constexpr copyable_box&
46 operator=(const copyable_box& other) noexcept(std::is_nothrow_copy_constructible_v<T>) {
47 if (this != std::addressof(other)) {
48 if (other.has_value())
49 instance_.emplace(*other);
50 else
51 instance_.reset();
52 }
53 return *this;
54 }
55
56 constexpr copyable_box& operator=(copyable_box&&) requires std::movable<T>
57 = default;
58
59 constexpr copyable_box&
60 operator=(copyable_box&& other) noexcept(std::is_nothrow_move_constructible_v<T>) {
61 if (this != std::addressof(other)) {
62 if (other.has_value())
63 instance_.emplace(std::move(*other));
64 else
65 instance_.reset();
66 }
67 return *this;
68 }
69
70 constexpr const T& operator*() const& noexcept {
71 return *instance_;
72 }
73 constexpr const T&& operator*() const&& noexcept {
74 return std::move(*instance_);
75 }
76 constexpr T& operator*() & noexcept {
77 return *instance_;
78 }
79 constexpr T&& operator*() && noexcept {
80 return std::move(*instance_);
81 }
82
83 constexpr const T* operator->() const noexcept {
84 return instance_.operator->();
85 }
86 constexpr T* operator->() noexcept {
87 return instance_.operator->();
88 }
89
90 constexpr bool has_value() const noexcept {
91 return instance_.has_value();
92 }
93 };
94
95 // This partial specialization implements an optimization for when we know we don't need to store
96 // an empty state to represent failure to perform an assignment. For copy-assignment, this
97 // happens:
98 //
99 // 1. If the type is copyable (which includes copy-assignment), we can use the type's own
100 // assignment operator
101 // directly and avoid using std::optional.
102 // 2. If the type is not copyable, but it is nothrow-copy-constructible, then we can implement
103 // assignment as
104 // destroy-and-then-construct and we know it will never fail, so we don't need an empty state.
105 //
106 // The exact same reasoning can be applied for move-assignment, with copyable replaced by movable
107 // and nothrow-copy-constructible replaced by nothrow-move-constructible. This specialization is
108 // enabled whenever we can apply any of these optimizations for both the copy assignment and the
109 // move assignment operator.
110
112 template <class T>
113 concept doesnt_need_empty_state_for_copy =
114 std::copyable<T> or std::is_nothrow_copy_constructible_v<T>;
115
116 template <class T>
117 concept doesnt_need_empty_state_for_move =
118 std::movable<T> or std::is_nothrow_move_constructible_v<T>;
120
122 template <copy_constructible_object T>
123 requires doesnt_need_empty_state_for_copy<T> and doesnt_need_empty_state_for_move<T>
124 class copyable_box<T> {
125 [[no_unique_address]] T instance_{};
126
127 public:
128 template <class... Args>
129 requires std::is_constructible_v<T, Args...>
130 constexpr explicit copyable_box(std::in_place_t, Args&&... args) //
131 noexcept(std::is_nothrow_constructible_v<T, Args...>)
132 : instance_(std::forward<Args>(args)...) {}
133
134 constexpr copyable_box() //
135 noexcept(std::is_nothrow_default_constructible_v<T>) //
136 requires std::default_initializable<T> : instance_{} {}
137
138 constexpr copyable_box(const copyable_box&) = default;
139 constexpr copyable_box(copyable_box&&) = default;
140
141 // Implementation of assignment operators in case we perform optimization (1)
142 constexpr copyable_box& operator=(const copyable_box&) requires std::copyable<T>
143 = default;
144 constexpr copyable_box& operator=(copyable_box&&) requires std::movable<T>
145 = default;
146
147 // Implementation of assignment operators in case we perform optimization (2)
148 constexpr copyable_box& operator=(const copyable_box& other) noexcept {
149 static_assert(std::is_nothrow_copy_constructible_v<T>);
150 if (this != std::addressof(other)) {
151 std::destroy_at(std::addressof(instance_));
152 std::construct_at(std::addressof(instance_), other.instance_);
153 }
154 return *this;
155 }
156
157 constexpr copyable_box& operator=(copyable_box&& other) noexcept {
158 static_assert(std::is_nothrow_move_constructible_v<T>);
159 if (this != std::addressof(other)) {
160 std::destroy_at(std::addressof(instance_));
161 std::construct_at(std::addressof(instance_), std::move(other.instance_));
162 }
163 return *this;
164 }
165
166 constexpr const T& operator*() const& noexcept {
167 return instance_;
168 }
169 constexpr const T&& operator*() const&& noexcept {
170 return std::move(instance_);
171 }
172 constexpr T& operator*() & noexcept {
173 return instance_;
174 }
175 constexpr T&& operator*() && noexcept {
176 return std::move(instance_);
177 }
178
179 constexpr const T* operator->() const noexcept {
180 return std::addressof(instance_);
181 }
182 constexpr T* operator->() noexcept {
183 return std::addressof(instance_);
184 }
185
186 constexpr bool has_value() const noexcept {
187 return true;
188 }
189 };
190} // namespace mpc
Makes copy_constructible but not copy_assignable types copy_assignable.
Definition: copyable_box.hpp:28
Requires copy_constructible and is_object.
Definition: copyable_box.hpp:22