AUI Framework  master
Cross-platform module-based framework for developing C++20 desktop applications
AOptional.h
1/*
2 * AUI Framework - Declarative UI toolkit for modern C++20
3 * Copyright (C) 2020-2024 Alex2772 and Contributors
4 *
5 * SPDX-License-Identifier: MPL-2.0
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 */
11
12#pragma once
13
14#include <cstdint>
15#include <cassert>
16#include <utility>
17#include <optional>
18#include <stdexcept>
19#include "AUI/Traits/concepts.h"
20#include <AUI/Core.h>
21
22
23namespace aui::impl::optional {
24 API_AUI_CORE void throwException(const char* message);
25}
26
31template<typename T>
32class AOptional {
33public:
34 constexpr AOptional() noexcept = default;
35 constexpr AOptional(std::nullopt_t) noexcept {}
36
37
38 template<typename U = T,
39 std::enable_if_t<std::is_constructible_v<T, U> && std::is_convertible_v<U&&, T>, bool> = true>
40 constexpr AOptional(U&& rhs) noexcept: mInitialized(true) {
41 new (ptrUnsafe()) T(std::forward<U>(rhs));
42 }
43
44 template<typename U = T,
45 std::enable_if_t<std::is_constructible_v<T, U> && !std::is_convertible_v<U&&, T>, bool> = true>
46 explicit constexpr AOptional(U&& rhs) noexcept: mInitialized(true) {
47 new (ptrUnsafe()) T(std::forward<U>(rhs));
48 }
49
50 constexpr AOptional(const AOptional& rhs) {
51 operator=(rhs);
52 }
53
54 constexpr AOptional(AOptional&& rhs) noexcept {
55 operator=(std::move(rhs));
56 }
57
58 template<typename U>
59 constexpr AOptional(const AOptional<U>& rhs) {
60 operator=(rhs);
61 }
62
63 template<typename U>
64 constexpr AOptional(AOptional<U>&& rhs) noexcept {
65 operator=(std::move(rhs));
66 }
67
68 ~AOptional() {
69 if (mInitialized) ptrUnsafe()->~T();
70 }
71
72
73 [[nodiscard]]
74 bool hasValue() const noexcept {
75 return mInitialized;
76 }
77
78 constexpr explicit operator bool() const noexcept {
79 return hasValue();
80 }
81
82 template<typename... Args>
83 constexpr AOptional<T>& emplace(Args&&... args) {
84 reset();
85 new (ptrUnsafe()) T(std::forward<Args>(args)...);
86 mInitialized = true;
87 return *this;
88 }
89
90 constexpr AOptional<T>& operator=(std::nullopt_t) noexcept {
91 reset();
92 return *this;
93 }
94
95 template<typename U = T, typename std::enable_if_t<std::is_convertible_v<U&&, T>, bool> = true>
96 constexpr AOptional<T>& operator=(U&& rhs) noexcept {
97 reset();
98 new (ptrUnsafe()) T(std::forward<U>(rhs));
99 mInitialized = true;
100 return *this;
101 }
102
103 constexpr AOptional<T>& operator=(const AOptional& rhs) noexcept {
104 if (rhs) {
105 operator=(rhs.value());
106 } else {
107 reset();
108 }
109 return *this;
110 }
111
112 constexpr AOptional<T>& operator=(AOptional&& rhs) noexcept {
113 if (rhs) {
114 operator=(std::move(rhs.value()));
115 rhs.reset();
116 } else {
117 reset();
118 }
119 return *this;
120 }
121
122 template<typename U>
123 constexpr AOptional<T>& operator=(const AOptional<U>& rhs) noexcept {
124 if (rhs) {
125 operator=(rhs.value());
126 } else {
127 reset();
128 }
129 return *this;
130 }
131
132 // we want to move the U value, not the whole optional
133 // NOLINTBEGIN(cppcoreguidelines-rvalue-reference-param-not-moved)
134 template<typename U>
135 constexpr AOptional<T>& operator=(AOptional<U>&& rhs) noexcept {
136 if (rhs) {
137 operator=(std::move(rhs.value()));
138 rhs.reset();
139 return *this;
140 } else {
141 reset();
142 }
143 return *this;
144 }
145 //NOLINTEND(cppcoreguidelines-rvalue-reference-param-not-moved)
146
147 template<typename U = T>
148 constexpr AOptional<T>& operator=(T&& rhs) noexcept {
149 reset();
150 new (ptrUnsafe()) T(std::move(rhs));
151 mInitialized = true;
152 return *this;
153 }
154
155 [[nodiscard]]
156 T& value() noexcept {
157 AUI_ASSERTX(mInitialized, "optional is empty");
158 return reinterpret_cast<T&>(mStorage);
159 }
160
161 [[nodiscard]]
162 const T& value() const noexcept {
163 AUI_ASSERTX(mInitialized, "optional is empty");
164 return reinterpret_cast<const T&>(mStorage);
165 }
166
167 [[nodiscard]]
168 T* ptr() noexcept {
169 return &value();
170 }
171
172 [[nodiscard]]
173 const T* ptr() const noexcept {
174 return &value();
175 }
176
177 [[nodiscard]]
178 T* operator->() noexcept {
179 return ptr();
180 }
181
182 [[nodiscard]]
183 const T* operator->() const noexcept {
184 return ptr();
185 }
186
187 [[nodiscard]]
188 T& operator*() noexcept {
189 return value();
190 }
191
192 [[nodiscard]]
193 const T& operator*() const noexcept {
194 return value();
195 }
196
197 void reset() noexcept {
198 if (mInitialized) {
199 ptrUnsafe()->~T();
200 mInitialized = false;
201 }
202 }
203
207 T& valueOrException(const char* message = "empty optional") {
208 if (mInitialized) {
209 return value();
210 }
211 aui::impl::optional::throwException(message);
212 throw std::logic_error("should not have reached here"); // silence "not all control paths return a value" warning
213 }
214
218 const T& valueOrException(const char* message = "empty optional") const {
219 if (mInitialized) {
220 return value();
221 }
222 aui::impl::optional::throwException(message);
223 throw std::logic_error("should not have reached here"); // silence "not all control paths return a value" warning
224 }
225
232 template<typename F>
233 T valueOr(F&& alternative) const {
234 if (mInitialized) {
235 return value();
236 }
237 constexpr bool isSame = std::is_constructible_v<T, F>;
238 constexpr bool isInvocable = std::is_invocable_v<F>;
239
240 static_assert(isSame || isInvocable, "F is neither same as T nor invokable returning T nor invokable throwing a exception");
241
242 if constexpr (isSame) {
243 return std::forward<F>(alternative);
244 } else if constexpr(isInvocable) {
245 if constexpr (std::is_same_v<std::invoke_result_t<F>, void>) {
246 alternative();
247 AUI_ASSERT_NO_CONDITION("should not have reached here");
248 throw std::runtime_error("should not have reached here"); // stub exception
249 } else {
250 return alternative();
251 }
252 }
253 }
254
255 template<typename U>
256 [[nodiscard]]
257 bool operator==(const AOptional<U>& rhs) const noexcept {
258 return (mInitialized == rhs.mInitialized) && (!mInitialized || value() == rhs.value());
259 }
260
261 template<typename U>
262 [[nodiscard]]
263 bool operator==(const U& rhs) const noexcept {
264 return mInitialized && value() == rhs;
265 }
266
267 [[nodiscard]]
268 bool operator==(const std::nullopt_t& rhs) const noexcept {
269 return !mInitialized;
270 }
271
277 template<aui::invocable<const T&> Mapper>
278 [[nodiscard]]
279 auto map(Mapper&& mapper) -> AOptional<decltype(std::invoke(std::forward<Mapper>(mapper), value()))> const {
280 if (hasValue()) {
281 return std::invoke(std::forward<Mapper>(mapper), value());
282 }
283 return std::nullopt;
284 }
285
286private:
287 std::aligned_storage_t<sizeof(T), alignof(T)> mStorage;
288 bool mInitialized = false;
289
290 [[nodiscard]]
291 T* ptrUnsafe() noexcept {
292 return &valueUnsafe();
293 }
294
295 [[nodiscard]]
296 T& valueUnsafe() noexcept {
297 return reinterpret_cast<T&>(mStorage);
298 }
299
300};
Utility wrapper implementing the stack-allocated (fast) optional idiom.
Definition: AOptional.h:32
const T & valueOrException(const char *message="empty optional") const
value or exception
Definition: AOptional.h:218
T valueOr(F &&alternative) const
value or alternative (either value or callback)
Definition: AOptional.h:233
T & valueOrException(const char *message="empty optional")
value or exception
Definition: AOptional.h:207
auto map(Mapper &&mapper) -> AOptional< decltype(std::invoke(std::forward< Mapper >(mapper), value()))> const
If a value is present, apply the provided mapper function to it.
Definition: AOptional.h:279
API_AUI_CORE const ACommandLineArgs & args() noexcept
Definition: OSAndroid.cpp:29
#define AUI_ASSERT_NO_CONDITION(what)
Always triggers assertion fail.
Definition: Assert.h:94
#define AUI_ASSERTX(condition, what)
Asserts that the passed condition evaluates to true. Adds extra message string.
Definition: Assert.h:74