| Tilt Five NDK |

result.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2020-2022 Tilt Five, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
21
22#include <iostream>
23#include <string>
24
25 namespace tiltfive {
26
28 class BadResultAccess : public std::logic_error {
29 public:
30 BadResultAccess() : logic_error("bad result access") {}
31 explicit BadResultAccess(const char* what) : logic_error(what) {}
32 ~BadResultAccess() noexcept override = default;
33};
34
36 template <typename T>
37 class [[nodiscard]] Result {
38 public:
39 using Value = T;
40
41 Result(Value&& value) noexcept : mValue(std::move(value)), mErrFlags(kErrFlagsNone) {}
42
43 Result(const Value& value) : mValue(value), mErrFlags(kErrFlagsNone) {}
44
45 Result(std::error_code err) noexcept : mErr(err), mErrFlags(kErrFlagHaveErr) {}
46
47 Result(Result&& other) : mErrFlags(other.mErrFlags) {
48 if (mErrFlags == kErrFlagsNone) {
49 new (&mValue) Value(std::move(other.mValue));
50 } else {
51 new (&mErr) std::error_code(std::move(other.mErr));
52 }
53 }
54
55 Result(const Result& other) noexcept : mErrFlags(other.mErrFlags) {
56 if (mErrFlags == kErrFlagsNone) {
57 new (&mValue) Value(other.mValue);
58 } else {
59 new (&mErr) std::error_code(other.mErr);
60 }
61 }
62
63 template <
64 typename ErrorCodeEnum,
65 typename = typename std::enable_if<std::is_error_code_enum<ErrorCodeEnum>::value>::type>
66 Result(ErrorCodeEnum err) noexcept : mErr(err), mErrFlags(kErrFlagHaveErr) {}
67
68 Result(std::errc err) noexcept : mErr(std::make_error_code(err)), mErrFlags(kErrFlagHaveErr) {}
69
70 ~Result() {
71 if (mErrFlags == kErrFlagsNone) {
72 mValue.~Value();
73 } else {
74 using std::error_code;
75 mErr.~error_code();
76 }
77 }
78
79 auto operator=(Result&& other) noexcept -> Result& {
80 if (mErrFlags == kErrFlagsNone) {
81 if (other.mErrFlags == kErrFlagsNone) {
82 mValue = std::move(other.mValue);
83 } else {
84 mValue.~Value();
85 new (&mErr) std::error_code(std::move(other.mErr));
86 }
87 } else {
88 if (other.mErrFlags == kErrFlagsNone) {
89 using std::error_code;
90 mErr.~error_code();
91 new (&mValue) Value(std::move(other.mValue));
92 } else {
93 mErr = std::move(other.mErr);
94 }
95 }
96 mErrFlags = other.mErrFlags;
97 return *this;
98 }
99
100 auto operator=(const Result& other) -> Result& {
101 if (mErrFlags == kErrFlagsNone) {
102 if (other.mErrFlags == kErrFlagsNone) {
103 mValue = other.mValue;
104 } else {
105 mValue.~Value();
106 new (&mErr) std::error_code(other.mErr);
107 }
108 } else {
109 if (other.mErrFlags == kErrFlagsNone) {
110 using std::error_code;
111 mErr.~error_code();
112 new (&mValue) Value(other.mValue);
113 } else {
114 mErr = other.mErr;
115 }
116 }
117 mErrFlags = other.mErrFlags;
118 return *this;
119 }
120
121 explicit operator bool() const noexcept {
122 return mErrFlags == kErrFlagsNone;
123 }
124
125 auto operator*() -> Value& {
126 if (mErrFlags != kErrFlagsNone) {
127 throwBadResultAccess();
128 }
129 return mValue;
130 }
131
132 auto operator*() const -> const Value& {
133 if (mErrFlags != kErrFlagsNone) {
134 throwBadResultAccess();
135 }
136 return mValue;
137 }
138
139 auto operator->() -> Value* {
140 if (mErrFlags != kErrFlagsNone) {
141 throwBadResultAccess();
142 }
143 return &mValue;
144 }
145
146 auto operator->() const -> const Value* {
147 if (mErrFlags != kErrFlagsNone) {
148 throwBadResultAccess();
149 }
150 return &mValue;
151 }
152
153 [[nodiscard]] auto error() const noexcept -> std::error_code {
154 if (mErrFlags != kErrFlagsNone) {
155 return mErr;
156 }
157 return {};
158 }
159
160 [[nodiscard]] auto logged() const noexcept -> bool {
161 return (mErrFlags & kErrFlagLogged) != 0;
162 }
163
164 [[nodiscard]] auto skipped() const noexcept -> bool {
165 return (mErrFlags & kErrFlagSkipped) != 0;
166 }
167
168 private:
169 [[noreturn]] void throwBadResultAccess() const {
170#if (__has_feature__cxx_exceptions)
171 throw BadResultAccess{};
172#else
173 std::terminate();
174#endif
175 }
176
177 union {
178 Value mValue;
179 std::error_code mErr;
180 };
181
182 static constexpr uint8_t kErrFlagsNone = 0x00;
183 static constexpr uint8_t kErrFlagHaveErr = 0x01; // Do we have an error?
184 static constexpr uint8_t kErrFlagLogged = 0x02; // Is mErr already logged?
185 static constexpr uint8_t kErrFlagSkipped = 0x04; // Was logging skipped?
186
187 uint8_t mErrFlags;
188};
189
191 struct success_t {
192 enum class Construct { kToken };
193
194 explicit constexpr success_t(Construct) {}
195};
196
198 template <>
199 class [[nodiscard]] Result<void> {
200 public:
201 using Value = void;
202
203 Result() noexcept : mDummy(), mErrFlags(kErrFlagsNone) {}
204
205 Result(std::error_code err) noexcept : mErr(err), mErrFlags(kErrFlagHaveErr) {}
206
207 Result(Result&& other) : mErrFlags(other.mErrFlags) {
208 if (mErrFlags != kErrFlagsNone) {
209 new (&mErr) std::error_code(other.mErr);
210 }
211 }
212
213 Result(const Result& other) noexcept : mErrFlags(other.mErrFlags) {
214 if (mErrFlags != kErrFlagsNone) {
215 new (&mErr) std::error_code(other.mErr);
216 }
217 }
218
219 template <
220 typename ErrorCodeEnum,
221 typename = typename std::enable_if<std::is_error_code_enum<ErrorCodeEnum>::value>::type>
222 Result(ErrorCodeEnum err) noexcept : mErr(err), mErrFlags(kErrFlagHaveErr) {}
223
224 Result(std::errc err) noexcept : mErr(std::make_error_code(err)), mErrFlags(kErrFlagHaveErr) {}
225
226 Result(success_t) noexcept : mDummy(), mErrFlags(kErrFlagsNone) {}
227
228 ~Result() {
229 if (mErrFlags != kErrFlagsNone) {
230 using std::error_code;
231 mErr.~error_code();
232 }
233 }
234
235 auto operator=(Result&& other) -> Result& {
236 if (mErrFlags == kErrFlagsNone) {
237 if (other.mErrFlags != kErrFlagsNone) {
238 new (&mErr) std::error_code(other.mErr);
239 }
240 } else {
241 if (other.mErrFlags == kErrFlagsNone) {
242 using std::error_code;
243 mErr.~error_code();
244 } else {
245 mErr = other.mErr;
246 }
247 }
248 mErrFlags = other.mErrFlags;
249 return *this;
250 }
251
252 auto operator=(const Result& other) -> Result& {
253 if (mErrFlags == kErrFlagsNone) {
254 if (other.mErrFlags != kErrFlagsNone) {
255 new (&mErr) std::error_code(other.mErr);
256 }
257 } else {
258 if (other.mErrFlags == kErrFlagsNone) {
259 using std::error_code;
260 mErr.~error_code();
261 } else {
262 mErr = other.mErr;
263 }
264 }
265 mErrFlags = other.mErrFlags;
266 return *this;
267 }
268
269 auto operator=(success_t) -> Result& {
270 if (mErrFlags != kErrFlagsNone) {
271 using std::error_code;
272 mErr.~error_code();
273 }
274 mErrFlags = kErrFlagsNone;
275 return *this;
276 }
277
278 explicit operator bool() const noexcept {
279 return mErrFlags == kErrFlagsNone;
280 }
281
282 auto operator==(success_t) const noexcept -> bool {
283 return mErrFlags == kErrFlagsNone;
284 }
285
286 [[nodiscard]] auto error() const noexcept -> std::error_code {
287 if (mErrFlags != kErrFlagsNone) {
288 return mErr;
289 }
290 return {};
291 }
292
293 [[nodiscard]] auto logged() const noexcept -> bool {
294 return (mErrFlags & kErrFlagLogged) != 0;
295 }
296
297 [[nodiscard]] auto skipped() const noexcept -> bool {
298 return (mErrFlags & kErrFlagSkipped) != 0;
299 }
300
301 private:
302 [[noreturn]] static void throwBadResultAccess() {
303#if (__has_feature__cxx_exceptions)
304 throw BadResultAccess{};
305#else
306 std::terminate();
307#endif
308 }
309
310 union {
311 uint8_t mDummy[sizeof(std::error_code)]{};
312 std::error_code mErr;
313 };
314
315 static constexpr uint8_t kErrFlagsNone = 0x00;
316 static constexpr uint8_t kErrFlagHaveErr = 0x01; // Do we have an error?
317 static constexpr uint8_t kErrFlagLogged = 0x02; // Is mErr already logged?
318 static constexpr uint8_t kErrFlagSkipped = 0x04; // Was logging skipped?
319
320 uint8_t mErrFlags;
321};
322
324 static constexpr success_t kSuccess{success_t::Construct::kToken};
325
326// Support struct to determine if a type supports std::ostream& operator<<
327 template <typename T, typename Enable = std::ostream&>
328 struct supports_ostream : std::false_type {};
329 template <typename T>
330 struct supports_ostream<T, decltype(std::declval<std::ostream&>() << std::declval<T>())>
331 : std::true_type {};
332
333 template <typename T>
334 void stringifyForStream(T& t,
335 std::ostream& os,
336 typename std::enable_if<supports_ostream<T>::value, T>::type* = 0) {
337 os << t;
338}
339
340 template <typename T>
341 void stringifyForStream(T& t,
342 std::ostream& os,
343 typename std::enable_if<!supports_ostream<T>::value, T>::type* = 0) {
344 os << "[" << typeid(t).name() << "]";
345}
346
347} // namespace tiltfive
348
350 template <typename T>
351 std::ostream& operator<<(std::ostream& os, const tiltfive::Result<T>& instance) {
352 if (!instance) {
353 os << instance.error().message();
354 } else {
355 tiltfive::stringifyForStream(*instance, os);
356 }
357 return os;
358}
359
361 template <>
362 inline std::ostream& operator<<(std::ostream& os, const tiltfive::Result<void>& instance) {
363 if (!instance) {
364 os << instance.error().message();
365 } else {
366 os << "Success";
367 }
368 return os;
369}
tiltfive::kSuccess
static constexpr success_t kSuccess
Indicates 'success' for a Result<void> function.
Definition: result.hpp:324
operator<<
std::ostream & operator<<(std::ostream &os, const tiltfive::Result< T > &instance)
Support for writing tiltfive::Result<> to an std::ostream.
Definition: result.hpp:351
iostream
string
operator*
constexpr complex< _Tp > operator*(const complex< _Tp > &__x, const complex< _Tp > &__y)
operator=
constexpr complex< _Tp > & operator=(const _Tp &)
operator==
constexpr bool operator==(const complex< _Tp > &__x, const complex< _Tp > &__y)
make_error_code
error_code make_error_code(future_errc __errc) noexcept
declval
auto declval() noexcept -> decltype(__declval< _Tp >(0))
move
constexpr _OI move(_II __first, _II __last, _OI __result)
terminate
void terminate() noexcept
basic_ostream< char >
std::logic_error
std::logic_error::logic_error
logic_error(const string &__arg) _GLIBCXX_TXN_SAFE
std::logic_error::what
virtual const char * what() const noexcept
std::error_code
std::integral_constant
tiltfive::BadResultAccess
Throw when attempting to access a bad result.
Definition: result.hpp:28
tiltfive::Result
Templated return type with support for error conditions.
Definition: result.hpp:37
tiltfive::Result< void >
Specialization of tiltfive::Result for functions with 'no return'.
Definition: result.hpp:199
tiltfive::supports_ostream
Definition: result.hpp:328
std::enable_if