Porytiles
Loading...
Searching...
No Matches
chainable_result.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <expected>
4#include <memory>
5#include <string>
6#include <type_traits>
7
10
11namespace porytiles2 {
12
30template <typename T, typename E = FormattableError>
32 public:
43 // NOLINTNEXTLINE(google-explicit-constructor)
44 ChainableResult(T value) : result_{std::move(value)} {}
45
56 // NOLINTNEXTLINE(google-explicit-constructor)
57 ChainableResult(const E &error) : result_{std::unexpected{error}}
58 {
59 static_assert(std::is_base_of_v<Error, E>, "ChainableResult error type E must be derived from Error");
60 error_chain_.push_back(std::make_unique<E>(error));
61 }
62
77 template <typename CauseT, typename CauseE>
78 explicit ChainableResult(const E &error, const ChainableResult<CauseT, CauseE> &cause_result)
79 : result_{std::unexpected{error}}
80 {
81 static_assert(std::is_base_of_v<Error, E>, "ChainableResult error type E must be derived from Error");
82 error_chain_.push_back(std::make_unique<E>(result_.error()));
83 add_cause(cause_result);
84 }
85
86 /*
87 * Move-only semantics
88 */
93
106 template <typename OtherT, typename OtherE>
108 {
109 assert_or_panic(!cause_result.has_value(), "cause_result has a value, but should have an error");
110
111 // Clone errors from cause_result's chain since we can't move from const
112 for (const std::unique_ptr<Error> &err : cause_result.chain()) {
113 error_chain_.push_back(err->clone());
114 }
115 }
116
122 [[nodiscard]] bool has_value() const
123 {
124 return result_.has_value();
125 }
126
136 [[nodiscard]] T &value() &
137 {
138 return result_.value();
139 }
140
150 [[nodiscard]] const T &value() const &
151 {
152 return result_.value();
153 }
154
165 [[nodiscard]] T &&value() &&
166 {
167 return std::move(result_).value();
168 }
169
179 [[nodiscard]] const E &error() const
180 {
181 return result_.error();
182 }
183
194 [[nodiscard]] const std::vector<std::unique_ptr<Error>> &chain() const
195 {
196 return error_chain_;
197 }
198
199 protected:
200 // Protected default constructor for use by the void specialization
201 ChainableResult() = default;
202
203 private:
204 std::expected<T, E> result_;
205 std::vector<std::unique_ptr<Error>> error_chain_;
206};
207
208namespace detail {
209// An empty struct to use as a placeholder for `void` in the ChainableResult specialization.
210struct Empty {};
211} // namespace detail
212
224template <typename E>
225class ChainableResult<void, E> : public ChainableResult<detail::Empty, E> {
227
228 public:
237 ChainableResult() : Base{detail::Empty{}} {}
238
250
265 template <typename CauseT, typename CauseE>
266 explicit ChainableResult(const E &error, const ChainableResult<CauseT, CauseE> &cause_result)
267 : Base{error, cause_result}
268 {
269 }
270
279 void value() &
280 {
281 Base::value();
282 }
283
292 void value() const &
293 {
294 Base::value();
295 }
296
305 void value() &&
306 {
307 std::move(*this).Base::value();
308 }
309};
310
311/*
312 * TODO: These macros need to support multi-line error messages with format params.
313 */
314
331#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, msg, return_type) \
332 auto var##_result = (expr); \
333 if (!var##_result.has_value()) { \
334 return ChainableResult<return_type>{FormattableError{msg}, var##_result}; \
335 } \
336 auto var = std::move(var##_result).value();
337
356#define PT_TRY_ASSIGN_PASS_ERR(var, expr, return_type) \
357 auto var##_result = (expr); \
358 if (!var##_result.has_value()) { \
359 return ChainableResult<return_type>{FormattableError{}, var##_result}; \
360 } \
361 auto var = std::move(var##_result).value();
362
378#define PT_TRY_ASSIGN_PASS_SAME_ERR(var, expr) \
379 auto var##_result = (expr); \
380 if (!var##_result.has_value()) { \
381 return var##_result; \
382 } \
383 auto var = std::move(var##_result).value();
384
385// Internal implementation detail - do not use directly
386#define PT_DETAIL_TRY_CALL_CHAIN_ERR_EXPAND(expr, msg, return_type, counter) \
387 auto pt_try_call_result_##counter = (expr); \
388 if (!pt_try_call_result_##counter.has_value()) { \
389 return ChainableResult<return_type>{FormattableError{msg}, pt_try_call_result_##counter}; \
390 }
391
392// Internal implementation detail - do not use directly
393#define PT_DETAIL_TRY_CALL_CHAIN_ERR_IMPL(expr, msg, return_type, counter) \
394 PT_DETAIL_TRY_CALL_CHAIN_ERR_EXPAND(expr, msg, return_type, counter)
395
413#define PT_TRY_CALL_CHAIN_ERR(expr, msg, return_type) \
414 PT_DETAIL_TRY_CALL_CHAIN_ERR_IMPL(expr, msg, return_type, __COUNTER__)
415
416// Internal implementation detail - do not use directly
417#define PT_DETAIL_TRY_CALL_PASS_ERR_EXPAND(expr, return_type, counter) \
418 auto pt_try_call_result_##counter = (expr); \
419 if (!pt_try_call_result_##counter.has_value()) { \
420 return ChainableResult<return_type>{FormattableError{}, pt_try_call_result_##counter}; \
421 }
422
423// Internal implementation detail - do not use directly
424#define PT_DETAIL_TRY_CALL_PASS_ERR_IMPL(expr, return_type, counter) \
425 PT_DETAIL_TRY_CALL_PASS_ERR_EXPAND(expr, return_type, counter)
426
446#define PT_TRY_CALL_PASS_ERR(expr, return_type) PT_DETAIL_TRY_CALL_PASS_ERR_IMPL(expr, return_type, __COUNTER__)
447
448// Internal implementation detail - do not use directly
449#define PT_DETAIL_TRY_CALL_PASS_SAME_ERR_EXPAND(expr, counter) \
450 auto pt_try_call_result_##counter = (expr); \
451 if (!pt_try_call_result_##counter.has_value()) { \
452 return pt_try_call_result_##counter; \
453 }
454
455// Internal implementation detail - do not use directly
456#define PT_DETAIL_TRY_CALL_PASS_SAME_ERR_IMPL(expr, counter) PT_DETAIL_TRY_CALL_PASS_SAME_ERR_EXPAND(expr, counter)
457
476#define PT_TRY_CALL_PASS_SAME_ERR(expr) PT_DETAIL_TRY_CALL_PASS_SAME_ERR_IMPL(expr, __COUNTER__)
477
478} // namespace porytiles2
ChainableResult(const E &error, const ChainableResult< CauseT, CauseE > &cause_result)
Constructs a ChainableResult by chaining a new error with an existing error chain.
void value() &&
Accesses the void success value (rvalue version).
ChainableResult()
Default constructor creating a successful void result.
ChainableResult(const E &error)
Constructs a ChainableResult from an error value.
void value() &
Accesses the void success value.
void value() const &
Accesses the void success value (const version).
A result type that maintains a chainable sequence of errors for debugging and error reporting.
ChainableResult(ChainableResult &&)=default
ChainableResult(const ChainableResult &)=delete
void add_cause(const ChainableResult< OtherT, OtherE > &cause_result)
Adds all errors from another ChainableResult's chain to this result's chain.
const std::vector< std::unique_ptr< Error > > & chain() const
Returns the complete error chain.
const E & error() const
Returns a const reference to the immediate error.
ChainableResult(const E &error)
Constructs a ChainableResult from an error value.
ChainableResult(T value)
Constructs a ChainableResult from a success value.
ChainableResult & operator=(ChainableResult &&)=default
const T & value() const &
Returns a const reference to the contained success value.
bool has_value() const
Checks whether the result contains a success value.
ChainableResult(const E &error, const ChainableResult< CauseT, CauseE > &cause_result)
Constructs a ChainableResult by chaining a new error with an existing error chain.
T && value() &&
Returns an rvalue reference to the contained success value.
ChainableResult & operator=(const ChainableResult &)=delete
T & value() &
Returns a reference to the contained success value.
void assert_or_panic(const bool condition, const StringViewSourceLoc &s)
Conditionally panics if the given condition is false.
Definition panic.hpp:70