ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
expected.hpp
1#pragma once
2
3#include <exception>
4#include <functional>
5#include <system_error>
6#include <type_traits>
7#include <utility>
8#if (defined(__cplusplus) && __cplusplus >= 202302L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)
9#include <expected>
10#define THREADSCHEDULE_HAS_STD_EXPECTED 1
11#elif defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L
12#include <expected>
13#define THREADSCHEDULE_HAS_STD_EXPECTED 1
14#else
15#define THREADSCHEDULE_HAS_STD_EXPECTED 0
16#endif
17
18// Exception handling control
19// Automatically detects if exceptions are available using __cpp_exceptions
20// When exceptions are disabled (e.g., with -fno-exceptions):
21// - value() will call std::terminate() if accessed in an error state
22// - Prefer value_or(), operator*, or check has_value() before accessing the value
23// - Compatible with exception-free builds
24#ifdef __cpp_exceptions
25#define THREADSCHEDULE_EXPECTED_THROW(ex) throw ex
26#else
27#define THREADSCHEDULE_EXPECTED_THROW(ex) ::std::terminate()
28#endif
29
30namespace threadschedule
31{
32
33#if THREADSCHEDULE_HAS_STD_EXPECTED
34template <typename E>
35using unexpected = std::unexpected<E>;
36using unexpect_t = std::unexpect_t;
37inline constexpr unexpect_t unexpect{};
38template <typename E>
39using bad_expected_access = std::bad_expected_access<E>;
40template <typename T, typename E = std::error_code>
41using expected = std::expected<T, E>;
42
43#else
44
45struct unexpect_t
46{
47 explicit unexpect_t() = default;
48};
49inline constexpr unexpect_t unexpect{};
50
51template <typename E>
53
55template <>
56class bad_expected_access<void> : public std::exception
57{
58 public:
59 bad_expected_access() = default;
60 [[nodiscard]] auto what() const noexcept -> char const* override
61 {
62 return "bad expected access";
63 }
64};
65
66template <typename E>
67class bad_expected_access : public bad_expected_access<void>
69{
70 public:
71 explicit bad_expected_access(E e) : error_(std::move(e))
72 {
73 }
74 [[nodiscard]] auto error() const& noexcept -> E const&
75 {
76 return error_;
77 }
78 auto error() & noexcept -> E&
79 {
80 return error_;
81 }
82 [[nodiscard]] auto error() const&& noexcept -> E const&&
83 {
84 return std::move(error_);
85 }
86 auto error() && noexcept -> E&&
87 {
88 return std::move(error_);
89 }
90
91 private:
92 E error_;
93};
94
95template <typename E>
96class unexpected
97{
98 public:
99 constexpr explicit unexpected(E const& e) : error_(e)
100 {
101 }
102 constexpr explicit unexpected(E&& e) : error_(std::move(e))
103 {
104 }
105 [[nodiscard]] constexpr auto error() const& noexcept -> E const&
106 {
107 return error_;
108 }
109 constexpr auto error() & noexcept -> E&
110 {
111 return error_;
112 }
113 constexpr auto error() && noexcept -> E&&
114 {
115 return std::move(error_);
116 }
117
118 private:
119 E error_;
120};
121
122template <typename T, typename E = std::error_code>
123class expected
124{
125 public:
126 using value_type = T;
127 using error_type = E;
128 using unexpected_type = unexpected<E>;
129
130 // constructors
131 template <typename U = T, typename std::enable_if_t<std::is_default_constructible_v<U>, int> = 0>
132 constexpr expected() : has_(true)
133 {
134 new (&storage_.value_) T();
135 }
136
137 template <typename U = T, typename std::enable_if_t<!std::is_default_constructible_v<U>, int> = 0>
138 constexpr expected() = delete;
139
140 constexpr expected(expected const& other) : has_(other.has_)
141 {
142 if (has_)
143 new (&storage_.value_) T(other.storage_.value_);
144 else
145 new (&storage_.error_) E(other.storage_.error_);
146 }
147
148 constexpr expected(expected&& other) noexcept(std::is_nothrow_move_constructible_v<T> &&
149 std::is_nothrow_move_constructible_v<E>)
150 : has_(other.has_)
151 {
152 if (has_)
153 new (&storage_.value_) T(std::move(other.storage_.value_));
154 else
155 new (&storage_.error_) E(std::move(other.storage_.error_));
156 }
157
158 template <typename U = T,
159 typename = std::enable_if_t<
160 !std::is_same_v<std::decay_t<U>, expected> && !std::is_same_v<std::decay_t<U>, std::in_place_t> &&
161 !std::is_same_v<std::decay_t<U>, unexpected<E>> && std::is_constructible_v<T, U>>>
162#if __cplusplus >= 202002L
163 constexpr explicit(!std::is_convertible_v<U, T>) expected(U&& value) : has_(true)
164#else
165 constexpr expected(U&& value, std::enable_if_t<std::is_convertible_v<U, T>, int> /*unused*/ = 0) : has_(true)
166 {
167 new (&storage_.value_) T(std::forward<U>(value));
168 }
169
170 template <typename U = T,
171 typename = std::enable_if_t<!std::is_same_v<std::decay_t<U>, expected> &&
172 !std::is_same_v<std::decay_t<U>, std::in_place_t> &&
173 !std::is_same_v<std::decay_t<U>, unexpected<E>> &&
174 std::is_constructible_v<T, U> && !std::is_convertible_v<U, T>>>
175 constexpr explicit expected(U&& value) : has_(true)
176#endif
177 {
178 new (&storage_.value_) T(std::forward<U>(value));
179 }
180
181 template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
182 constexpr explicit expected(std::in_place_t /*unused*/, Args&&... args) : has_(true)
183 {
184 new (&storage_.value_) T(std::forward<Args>(args)...);
185 }
186
187 constexpr expected(unexpected<E> const& ue) : has_(false)
188 {
189 new (&storage_.error_) E(ue.error());
190 }
191
192 constexpr expected(unexpected<E>&& ue) : has_(false)
193 {
194 new (&storage_.error_) E(std::move(ue.error()));
195 }
196
197 template <typename... Args>
198 constexpr explicit expected(unexpect_t /*unused*/, Args&&... args) : has_(false)
199 {
200 new (&storage_.error_) E(std::forward<Args>(args)...);
201 }
202
203 // assignment operators
204 constexpr auto operator=(expected const& other) -> expected&
205 {
206 if (this == &other)
207 return *this;
208 this->~expected();
209 new (this) expected(other);
210 return *this;
211 }
212
213 constexpr auto operator=(expected&& other) noexcept(std::is_nothrow_move_constructible_v<T> &&
214 std::is_nothrow_move_constructible_v<E>) -> expected&
215 {
216 if (this == &other)
217 return *this;
218 this->~expected();
219 new (this) expected(std::move(other));
220 return *this;
221 }
222
223 template <typename U = T,
224 typename = std::enable_if_t<!std::is_same_v<std::decay_t<U>, expected> && std::is_constructible_v<T, U>>>
225 constexpr auto operator=(U&& value) -> expected&
226 {
227 if (has_)
228 {
229 storage_.value_.~T();
230 new (&storage_.value_) T(std::forward<U>(value));
231 }
232 else
233 {
234 this->~expected();
235 new (this) expected(std::forward<U>(value));
236 }
237 return *this;
238 }
239
240 constexpr auto operator=(unexpected<E> const& ue) -> expected&
241 {
242 if (!has_)
243 {
244 storage_.error_ = ue.error();
245 }
246 else
247 {
248 this->~expected();
249 new (this) expected(ue);
250 }
251 return *this;
252 }
253
254 constexpr auto operator=(unexpected<E>&& ue) -> expected&
255 {
256 if (!has_)
257 {
258 storage_.error_ = std::move(ue.error());
259 }
260 else
261 {
262 this->~expected();
263 new (this) expected(std::move(ue));
264 }
265 return *this;
266 }
267
268 ~expected()
269 {
270 if (has_)
271 storage_.value_.~T();
272 else
273 storage_.error_.~E();
274 }
275
276 // observers
277 constexpr auto operator->() const noexcept -> T const*
278 {
279 return &storage_.value_;
280 }
281
282 constexpr auto operator->() noexcept -> T*
283 {
284 return &storage_.value_;
285 }
286
287 constexpr auto operator*() const& noexcept -> T const&
288 {
289 return storage_.value_;
290 }
291
292 constexpr auto operator*() & noexcept -> T&
293 {
294 return storage_.value_;
295 }
296
297 constexpr auto operator*() const&& noexcept -> T const&&
298 {
299 return std::move(storage_.value_);
300 }
301
302 constexpr auto operator*() && noexcept -> T&&
303 {
304 return std::move(storage_.value_);
305 }
306
307 constexpr explicit operator bool() const noexcept
308 {
309 return has_;
310 }
311
312 [[nodiscard]] constexpr auto has_value() const noexcept -> bool
313 {
314 return has_;
315 }
316
317 [[nodiscard]] constexpr auto value() const& -> T const&
318 {
319 if (!has_)
320 THREADSCHEDULE_EXPECTED_THROW(bad_expected_access<E>(storage_.error_));
321 return storage_.value_;
322 }
323
324 constexpr auto value() & -> T&
325 {
326 if (!has_)
327 THREADSCHEDULE_EXPECTED_THROW(bad_expected_access<E>(storage_.error_));
328 return storage_.value_;
329 }
330
331 [[nodiscard]] constexpr auto value() const&& -> T const&&
332 {
333 if (!has_)
334 THREADSCHEDULE_EXPECTED_THROW(bad_expected_access<E>(std::move(storage_.error_)));
335 return std::move(storage_.value_);
336 }
337
338 constexpr auto value() && -> T&&
339 {
340 if (!has_)
341 THREADSCHEDULE_EXPECTED_THROW(bad_expected_access<E>(std::move(storage_.error_)));
342 return std::move(storage_.value_);
343 }
344
345 [[nodiscard]] constexpr auto error() const& noexcept -> E const&
346 {
347 return storage_.error_;
348 }
349
350 constexpr auto error() & noexcept -> E&
351 {
352 return storage_.error_;
353 }
354
355 [[nodiscard]] constexpr auto error() const&& noexcept -> E const&&
356 {
357 return std::move(storage_.error_);
358 }
359
360 constexpr auto error() && noexcept -> E&&
361 {
362 return std::move(storage_.error_);
363 }
364
365 template <typename U>
366 constexpr auto value_or(U&& default_value) const& -> T
367 {
368 return has_ ? storage_.value_ : static_cast<T>(std::forward<U>(default_value));
369 }
370
371 template <typename U>
372 constexpr auto value_or(U&& default_value) && -> T
373 {
374 return has_ ? std::move(storage_.value_) : static_cast<T>(std::forward<U>(default_value));
375 }
376
377 // emplace
378 template <typename... Args>
379 constexpr auto emplace(Args&&... args) -> T&
380 {
381 this->~expected();
382 new (this) expected(std::in_place, std::forward<Args>(args)...);
383 return storage_.value_;
384 }
385
386 // swap
387 constexpr void swap(expected& other) noexcept(std::is_nothrow_move_constructible_v<T> &&
388 std::is_nothrow_move_constructible_v<E> &&
389 std::is_nothrow_swappable_v<T> && std::is_nothrow_swappable_v<E>)
390 {
391 if (has_ && other.has_)
392 {
393 using std::swap;
394 swap(storage_.value_, other.storage_.value_);
395 }
396 else if (!has_ && !other.has_)
397 {
398 using std::swap;
399 swap(storage_.error_, other.storage_.error_);
400 }
401 else
402 {
403 expected temp(std::move(other));
404 other.~expected();
405 new (&other) expected(std::move(*this));
406 this->~expected();
407 new (this) expected(std::move(temp));
408 }
409 }
410
411 // monadic operations
412 template <typename F>
413 constexpr auto and_then(F&& f) &
414 {
415 using U = std::invoke_result_t<F, T&>;
416 if (has_)
417 return std::invoke(std::forward<F>(f), storage_.value_);
418 return U(unexpect, storage_.error_);
419 }
420
421 template <typename F>
422 constexpr auto and_then(F&& f) const&
423 {
424 using U = std::invoke_result_t<F, T const&>;
425 if (has_)
426 return std::invoke(std::forward<F>(f), storage_.value_);
427 return U(unexpect, storage_.error_);
428 }
429
430 template <typename F>
431 constexpr auto and_then(F&& f) &&
432 {
433 using U = std::invoke_result_t<F, T&&>;
434 if (has_)
435 return std::invoke(std::forward<F>(f), std::move(storage_.value_));
436 return U(unexpect, std::move(storage_.error_));
437 }
438
439 template <typename F>
440 constexpr auto and_then(F&& f) const&&
441 {
442 using U = std::invoke_result_t<F, T const&&>;
443 if (has_)
444 return std::invoke(std::forward<F>(f), std::move(storage_.value_));
445 return U(unexpect, std::move(storage_.error_));
446 }
447
448 template <typename F>
449 constexpr auto or_else(F&& f) &
450 {
451 using U = std::invoke_result_t<F, E&>;
452 if (has_)
453 return U(storage_.value_);
454 return std::invoke(std::forward<F>(f), storage_.error_);
455 }
456
457 template <typename F>
458 constexpr auto or_else(F&& f) const&
459 {
460 using U = std::invoke_result_t<F, E const&>;
461 if (has_)
462 return U(storage_.value_);
463 return std::invoke(std::forward<F>(f), storage_.error_);
464 }
465
466 template <typename F>
467 constexpr auto or_else(F&& f) &&
468 {
469 using U = std::invoke_result_t<F, E&&>;
470 if (has_)
471 return U(std::move(storage_.value_));
472 return std::invoke(std::forward<F>(f), std::move(storage_.error_));
473 }
474
475 template <typename F>
476 constexpr auto or_else(F&& f) const&&
477 {
478 using U = std::invoke_result_t<F, E const&&>;
479 if (has_)
480 return U(std::move(storage_.value_));
481 return std::invoke(std::forward<F>(f), std::move(storage_.error_));
482 }
483
484 template <typename F>
485 constexpr auto transform(F&& f) &
486 {
487 using U = std::remove_cv_t<std::invoke_result_t<F, T&>>;
488 if (has_)
489 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f), storage_.value_));
490 return expected<U, E>(unexpect, storage_.error_);
491 }
492
493 template <typename F>
494 constexpr auto transform(F&& f) const&
495 {
496 using U = std::remove_cv_t<std::invoke_result_t<F, T const&>>;
497 if (has_)
498 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f), storage_.value_));
499 return expected<U, E>(unexpect, storage_.error_);
500 }
501
502 template <typename F>
503 constexpr auto transform(F&& f) &&
504 {
505 using U = std::remove_cv_t<std::invoke_result_t<F, T&&>>;
506 if (has_)
507 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f), std::move(storage_.value_)));
508 return expected<U, E>(unexpect, std::move(storage_.error_));
509 }
510
511 template <typename F>
512 constexpr auto transform(F&& f) const&&
513 {
514 using U = std::remove_cv_t<std::invoke_result_t<F, T const&&>>;
515 if (has_)
516 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f), std::move(storage_.value_)));
517 return expected<U, E>(unexpect, std::move(storage_.error_));
518 }
519
520 template <typename F>
521 constexpr auto transform_error(F&& f) &
522 {
523 using G = std::remove_cv_t<std::invoke_result_t<F, E&>>;
524 if (has_)
525 return expected<T, G>(storage_.value_);
526 return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), storage_.error_));
527 }
528
529 template <typename F>
530 constexpr auto transform_error(F&& f) const&
531 {
532 using G = std::remove_cv_t<std::invoke_result_t<F, E const&>>;
533 if (has_)
534 return expected<T, G>(storage_.value_);
535 return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), storage_.error_));
536 }
537
538 template <typename F>
539 constexpr auto transform_error(F&& f) &&
540 {
541 using G = std::remove_cv_t<std::invoke_result_t<F, E&&>>;
542 if (has_)
543 return expected<T, G>(std::move(storage_.value_));
544 return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), std::move(storage_.error_)));
545 }
546
547 template <typename F>
548 constexpr auto transform_error(F&& f) const&&
549 {
550 using G = std::remove_cv_t<std::invoke_result_t<F, E const&&>>;
551 if (has_)
552 return expected<T, G>(std::move(storage_.value_));
553 return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), std::move(storage_.error_)));
554 }
555
556 // equality operators
557 template <typename T2, typename E2>
558 constexpr friend auto operator==(expected const& lhs, expected<T2, E2> const& rhs) -> bool
559 {
560 if (lhs.has_value() != rhs.has_value())
561 return false;
562 if (lhs.has_value())
563 return *lhs == *rhs;
564 return lhs.error() == rhs.error();
565 }
566
567 template <typename T2, typename E2>
568 constexpr friend auto operator!=(expected const& lhs, expected<T2, E2> const& rhs) -> bool
569 {
570 return !(lhs == rhs);
571 }
572
573 template <typename T2, typename = std::enable_if_t<!std::is_same_v<expected, std::decay_t<T2>>>>
574 constexpr friend auto operator==(expected const& lhs, const T2& rhs) -> bool
575 {
576 return lhs.has_value() && *lhs == rhs;
577 }
578
579 template <typename T2, typename = std::enable_if_t<!std::is_same_v<expected, std::decay_t<T2>>>>
580 constexpr friend auto operator==(const T2& lhs, expected const& rhs) -> bool
581 {
582 return rhs.has_value() && lhs == *rhs;
583 }
584
585 template <typename T2, typename = std::enable_if_t<!std::is_same_v<expected, std::decay_t<T2>>>>
586 constexpr friend auto operator!=(expected const& lhs, const T2& rhs) -> bool
587 {
588 return !(lhs == rhs);
589 }
590
591 template <typename T2, typename = std::enable_if_t<!std::is_same_v<expected, std::decay_t<T2>>>>
592 constexpr friend auto operator!=(const T2& lhs, expected const& rhs) -> bool
593 {
594 return !(lhs == rhs);
595 }
596
597 template <typename E2>
598 constexpr friend auto operator==(expected const& lhs, unexpected<E2> const& rhs) -> bool
599 {
600 return !lhs.has_value() && lhs.error() == rhs.error();
601 }
602
603 template <typename E2>
604 constexpr friend auto operator==(unexpected<E2> const& lhs, expected const& rhs) -> bool
605 {
606 return !rhs.has_value() && lhs.error() == rhs.error();
607 }
608
609 template <typename E2>
610 constexpr friend auto operator!=(expected const& lhs, unexpected<E2> const& rhs) -> bool
611 {
612 return !(lhs == rhs);
613 }
614
615 template <typename E2>
616 constexpr friend auto operator!=(unexpected<E2> const& lhs, expected const& rhs) -> bool
617 {
618 return !(lhs == rhs);
619 }
620
621 private:
622 bool has_;
623 union Storage {
624 Storage()
625 {
626 }
627 ~Storage()
628 {
629 }
630 T value_;
631 E error_;
632 } storage_;
633};
634
635template <typename E>
636class expected<void, E>
637{
638 public:
639 using value_type = void;
640 using error_type = E;
641 using unexpected_type = unexpected<E>;
642
643 constexpr expected() : has_(true)
644 {
645 }
646
647 constexpr expected(expected const& other) = default;
648 constexpr expected(expected&& other) = default;
649
650 template <typename... Args>
651 constexpr explicit expected(unexpect_t /*unused*/, Args&&... args)
652 : has_(false), error_(std::forward<Args>(args)...)
653 {
654 }
655
656 constexpr expected(unexpected<E> const& ue) : has_(false), error_(ue.error())
657 {
658 }
659
660 constexpr expected(unexpected<E>&& ue) : has_(false), error_(std::move(ue.error()))
661 {
662 }
663
664 constexpr auto operator=(expected const& other) -> expected& = default;
665 constexpr auto operator=(expected&& other) -> expected& = default;
666
667 constexpr auto operator=(unexpected<E> const& ue) -> expected&
668 {
669 has_ = false;
670 error_ = ue.error();
671 return *this;
672 }
673
674 constexpr auto operator=(unexpected<E>&& ue) -> expected&
675 {
676 has_ = false;
677 error_ = std::move(ue.error());
678 return *this;
679 }
680
681 constexpr explicit operator bool() const noexcept
682 {
683 return has_;
684 }
685
686 [[nodiscard]] constexpr auto has_value() const noexcept -> bool
687 {
688 return has_;
689 }
690
691 constexpr void value() const
692 {
693 if (!has_)
694 THREADSCHEDULE_EXPECTED_THROW(bad_expected_access<E>(error_));
695 }
696
697 [[nodiscard]] constexpr auto error() const& noexcept -> E const&
698 {
699 return error_;
700 }
701
702 constexpr auto error() & noexcept -> E&
703 {
704 return error_;
705 }
706
707 [[nodiscard]] constexpr auto error() const&& noexcept -> E const&&
708 {
709 return std::move(error_);
710 }
711
712 constexpr auto error() && noexcept -> E&&
713 {
714 return std::move(error_);
715 }
716
717 constexpr void emplace()
718 {
719 has_ = true;
720 }
721
722 constexpr void swap(expected& other) noexcept(std::is_nothrow_move_constructible_v<E> &&
723 std::is_nothrow_swappable_v<E>)
724 {
725 if (has_ && other.has_)
726 {
727 // both have values, nothing to swap
728 }
729 else if (!has_ && !other.has_)
730 {
731 using std::swap;
732 swap(error_, other.error_);
733 }
734 else
735 {
736 std::swap(has_, other.has_);
737 std::swap(error_, other.error_);
738 }
739 }
740
741 // monadic operations
742 template <typename F>
743 constexpr auto and_then(F&& f) &
744 {
745 using U = std::invoke_result_t<F>;
746 if (has_)
747 return std::invoke(std::forward<F>(f));
748 return U(unexpect, error_);
749 }
750
751 template <typename F>
752 constexpr auto and_then(F&& f) const&
753 {
754 using U = std::invoke_result_t<F>;
755 if (has_)
756 return std::invoke(std::forward<F>(f));
757 return U(unexpect, error_);
758 }
759
760 template <typename F>
761 constexpr auto and_then(F&& f) &&
762 {
763 using U = std::invoke_result_t<F>;
764 if (has_)
765 return std::invoke(std::forward<F>(f));
766 return U(unexpect, std::move(error_));
767 }
768
769 template <typename F>
770 constexpr auto and_then(F&& f) const&&
771 {
772 using U = std::invoke_result_t<F>;
773 if (has_)
774 return std::invoke(std::forward<F>(f));
775 return U(unexpect, std::move(error_));
776 }
777
778 template <typename F>
779 constexpr auto or_else(F&& f) &
780 {
781 using U = std::invoke_result_t<F, E&>;
782 if (has_)
783 return U();
784 return std::invoke(std::forward<F>(f), error_);
785 }
786
787 template <typename F>
788 constexpr auto or_else(F&& f) const&
789 {
790 using U = std::invoke_result_t<F, E const&>;
791 if (has_)
792 return U();
793 return std::invoke(std::forward<F>(f), error_);
794 }
795
796 template <typename F>
797 constexpr auto or_else(F&& f) &&
798 {
799 using U = std::invoke_result_t<F, E&&>;
800 if (has_)
801 return U();
802 return std::invoke(std::forward<F>(f), std::move(error_));
803 }
804
805 template <typename F>
806 constexpr auto or_else(F&& f) const&&
807 {
808 using U = std::invoke_result_t<F, E const&&>;
809 if (has_)
810 return U();
811 return std::invoke(std::forward<F>(f), std::move(error_));
812 }
813
814 template <typename F>
815 constexpr auto transform(F&& f) &
816 {
817 using U = std::remove_cv_t<std::invoke_result_t<F>>;
818 if (has_)
819 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f)));
820 return expected<U, E>(unexpect, error_);
821 }
822
823 template <typename F>
824 constexpr auto transform(F&& f) const&
825 {
826 using U = std::remove_cv_t<std::invoke_result_t<F>>;
827 if (has_)
828 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f)));
829 return expected<U, E>(unexpect, error_);
830 }
831
832 template <typename F>
833 constexpr auto transform(F&& f) &&
834 {
835 using U = std::remove_cv_t<std::invoke_result_t<F>>;
836 if (has_)
837 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f)));
838 return expected<U, E>(unexpect, std::move(error_));
839 }
840
841 template <typename F>
842 constexpr auto transform(F&& f) const&&
843 {
844 using U = std::remove_cv_t<std::invoke_result_t<F>>;
845 if (has_)
846 return expected<U, E>(std::in_place, std::invoke(std::forward<F>(f)));
847 return expected<U, E>(unexpect, std::move(error_));
848 }
849
850 template <typename F>
851 constexpr auto transform_error(F&& f) &
852 {
853 using G = std::remove_cv_t<std::invoke_result_t<F, E&>>;
854 if (has_)
855 return expected<void, G>();
856 return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), error_));
857 }
858
859 template <typename F>
860 constexpr auto transform_error(F&& f) const&
861 {
862 using G = std::remove_cv_t<std::invoke_result_t<F, E const&>>;
863 if (has_)
864 return expected<void, G>();
865 return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), error_));
866 }
867
868 template <typename F>
869 constexpr auto transform_error(F&& f) &&
870 {
871 using G = std::remove_cv_t<std::invoke_result_t<F, E&&>>;
872 if (has_)
873 return expected<void, G>();
874 return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), std::move(error_)));
875 }
876
877 template <typename F>
878 constexpr auto transform_error(F&& f) const&&
879 {
880 using G = std::remove_cv_t<std::invoke_result_t<F, E const&&>>;
881 if (has_)
882 return expected<void, G>();
883 return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), std::move(error_)));
884 }
885
886 // equality operators
887 template <typename E2>
888 constexpr friend auto operator==(expected const& lhs, expected<void, E2> const& rhs) -> bool
889 {
890 if (lhs.has_value() != rhs.has_value())
891 return false;
892 if (lhs.has_value())
893 return true;
894 return lhs.error() == rhs.error();
895 }
896
897 template <typename E2>
898 constexpr friend auto operator!=(expected const& lhs, expected<void, E2> const& rhs) -> bool
899 {
900 return !(lhs == rhs);
901 }
902
903 template <typename E2>
904 constexpr friend auto operator==(expected const& lhs, unexpected<E2> const& rhs) -> bool
905 {
906 return !lhs.has_value() && lhs.error() == rhs.error();
907 }
908
909 template <typename E2>
910 constexpr friend auto operator==(unexpected<E2> const& lhs, expected const& rhs) -> bool
911 {
912 return !rhs.has_value() && lhs.error() == rhs.error();
913 }
914
915 template <typename E2>
916 constexpr friend auto operator!=(expected const& lhs, unexpected<E2> const& rhs) -> bool
917 {
918 return !(lhs == rhs);
919 }
920
921 template <typename E2>
922 constexpr friend auto operator!=(unexpected<E2> const& lhs, expected const& rhs) -> bool
923 {
924 return !(lhs == rhs);
925 }
926
927 private:
928 bool has_;
929 E error_{};
930};
931
932#endif // std::expected fallback
933
934// swap for expected
935template <typename T, typename E>
936constexpr void swap(expected<T, E>& lhs, expected<T, E>& rhs) noexcept(noexcept(lhs.swap(rhs)))
937{
938 lhs.swap(rhs);
939}
940
941} // namespace threadschedule