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