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