]> git.tdb.fi Git - ext/openal.git/blob - common/aloptional.h
Tweak some types to work around an MSVC compile error
[ext/openal.git] / common / aloptional.h
1 #ifndef AL_OPTIONAL_H
2 #define AL_OPTIONAL_H
3
4 #include <initializer_list>
5 #include <type_traits>
6 #include <utility>
7
8 #include "almalloc.h"
9
10 namespace al {
11
12 struct nullopt_t { };
13 struct in_place_t { };
14
15 constexpr nullopt_t nullopt{};
16 constexpr in_place_t in_place{};
17
18 #define NOEXCEPT_AS(...)  noexcept(noexcept(__VA_ARGS__))
19
20 namespace detail_ {
21 /* Base storage struct for an optional. Defines a trivial destructor, for types
22  * that can be trivially destructed.
23  */
24 template<typename T, bool = std::is_trivially_destructible<T>::value>
25 struct optstore_base {
26     bool mHasValue{false};
27     union {
28         char mDummy{};
29         T mValue;
30     };
31
32     constexpr optstore_base() noexcept { }
33     template<typename ...Args>
34     constexpr explicit optstore_base(in_place_t, Args&& ...args)
35         noexcept(std::is_nothrow_constructible<T, Args...>::value)
36         : mHasValue{true}, mValue{std::forward<Args>(args)...}
37     { }
38     ~optstore_base() = default;
39 };
40
41 /* Specialization needing a non-trivial destructor. */
42 template<typename T>
43 struct optstore_base<T, false> {
44     bool mHasValue{false};
45     union {
46         char mDummy{};
47         T mValue;
48     };
49
50     constexpr optstore_base() noexcept { }
51     template<typename ...Args>
52     constexpr explicit optstore_base(in_place_t, Args&& ...args)
53         noexcept(std::is_nothrow_constructible<T, Args...>::value)
54         : mHasValue{true}, mValue{std::forward<Args>(args)...}
55     { }
56     ~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); }
57 };
58
59 /* Next level of storage, which defines helpers to construct and destruct the
60  * stored object.
61  */
62 template<typename T>
63 struct optstore_helper : public optstore_base<T> {
64     using optstore_base<T>::optstore_base;
65
66     template<typename... Args>
67     constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible<T, Args...>::value)
68     {
69         al::construct_at(std::addressof(this->mValue), std::forward<Args>(args)...);
70         this->mHasValue = true;
71     }
72
73     constexpr void reset() noexcept
74     {
75         if(this->mHasValue)
76             al::destroy_at(std::addressof(this->mValue));
77         this->mHasValue = false;
78     }
79
80     constexpr void assign(const optstore_helper &rhs)
81         noexcept(std::is_nothrow_copy_constructible<T>::value
82             && std::is_nothrow_copy_assignable<T>::value)
83     {
84         if(!rhs.mHasValue)
85             this->reset();
86         else if(this->mHasValue)
87             this->mValue = rhs.mValue;
88         else
89             this->construct(rhs.mValue);
90     }
91
92     constexpr void assign(optstore_helper&& rhs)
93         noexcept(std::is_nothrow_move_constructible<T>::value
94             && std::is_nothrow_move_assignable<T>::value)
95     {
96         if(!rhs.mHasValue)
97             this->reset();
98         else if(this->mHasValue)
99             this->mValue = std::move(rhs.mValue);
100         else
101             this->construct(std::move(rhs.mValue));
102     }
103 };
104
105 /* Define copy and move constructors and assignment operators, which may or may
106  * not be trivial.
107  */
108 template<typename T, bool trivial_copy = std::is_trivially_copy_constructible<T>::value,
109     bool trivial_move = std::is_trivially_move_constructible<T>::value,
110     /* Trivial assignment is dependent on trivial construction+destruction. */
111     bool = trivial_copy && std::is_trivially_copy_assignable<T>::value
112         && std::is_trivially_destructible<T>::value,
113     bool = trivial_move && std::is_trivially_move_assignable<T>::value
114         && std::is_trivially_destructible<T>::value>
115 struct optional_storage;
116
117 /* Some versions of GCC have issues with 'this' in the following noexcept(...)
118  * statements, so this macro is a workaround.
119  */
120 #define _this std::declval<optional_storage*>()
121
122 /* Completely trivial. */
123 template<typename T>
124 struct optional_storage<T, true, true, true, true> : public optstore_helper<T> {
125     using optstore_helper<T>::optstore_helper;
126     constexpr optional_storage() noexcept = default;
127     constexpr optional_storage(const optional_storage&) = default;
128     constexpr optional_storage(optional_storage&&) = default;
129     constexpr optional_storage& operator=(const optional_storage&) = default;
130     constexpr optional_storage& operator=(optional_storage&&) = default;
131 };
132
133 /* Non-trivial move assignment. */
134 template<typename T>
135 struct optional_storage<T, true, true, true, false> : public optstore_helper<T> {
136     using optstore_helper<T>::optstore_helper;
137     constexpr optional_storage() noexcept = default;
138     constexpr optional_storage(const optional_storage&) = default;
139     constexpr optional_storage(optional_storage&&) = default;
140     constexpr optional_storage& operator=(const optional_storage&) = default;
141     constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
142     { this->assign(std::move(rhs)); return *this; }
143 };
144
145 /* Non-trivial move construction. */
146 template<typename T>
147 struct optional_storage<T, true, false, true, false> : public optstore_helper<T> {
148     using optstore_helper<T>::optstore_helper;
149     constexpr optional_storage() noexcept = default;
150     constexpr optional_storage(const optional_storage&) = default;
151     constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
152     { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
153     constexpr optional_storage& operator=(const optional_storage&) = default;
154     constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
155     { this->assign(std::move(rhs)); return *this; }
156 };
157
158 /* Non-trivial copy assignment. */
159 template<typename T>
160 struct optional_storage<T, true, true, false, true> : public optstore_helper<T> {
161     using optstore_helper<T>::optstore_helper;
162     constexpr optional_storage() noexcept = default;
163     constexpr optional_storage(const optional_storage&) = default;
164     constexpr optional_storage(optional_storage&&) = default;
165     constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
166     { this->assign(rhs); return *this; }
167     constexpr optional_storage& operator=(optional_storage&&) = default;
168 };
169
170 /* Non-trivial copy construction. */
171 template<typename T>
172 struct optional_storage<T, false, true, false, true> : public optstore_helper<T> {
173     using optstore_helper<T>::optstore_helper;
174     constexpr optional_storage() noexcept = default;
175     constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
176     { if(rhs.mHasValue) this->construct(rhs.mValue); }
177     constexpr optional_storage(optional_storage&&) = default;
178     constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
179     { this->assign(rhs); return *this; }
180     constexpr optional_storage& operator=(optional_storage&&) = default;
181 };
182
183 /* Non-trivial assignment. */
184 template<typename T>
185 struct optional_storage<T, true, true, false, false> : public optstore_helper<T> {
186     using optstore_helper<T>::optstore_helper;
187     constexpr optional_storage() noexcept = default;
188     constexpr optional_storage(const optional_storage&) = default;
189     constexpr optional_storage(optional_storage&&) = default;
190     constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
191     { this->assign(rhs); return *this; }
192     constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
193     { this->assign(std::move(rhs)); return *this; }
194 };
195
196 /* Non-trivial assignment, non-trivial move construction. */
197 template<typename T>
198 struct optional_storage<T, true, false, false, false> : public optstore_helper<T> {
199     using optstore_helper<T>::optstore_helper;
200     constexpr optional_storage() noexcept = default;
201     constexpr optional_storage(const optional_storage&) = default;
202     constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
203     { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
204     constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
205     { this->assign(rhs); return *this; }
206     constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
207     { this->assign(std::move(rhs)); return *this; }
208 };
209
210 /* Non-trivial assignment, non-trivial copy construction. */
211 template<typename T>
212 struct optional_storage<T, false, true, false, false> : public optstore_helper<T> {
213     using optstore_helper<T>::optstore_helper;
214     constexpr optional_storage() noexcept = default;
215     constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
216     { if(rhs.mHasValue) this->construct(rhs.mValue); }
217     constexpr optional_storage(optional_storage&&) = default;
218     constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
219     { this->assign(rhs); return *this; }
220     constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
221     { this->assign(std::move(rhs)); return *this; }
222 };
223
224 /* Completely non-trivial. */
225 template<typename T>
226 struct optional_storage<T, false, false, false, false> : public optstore_helper<T> {
227     using optstore_helper<T>::optstore_helper;
228     constexpr optional_storage() noexcept = default;
229     constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
230     { if(rhs.mHasValue) this->construct(rhs.mValue); }
231     constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
232     { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
233     constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
234     { this->assign(rhs); return *this; }
235     constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
236     { this->assign(std::move(rhs)); return *this; }
237 };
238
239 #undef _this
240
241 } // namespace detail_
242
243 #define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
244
245 template<typename T>
246 class optional {
247     using storage_t = detail_::optional_storage<T>;
248
249     storage_t mStore{};
250
251 public:
252     using value_type = T;
253
254     constexpr optional() = default;
255     constexpr optional(const optional&) = default;
256     constexpr optional(optional&&) = default;
257     constexpr optional(nullopt_t) noexcept { }
258     template<typename ...Args>
259     constexpr explicit optional(in_place_t, Args&& ...args)
260         NOEXCEPT_AS(storage_t{al::in_place, std::forward<Args>(args)...})
261         : mStore{al::in_place, std::forward<Args>(args)...}
262     { }
263     template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
264         && !std::is_same<std::decay_t<U>, al::in_place_t>::value
265         && !std::is_same<std::decay_t<U>, optional<T>>::value
266         && std::is_convertible<U&&, T>::value)>
267     constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
268         : mStore{al::in_place, std::forward<U>(rhs)}
269     { }
270     template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
271         && !std::is_same<std::decay_t<U>, al::in_place_t>::value
272         && !std::is_same<std::decay_t<U>, optional<T>>::value
273         && !std::is_convertible<U&&, T>::value)>
274     constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
275         : mStore{al::in_place, std::forward<U>(rhs)}
276     { }
277     ~optional() = default;
278
279     constexpr optional& operator=(const optional&) = default;
280     constexpr optional& operator=(optional&&) = default;
281     constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; }
282     template<typename U=T>
283     constexpr std::enable_if_t<std::is_constructible<T, U>::value
284         && std::is_assignable<T&, U>::value
285         && !std::is_same<std::decay_t<U>, optional<T>>::value
286         && (!std::is_same<std::decay_t<U>, T>::value || !std::is_scalar<U>::value),
287     optional&> operator=(U&& rhs)
288     {
289         if(mStore.mHasValue)
290             mStore.mValue = std::forward<U>(rhs);
291         else
292             mStore.construct(std::forward<U>(rhs));
293         return *this;
294     }
295
296     constexpr const T* operator->() const { return std::addressof(mStore.mValue); }
297     constexpr T* operator->() { return std::addressof(mStore.mValue); }
298     constexpr const T& operator*() const& { return mStore.mValue; }
299     constexpr T& operator*() & { return mStore.mValue; }
300     constexpr const T&& operator*() const&& { return std::move(mStore.mValue); }
301     constexpr T&& operator*() && { return std::move(mStore.mValue); }
302
303     constexpr explicit operator bool() const noexcept { return mStore.mHasValue; }
304     constexpr bool has_value() const noexcept { return mStore.mHasValue; }
305
306     constexpr T& value() & { return mStore.mValue; }
307     constexpr const T& value() const& { return mStore.mValue; }
308     constexpr T&& value() && { return std::move(mStore.mValue); }
309     constexpr const T&& value() const&& { return std::move(mStore.mValue); }
310
311     template<typename U>
312     constexpr T value_or(U&& defval) const&
313     { return bool(*this) ? **this : static_cast<T>(std::forward<U>(defval)); }
314     template<typename U>
315     constexpr T value_or(U&& defval) &&
316     { return bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(defval)); }
317
318     template<typename ...Args>
319     constexpr T& emplace(Args&& ...args)
320     {
321         mStore.reset();
322         mStore.construct(std::forward<Args>(args)...);
323         return mStore.mValue;
324     }
325     template<typename U, typename ...Args>
326     constexpr std::enable_if_t<std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
327     T&> emplace(std::initializer_list<U> il, Args&& ...args)
328     {
329         mStore.reset();
330         mStore.construct(il, std::forward<Args>(args)...);
331         return mStore.mValue;
332     }
333
334     constexpr void reset() noexcept { mStore.reset(); }
335 };
336
337 template<typename T>
338 constexpr optional<std::decay_t<T>> make_optional(T&& arg)
339 { return optional<std::decay_t<T>>{in_place, std::forward<T>(arg)}; }
340
341 template<typename T, typename... Args>
342 constexpr optional<T> make_optional(Args&& ...args)
343 { return optional<T>{in_place, std::forward<Args>(args)...}; }
344
345 template<typename T, typename U, typename... Args>
346 constexpr optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
347 { return optional<T>{in_place, il, std::forward<Args>(args)...}; }
348
349 #undef REQUIRES
350 #undef NOEXCEPT_AS
351 } // namespace al
352
353 #endif /* AL_OPTIONAL_H */