YOrch 1.0.0
Loading...
Searching...
No Matches
maybe_storage.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <memory>
5#include <new>
6#include <type_traits>
7#include <utility>
8
9#include "../../assert.hpp"
10
11namespace yorch::detail {
12
22template <typename T>
24 static_assert(!std::is_reference_v<T>,
25 "yorch::detail::maybe_storage<T> does not support reference types");
26 static_assert(!std::is_void_v<T>,
27 "yorch::detail::maybe_storage<T> requires a non-void type");
28
29public:
31
40
41 constexpr maybe_storage(const maybe_storage&)
42 requires (!std::copy_constructible<T>)
43 = delete;
44
46 // NOLINTNEXTLINE(cppcoreguidelines-noexcept-move-operations, performance-noexcept-move-constructor)
47 noexcept(std::is_nothrow_move_constructible_v<T>)
48 requires std::move_constructible<T>
49 {
50 if (other.has_value()) {
51 emplace(std::move(other.get()));
52 }
53 }
54
56 requires (!std::move_constructible<T>)
57 = delete;
58
61 requires std::copy_constructible<T>
62 {
63 if (this == &other) {
64 return *this;
65 }
66
67 assign_from(other);
68 return *this;
69 }
70
72 requires (!std::copy_constructible<T>)
73 = delete;
74
76 // NOLINTNEXTLINE(cppcoreguidelines-noexcept-move-operations, performance-noexcept-move-constructor)
78 requires std::move_constructible<T>
79 {
80 if (this == &other) {
81 return *this;
82 }
83
84 assign_from(std::move(other));
85 return *this;
86 }
87
89 requires (!std::move_constructible<T>)
90 = delete;
91
93 destroy();
94 }
95
96 template <typename... Args>
97 constexpr T& emplace(Args&&... args)
98 noexcept(std::is_nothrow_constructible_v<T, Args&&...>)
99 {
100 YORCH_ASSERT(!engaged_ &&
101 "yorch::detail::maybe_storage<T>::emplace() called on a live value");
102
103 auto* object = std::construct_at(ptr(), std::forward<Args>(args)...);
104 engaged_ = true;
105 return *object;
106 }
107
108 constexpr void destroy() noexcept {
109 if (!engaged_) {
110 return;
111 }
112
113 std::destroy_at(ptr());
114 engaged_ = false;
115 }
116
117 [[nodiscard]] constexpr bool has_value() const noexcept {
118 return engaged_;
119 }
120
121 [[nodiscard]] constexpr T& get() & noexcept {
122 YORCH_ASSERT(engaged_ &&
123 "yorch::detail::maybe_storage<T>::get() called on an empty value");
124 return *ptr();
125 }
126
127 [[nodiscard]] constexpr const T& get() const& noexcept {
128 YORCH_ASSERT(engaged_ &&
129 "yorch::detail::maybe_storage<T>::get() called on an empty value");
130 return *ptr();
131 }
132
133 [[nodiscard]] constexpr T&& get() && noexcept {
134 YORCH_ASSERT(engaged_ &&
135 "yorch::detail::maybe_storage<T>::get() called on an empty value");
136 return std::move(*ptr());
137 }
138
139 [[nodiscard]] constexpr T* raw_ptr() noexcept {
140 return ptr();
141 }
142
143 [[nodiscard]] constexpr const T* raw_ptr() const noexcept {
144 return ptr();
145 }
146
147 [[nodiscard]] constexpr bool* engaged_ptr() noexcept {
148 return &engaged_;
149 }
150
151 [[nodiscard]] constexpr const bool* engaged_ptr() const noexcept {
152 return &engaged_;
153 }
154
155private:
156 template <typename Storage>
157 using storage_value_t = decltype(std::declval<Storage>().get());
158
159 template <typename Storage>
160 static constexpr bool assign_from_nothrow_v =
161 std::is_nothrow_destructible_v<T> &&
162 std::is_nothrow_constructible_v<T, storage_value_t<Storage>> &&
163 (!std::is_assignable_v<T&, storage_value_t<Storage>> ||
164 std::is_nothrow_assignable_v<T&, storage_value_t<Storage>>);
165
166 template <typename Storage>
167 constexpr void assign_from(Storage&& other) noexcept(assign_from_nothrow_v<Storage&&>) {
168 if (other.has_value()) {
169 if (engaged_) {
170 if constexpr (std::is_assignable_v<T&, decltype(std::forward<Storage>(other).get())>) {
171 get() = std::forward<Storage>(other).get();
172 } else {
173 destroy();
174 emplace(std::forward<Storage>(other).get());
175 }
176 } else {
177 emplace(std::forward<Storage>(other).get());
178 }
179 } else {
180 destroy();
181 }
182 }
183
184 [[nodiscard]] constexpr T* ptr() noexcept {
185 return std::launder(reinterpret_cast<T*>(storage_));
186 }
187
188 [[nodiscard]] constexpr const T* ptr() const noexcept {
189 return std::launder(reinterpret_cast<const T*>(storage_));
190 }
191
192 // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
193 alignas(T) std::byte storage_[sizeof(T)] {};
194 bool engaged_ = false;
195};
196
197} // namespace yorch::detail
#define YORCH_ASSERT(condition)
Definition assert.hpp:10
constexpr bool is_adapter_descriptor_v
Definition adapters.hpp:63
Manual-lifetime storage for an optional in-place T.
constexpr T & get() &noexcept
constexpr maybe_storage(maybe_storage &&other) noexcept(std::is_nothrow_move_constructible_v< T >)
constexpr void destroy() noexcept
constexpr T & emplace(Args &&... args) noexcept(std::is_nothrow_constructible_v< T, Args &&... >)
constexpr T * raw_ptr() noexcept
constexpr maybe_storage & operator=(maybe_storage &&other) noexcept(assign_from_nothrow_v< maybe_storage && >)
constexpr bool has_value() const noexcept
constexpr maybe_storage & operator=(maybe_storage &&)=delete
constexpr maybe_storage() noexcept=default
constexpr maybe_storage(const maybe_storage &)=delete
constexpr const T * raw_ptr() const noexcept
constexpr const T & get() const &noexcept
constexpr bool * engaged_ptr() noexcept
constexpr T && get() &&noexcept
constexpr maybe_storage(maybe_storage &&)=delete
constexpr maybe_storage & operator=(const maybe_storage &)=delete
constexpr maybe_storage & operator=(const maybe_storage &other) noexcept(assign_from_nothrow_v< const maybe_storage & >)
constexpr const bool * engaged_ptr() const noexcept