summaryrefslogtreecommitdiffstats
path: root/framework/src/errors.h
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/errors.h')
-rw-r--r--framework/src/errors.h308
1 files changed, 308 insertions, 0 deletions
diff --git a/framework/src/errors.h b/framework/src/errors.h
new file mode 100644
index 00000000..4249cf8d
--- /dev/null
+++ b/framework/src/errors.h
@@ -0,0 +1,308 @@
1/*
2 Copyright (c) 2018 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#pragma once
20
21#include <memory>
22#include <type_traits>
23#include <utility>
24
25#include <QtGlobal>
26
27// A somewhat implementation of the expected monad, proposed here:
28// https://isocpp.org/files/papers/n4015.pdf
29
30// A class used to differentiate errors and values when they are of the same type.
31template <typename Error>
32class Unexpected
33{
34
35 static_assert(!std::is_same<Error, void>::value, "Cannot have an Unexpected void");
36
37public:
38 Unexpected() = delete;
39
40 constexpr explicit Unexpected(const Error &error) : mValue(error) {}
41 constexpr explicit Unexpected(Error &&error) : mValue(std::move(error)) {}
42
43 // For implicit conversions when doing makeUnexpected(other)
44 template <typename Other>
45 constexpr explicit Unexpected(const Unexpected<Other> &error) : mValue(error.value())
46 {
47 }
48 template <typename Other>
49 constexpr explicit Unexpected(Unexpected<Other> &&error) : mValue(std::move(error.value()))
50 {
51 }
52
53 constexpr const Error &value() const &
54 {
55 return mValue;
56 }
57 Error &value() &
58 {
59 return mValue;
60 }
61
62 constexpr const Error &&value() const &&
63 {
64 return std::move(mValue);
65 }
66 Error &&value() &&
67 {
68 return std::move(mValue);
69 }
70
71private:
72 Error mValue;
73};
74
75template <class Error>
76Unexpected<typename std::decay<Error>::type> makeUnexpected(Error &&e)
77{
78 return Unexpected<typename std::decay<Error>::type>(std::forward<Error>(e));
79}
80
81template <typename Error>
82bool operator==(const Unexpected<Error> &lhs, const Unexpected<Error> &rhs)
83{
84 return lhs.value() == rhs.value();
85}
86
87template <typename Error>
88bool operator!=(const Unexpected<Error> &lhs, const Unexpected<Error> &rhs)
89{
90 return lhs.value() != rhs.value();
91}
92
93namespace detail {
94
95namespace tags {
96struct Expected
97{};
98struct Unexpected
99{};
100} // namespace tags
101
102// Write functions here when storage related and when Type != void
103template <typename Error, typename Type>
104struct StorageBase
105{
106protected:
107 // Rule of 5 {{{
108
109 StorageBase(const StorageBase &other) : mIsValue(other.mIsValue)
110 {
111 // This is a constructor, you have to construct object, not assign them
112 // (hence the placement new)
113 //
114 // Here's the problem:
115 //
116 // Object that are part of a union are not initialized (which is
117 // normal). If we replaced the placement new by a line like this:
118 //
119 // ```
120 // mValue = other.mValue;
121 // ```
122 //
123 // If overloaded, this will call `mValue.operator=(other.mValue);`, but
124 // since we're in the constructor, mValue is not initialized. This can
125 // cause big issues if `Type` / `Error` is not trivially (move)
126 // assignable.
127 //
128 // And so, the placement new allows us to call the constructor of
129 // `Type` or `Error` instead of its assignment operator.
130 if (mIsValue) {
131 new (std::addressof(mValue)) Type(other.mValue);
132 } else {
133 new (std::addressof(mError)) Unexpected<Error>(other.mError);
134 }
135 }
136
137 StorageBase(StorageBase &&other) : mIsValue(other.mIsValue)
138 {
139 // If you're thinking WTF, see the comment in the copy constructor above.
140 if (mIsValue) {
141 new (std::addressof(mValue)) Type(std::move(other.mValue));
142 } else {
143 new (std::addressof(mError)) Unexpected<Error>(std::move(other.mError));
144 }
145 }
146
147 constexpr StorageBase &operator=(const StorageBase &other)
148 {
149 mIsValue = other.mIsValue;
150 if (mIsValue) {
151 mValue = other.mValue;
152 } else {
153 mError = other.mError;
154 }
155 return *this;
156 }
157
158 constexpr StorageBase &operator=(StorageBase &&other)
159 {
160 this->~StorageBase();
161 mIsValue = other.mIsValue;
162 if (mIsValue) {
163 mValue = std::move(other.mValue);
164 } else {
165 mError = std::move(other.mError);
166 }
167 return *this;
168 }
169
170 ~StorageBase()
171 {
172 if (mIsValue) {
173 mValue.~Type();
174 } else {
175 mError.~Unexpected<Error>();
176 }
177 }
178
179 // }}}
180
181 template <typename... Args>
182 constexpr StorageBase(tags::Expected, Args &&... args)
183 : mValue(std::forward<Args>(args)...), mIsValue(true)
184 {
185 }
186
187 template <typename... Args>
188 constexpr StorageBase(tags::Unexpected, Args &&... args)
189 : mError(std::forward<Args>(args)...), mIsValue(false)
190 {
191 }
192
193 union
194 {
195 Unexpected<Error> mError;
196 Type mValue;
197 };
198 bool mIsValue;
199};
200
201// Write functions here when storage related and when Type == void
202template <typename Error>
203struct StorageBase<Error, void>
204{
205protected:
206 constexpr StorageBase(tags::Expected) : mIsValue(true) {}
207
208 template <typename... Args>
209 constexpr StorageBase(tags::Unexpected, Args &&... args)
210 : mError(std::forward<Args>(args)...), mIsValue(false)
211 {
212 }
213
214 Unexpected<Error> mError;
215 bool mIsValue;
216};
217
218// Write functions here when storage related, whether Type is void or not
219template <typename Error, typename Type>
220struct Storage : StorageBase<Error, Type>
221{
222protected:
223 // Forward the construction to StorageBase
224 using StorageBase<Error, Type>::StorageBase;
225};
226
227// Write functions here when dev API related and when Type != void
228template <typename Error, typename Type>
229struct ExpectedBase : detail::Storage<Error, Type>
230{
231 constexpr ExpectedBase() : detail::Storage<Error, Type>(detail::tags::Expected{}) {}
232
233 template <typename OtherError>
234 constexpr ExpectedBase(const Unexpected<OtherError> &error)
235 : detail::Storage<Error, Type>(detail::tags::Unexpected{}, error)
236 {
237 }
238 template <typename OtherError>
239 constexpr ExpectedBase(Unexpected<OtherError> &&error)
240 : detail::Storage<Error, Type>(detail::tags::Unexpected{}, std::move(error))
241 {
242 }
243
244 constexpr ExpectedBase(const Type &value)
245 : detail::Storage<Error, Type>(detail::tags::Expected{}, value)
246 {
247 }
248 constexpr ExpectedBase(Type &&value)
249 : detail::Storage<Error, Type>(detail::tags::Expected{}, std::move(value))
250 {
251 }
252
253 // Warning: will crash if this is an error. You should always check this is
254 // an expected value before calling `.value()`
255 constexpr const Type &value() const &
256 {
257 Q_ASSERT(this->mIsValue);
258 return this->mValue;
259 }
260 Type &&value() &&
261 {
262 Q_ASSERT(this->mIsValue);
263 return std::move(this->mValue);
264 }
265};
266
267// Write functions here when dev API related and when Type == void
268template <typename Error>
269struct ExpectedBase<Error, void> : detail::Storage<Error, void>
270{
271 // Rewrite constructors for unexpected because Expected doesn't have direct access to it.
272 template <typename OtherError>
273 constexpr ExpectedBase(const Unexpected<OtherError> &error)
274 : detail::Storage<Error, void>(detail::tags::Unexpected{}, error)
275 {
276 }
277 template <typename OtherError>
278 constexpr ExpectedBase(Unexpected<OtherError> &&error)
279 : detail::Storage<Error, void>(detail::tags::Unexpected{}, std::move(error))
280 {
281 }
282};
283
284} // namespace detail
285
286// Write functions here when dev API related, whether Type is void or not
287template <typename Error, typename Type = void>
288class Expected : public detail::ExpectedBase<Error, Type>
289{
290 static_assert(!std::is_same<Error, void>::value, "Expected with void Error is not implemented");
291
292public:
293 using detail::ExpectedBase<Error, Type>::ExpectedBase;
294
295 constexpr const Error &error() const &
296 {
297 return this->mError.value();
298 }
299
300 constexpr bool isValue() const
301 {
302 return this->mIsValue;
303 }
304 constexpr explicit operator bool() const
305 {
306 return this->mIsValue;
307 }
308};