// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_TYPES_EXPECTED_H_ #define BASE_TYPES_EXPECTED_H_ #include #include #include "base/check.h" #include "base/types/expected_internal.h" // IWYU pragma: export #include "third_party/abseil-cpp/absl/utility/utility.h" // Class template `expected` is a vocabulary type which contains an // expected value of type `T`, or an error `E`. The class skews towards behaving // like a `T`, because its intended use is when the expected type is contained. // When something unexpected occurs, more typing is required. When all is good, // code mostly looks as if a `T` were being handled. // // Class template `expected` contains either: // * A value of type `T`, the expected value type; or // * A value of type `E`, an error type used when an unexpected outcome // occurred. // // The interface can be queried as to whether the underlying value is the // expected value (of type `T`) or an unexpected value (of type `E`). The // interface and the rational are based on `std::optional`. We consider // `expected` as a supplement to `optional`, expressing why an expected // value isn’t contained in the object. // // Example Usage: // // Before: // bool ParseInt32(base::StringPiece input, // int32_t* output, // ParseIntError* error); // ... // // int32_t output; // ParseIntError error; // if (ParseInt32("...". &output, &error)) { // // process `output` // } else { // // process `error` // } // // After: // // base::expected ParseInt32(base::StringPiece input); // ... // // if (auto parsed = ParseInt32("..."); parsed.has_value()) { // // process `parsed.value()` // } else { // // process `parsed.error()` // } // // References: // * https://wg21.link/P0323 // * https://eel.is/c++draft/expected namespace base { // Note: base::unexpected and base::expected are C++17 compatible backports of // C++23's std::unexpected and std::expected. They differ from the standard in // the following ways: // // * Not all member functions can be used in a constexpr context. This is due to // limitations in both the C++17 language and the Abseil library used for the // implementation. // * Since Chromium does not support exceptions, there is no bad_expected_access // exception and the program will just terminate when the exception would have // been thrown. Furthermore, all member functions are marked noexcept. // * C++23 allows an implicit conversion from U to expected if U is // implicitly convertible to T; the Chromium version only allows an implicit // conversion if U is implicitly convertible to T *and* U is *not* implicitly // convertible to E, to guard against bug-prone patterns such as: // // creates an expected value containing true, not an unexpected value // // containing 123L. // expected e = 123L; // * Because of the above restriction, the Chromium version also introduces // `base::ok` as a complement to `base::unexpected` to simplify returning // success values when the implicit conversion above is disallowed. // * Calling operator* or operator-> on an unexpected value results in program // termination, and not UB. // * There is no operator bool due to bug-prone usage when the value type is // convertible to bool, see e.g. https://abseil.io/tips/141. // * Moving out of an expected object will put it into a moved-from state. // Trying to use it before re-initializing it will result in program // termination. // * The expected specialization is done via a defaulted boolean template // parameter, due to the lack of requires clauses in C++17. // * Since equality operators can not be defaulted in C++17, equality and // inequality operators are specified explicitly. // * base::expected implements the monadic interface proposal // (https://wg21.link/P2505), which is currently only on track for C++26. // Class template used as a type hint for constructing a `base::expected` // containing a value (i.e. success). Useful when implicit conversion // construction of `base::expected` is disallowed, e.g. due to ambiguity. // Example usage: // // base::expected RunOp() { // std::string error; // std::string result = RunOpImpl(..., &error); // if (!error.empty()) { // return base::unexpected(std::move(error)); // // // The C++23 std::expected proposal allows this to be simply written as // // return result; // // // // However, the Chromium version disallows this if E implicitly converts // // to T, so without base::ok(), this would have to be written as: // // return base::expected(std::move(result)); // // return base::ok(std::move(result)); // } template class ok = */ false> { public: template = 0> constexpr explicit ok(U&& val) noexcept : value_(std::forward(val)) {} template constexpr explicit ok(absl::in_place_t, Args&&... args) noexcept : value_(std::forward(args)...) {} template constexpr explicit ok(absl::in_place_t, std::initializer_list il, Args&&... args) noexcept : value_(il, std::forward(args)...) {} constexpr T& value() & noexcept { return value_; } constexpr const T& value() const& noexcept { return value_; } constexpr T&& value() && noexcept { return std::move(value()); } constexpr const T&& value() const&& noexcept { return std::move(value()); } constexpr void swap(ok& other) noexcept { using std::swap; swap(value(), other.value()); } friend constexpr void swap(ok& x, ok& y) noexcept { x.swap(y); } private: T value_; }; template class ok = */ true> { public: constexpr explicit ok() noexcept = default; }; template constexpr bool operator==(const ok& lhs, const ok& rhs) noexcept { return lhs.value() == rhs.value(); } template constexpr bool operator!=(const ok& lhs, const ok& rhs) noexcept { return !(lhs == rhs); } template ok(T) -> ok; ok()->ok; // [expected.un.object], class template unexpected // https://eel.is/c++draft/expected#un.object template class unexpected { public: // [expected.un.ctor] Constructors template = 0> constexpr explicit unexpected(Err&& err) noexcept : error_(std::forward(err)) {} template constexpr explicit unexpected(absl::in_place_t, Args&&... args) noexcept : error_(std::forward(args)...) {} template constexpr explicit unexpected(absl::in_place_t, std::initializer_list il, Args&&... args) noexcept : error_(il, std::forward(args)...) {} // [expected.un.obs] Observers constexpr E& error() & noexcept { return error_; } constexpr const E& error() const& noexcept { return error_; } constexpr E&& error() && noexcept { return std::move(error()); } constexpr const E&& error() const&& noexcept { return std::move(error()); } // [expected.un.swap] Swap constexpr void swap(unexpected& other) noexcept { using std::swap; swap(error(), other.error()); } friend constexpr void swap(unexpected& x, unexpected& y) noexcept { x.swap(y); } private: E error_; }; // [expected.un.eq] Equality operator template constexpr bool operator==(const unexpected& lhs, const unexpected& rhs) noexcept { return lhs.error() == rhs.error(); } template constexpr bool operator!=(const unexpected& lhs, const unexpected& rhs) noexcept { return !(lhs == rhs); } template unexpected(E) -> unexpected; // [expected.expected], class template expected // https://eel.is/c++draft/expected#expected template class [[nodiscard]] expected = */ false> { // Note: A partial specialization for void value types follows below. static_assert(!std::is_void_v, "Error: T must not be void"); public: using value_type = T; using error_type = E; using unexpected_type = unexpected; // Alias template to explicitly opt into the std::pointer_traits machinery. // See e.g. https://en.cppreference.com/w/cpp/memory/pointer_traits#Notes template using rebind = expected; template friend class expected; // [expected.object.ctor], constructors constexpr expected() noexcept = default; // Converting copy and move constructors. These constructors are explicit if // either the value or error type is not implicitly convertible from `rhs`'s // corresponding type. template = 0> explicit constexpr expected(const expected& rhs) noexcept : impl_(rhs.impl_) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(const expected& rhs) noexcept : impl_(rhs.impl_) {} template = 0> explicit constexpr expected(expected&& rhs) noexcept : impl_(std::move(rhs.impl_)) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(expected&& rhs) noexcept : impl_(std::move(rhs.impl_)) {} // Deviation from the Standard, which allows implicit conversions as long as U // is implicitly convertible to T: Chromium additionally requires that U is // not implicitly convertible to E. template = 0> explicit constexpr expected(U&& v) noexcept : impl_(kValTag, std::forward(v)) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(U&& v) noexcept : impl_(kValTag, std::forward(v)) {} template = 0> explicit constexpr expected(const ok& o) noexcept : impl_(kValTag, o.value()) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(const ok& o) noexcept : impl_(kValTag, o.value()) {} template = 0> explicit constexpr expected(ok&& o) noexcept : impl_(kValTag, std::move(o.value())) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(ok&& o) noexcept : impl_(kValTag, std::move(o.value())) {} template = 0> explicit constexpr expected(const unexpected& e) noexcept : impl_(kErrTag, e.error()) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(const unexpected& e) noexcept : impl_(kErrTag, e.error()) {} template = 0> explicit constexpr expected(unexpected&& e) noexcept : impl_(kErrTag, std::move(e.error())) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(unexpected&& e) noexcept : impl_(kErrTag, std::move(e.error())) {} template constexpr explicit expected(absl::in_place_t, Args&&... args) noexcept : impl_(kValTag, std::forward(args)...) {} template constexpr explicit expected(absl::in_place_t, std::initializer_list il, Args&&... args) noexcept : impl_(kValTag, il, std::forward(args)...) {} template constexpr explicit expected(unexpect_t, Args&&... args) noexcept : impl_(kErrTag, std::forward(args)...) {} template constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args) noexcept : impl_(kErrTag, il, std::forward(args)...) {} // [expected.object.assign], assignment template = 0> constexpr expected& operator=(U&& v) noexcept { emplace(std::forward(v)); return *this; } template constexpr expected& operator=(const ok& o) noexcept { emplace(o.value()); return *this; } template constexpr expected& operator=(ok&& o) noexcept { emplace(std::move(o.value())); return *this; } template constexpr expected& operator=(const unexpected& e) noexcept { impl_.emplace_error(e.error()); return *this; } template constexpr expected& operator=(unexpected&& e) noexcept { impl_.emplace_error(std::move(e.error())); return *this; } template constexpr T& emplace(Args&&... args) noexcept { return impl_.emplace_value(std::forward(args)...); } template constexpr T& emplace(std::initializer_list il, Args&&... args) noexcept { return impl_.emplace_value(il, std::forward(args)...); } // [expected.object.swap], swap constexpr void swap(expected& rhs) noexcept { impl_.swap(rhs.impl_); } friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); } // [expected.object.obs], observers constexpr T* operator->() noexcept { return std::addressof(value()); } constexpr const T* operator->() const noexcept { return std::addressof(value()); } constexpr T& operator*() & noexcept { return value(); } constexpr const T& operator*() const& noexcept { return value(); } constexpr T&& operator*() && noexcept { return std::move(value()); } constexpr const T&& operator*() const&& noexcept { return std::move(value()); } // Note: Deviation from the Standard: No operator bool due to bug-prone // patterns when the value type is convertible to bool, see e.g. // https://abseil.io/tips/141. constexpr bool has_value() const noexcept { return impl_.has_value(); } constexpr T& value() & noexcept { return impl_.value(); } constexpr const T& value() const& noexcept { return impl_.value(); } constexpr T&& value() && noexcept { return std::move(value()); } constexpr const T&& value() const&& noexcept { return std::move(value()); } constexpr E& error() & noexcept { return impl_.error(); } constexpr const E& error() const& noexcept { return impl_.error(); } constexpr E&& error() && noexcept { return std::move(error()); } constexpr const E&& error() const&& noexcept { return std::move(error()); } template constexpr T value_or(U&& v) const& noexcept { static_assert(std::is_copy_constructible_v, "expected::value_or: T must be copy constructible"); static_assert(std::is_convertible_v, "expected::value_or: U must be convertible to T"); return has_value() ? value() : static_cast(std::forward(v)); } template constexpr T value_or(U&& v) && noexcept { static_assert(std::is_move_constructible_v, "expected::value_or: T must be move constructible"); static_assert(std::is_convertible_v, "expected::value_or: U must be convertible to T"); return has_value() ? std::move(value()) : static_cast(std::forward(v)); } template constexpr E error_or(G&& e) const& noexcept { static_assert(std::is_copy_constructible_v, "expected::error_or: E must be copy constructible"); static_assert(std::is_convertible_v, "expected::error_or: G must be convertible to E"); return has_value() ? static_cast(std::forward(e)) : error(); } template constexpr E error_or(G&& e) && noexcept { static_assert(std::is_move_constructible_v, "expected::error_or: E must be move constructible"); static_assert(std::is_convertible_v, "expected::error_or: G must be convertible to E"); return has_value() ? static_cast(std::forward(e)) : std::move(error()); } // [expected.object.monadic], monadic operations // // This section implements the monadic interface consisting of `and_then`, // `or_else`, `transform` and `transform_error`. // `and_then`: This methods accepts a callable `f` that is invoked with // `value()` in case `has_value()` is true. // // `f`'s return type is required to be a (cvref-qualified) specialization of // base::expected with a matching error_type, i.e. it needs to be of the form // base::expected cv ref for some U. // // If `has_value()` is false, this is effectively a no-op, and the function // returns base::expected(unexpect, std::forward(error())); // // `and_then` is overloaded for all possible forms of const and ref // qualifiers. template = 0> constexpr auto and_then(F&& f) & noexcept { return internal::AndThen(*this, std::forward(f)); } template = 0> constexpr auto and_then(F&& f) const& noexcept { return internal::AndThen(*this, std::forward(f)); } template = 0> constexpr auto and_then(F&& f) && noexcept { return internal::AndThen(std::move(*this), std::forward(f)); } template = 0> constexpr auto and_then(F&& f) const&& noexcept { return internal::AndThen(std::move(*this), std::forward(f)); } // `or_else`: This methods accepts a callable `f` that is invoked with // `error()` in case `has_value()` is false. // // `f`'s return type is required to be a (cvref-qualified) specialization of // base::expected with a matching value_type, i.e. it needs to be of the form // base::expected cv ref for some G. // // If `has_value()` is true, this is effectively a no-op, and the function // returns base::expected(std::forward(value())); // // `or_else` is overloaded for all possible forms of const and ref // qualifiers. template = 0> constexpr auto or_else(F&& f) & noexcept { return internal::OrElse(*this, std::forward(f)); } template = 0> constexpr auto or_else(F&& f) const& noexcept { return internal::OrElse(*this, std::forward(f)); } template = 0> constexpr auto or_else(F&& f) && noexcept { return internal::OrElse(std::move(*this), std::forward(f)); } template = 0> constexpr auto or_else(F&& f) const&& noexcept { return internal::OrElse(std::move(*this), std::forward(f)); } template = 0> constexpr auto transform(F&& f) & noexcept { return internal::Transform(*this, std::forward(f)); } // `transform`: This methods accepts a callable `f` that is invoked with // `value()` in case `has_value()` is true. // // `f`'s return type U needs to be a valid value_type for expected, i.e. any // type for which `remove_cv_t` is either void, or a complete non-array object // type that is not `absl::in_place_t`, `base::unexpect_t`, or a // specialization of `base::ok` or `base::unexpected`. // // Returns an instance of base::expected, E> that is // constructed with f(value()) if there is a value, or unexpected(error()) // otherwise. // // `transform` is overloaded for all possible forms of const and ref // qualifiers. template = 0> constexpr auto transform(F&& f) const& noexcept { return internal::Transform(*this, std::forward(f)); } template = 0> constexpr auto transform(F&& f) && noexcept { return internal::Transform(std::move(*this), std::forward(f)); } template = 0> constexpr auto transform(F&& f) const&& noexcept { return internal::Transform(std::move(*this), std::forward(f)); } // `transform_error`: This methods accepts a callable `f` that is invoked with // `error()` in case `has_value()` is false. // // `f`'s return type G needs to be a valid error_type for expected, i.e. any // type for which `remove_cv_t` is a complete non-array object type that is // not `absl::in_place_t`, `base::unexpect_t`, or a specialization of // `base::ok` or `base::unexpected`. // // Returns an instance of base::expected> that is // constructed with unexpected(f(error())) if there is no value, or value() // otherwise. // // `transform_error` is overloaded for all possible forms of const and ref // qualifiers. template = 0> constexpr auto transform_error(F&& f) & noexcept { return internal::TransformError(*this, std::forward(f)); } template = 0> constexpr auto transform_error(F&& f) const& noexcept { return internal::TransformError(*this, std::forward(f)); } template = 0> constexpr auto transform_error(F&& f) && noexcept { return internal::TransformError(std::move(*this), std::forward(f)); } template = 0> constexpr auto transform_error(F&& f) const&& noexcept { return internal::TransformError(std::move(*this), std::forward(f)); } private: using Impl = internal::ExpectedImpl; static constexpr auto kValTag = Impl::kValTag; static constexpr auto kErrTag = Impl::kErrTag; Impl impl_; }; // [expected.void], partial specialization of expected for void types template class [[nodiscard]] expected = */ true> { // Note: A partial specialization for non-void value types can be found above. static_assert(std::is_void_v, "Error: T must be void"); public: using value_type = T; using error_type = E; using unexpected_type = unexpected; // Alias template to explicitly opt into the std::pointer_traits machinery. // See e.g. https://en.cppreference.com/w/cpp/memory/pointer_traits#Notes template using rebind = expected; template friend class expected; // [expected.void.ctor], constructors constexpr expected() noexcept = default; // Converting copy and move constructors. These constructors are explicit if // the error type is not implicitly convertible from `rhs`'s error type. template = 0> constexpr explicit expected(const expected& rhs) noexcept : impl_(rhs.impl_) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) constexpr /* implicit */ expected(const expected& rhs) noexcept : impl_(rhs.impl_) {} template = 0> constexpr explicit expected(expected&& rhs) noexcept : impl_(std::move(rhs.impl_)) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) constexpr /* implicit */ expected(expected&& rhs) noexcept : impl_(std::move(rhs.impl_)) {} // NOLINTNEXTLINE(google-explicit-constructor) constexpr /* implicit */ expected(base::ok) noexcept {} template = 0> explicit constexpr expected(const unexpected& e) noexcept : impl_(kErrTag, e.error()) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(const unexpected& e) noexcept : impl_(kErrTag, e.error()) {} template = 0> explicit constexpr expected(unexpected&& e) noexcept : impl_(kErrTag, std::move(e.error())) {} template = 0> // NOLINTNEXTLINE(google-explicit-constructor) /* implicit */ constexpr expected(unexpected&& e) noexcept : impl_(kErrTag, std::move(e.error())) {} constexpr explicit expected(absl::in_place_t) noexcept {} template constexpr explicit expected(unexpect_t, Args&&... args) noexcept : impl_(kErrTag, std::forward(args)...) {} template constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args) noexcept : impl_(kErrTag, il, std::forward(args)...) {} // [expected.void.assign], assignment template constexpr expected& operator=(const unexpected& e) noexcept { impl_.emplace_error(e.error()); return *this; } template constexpr expected& operator=(unexpected&& e) noexcept { impl_.emplace_error(std::move(e.error())); return *this; } constexpr void emplace() noexcept { impl_.emplace_value(); } // [expected.void.swap], swap constexpr void swap(expected& rhs) noexcept { impl_.swap(rhs.impl_); } friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); } // [expected.void.obs], observers // Note: Deviation from the Standard: No operator bool due to consistency with // non-void expected types. constexpr bool has_value() const noexcept { return impl_.has_value(); } constexpr void operator*() const { CHECK(has_value()); } constexpr void value() const { CHECK(has_value()); } constexpr E& error() & { return impl_.error(); } constexpr const E& error() const& { return impl_.error(); } constexpr E&& error() && { return std::move(error()); } constexpr const E&& error() const&& { return std::move(error()); } template constexpr E error_or(G&& e) const& noexcept { static_assert(std::is_copy_constructible_v, "expected::error_or: E must be copy constructible"); static_assert(std::is_convertible_v, "expected::error_or: G must be convertible to E"); return has_value() ? static_cast(std::forward(e)) : error(); } template constexpr E error_or(G&& e) && noexcept { static_assert(std::is_move_constructible_v, "expected::error_or: E must be move constructible"); static_assert(std::is_convertible_v, "expected::error_or: G must be convertible to E"); return has_value() ? static_cast(std::forward(e)) : std::move(error()); } // [expected.void.monadic], monadic operations // // This section implements the monadic interface consisting of `and_then`, // `or_else`, `transform` and `transform_error`. In contrast to the non void // specialization it is mandated that the callables for `and_then` and // `transform`don't take any arguments. // `and_then`: This methods accepts a callable `f` that is invoked with // no arguments in case `has_value()` is true. // // `f`'s return type is required to be a (cvref-qualified) specialization of // base::expected with a matching error_type, i.e. it needs to be of the form // base::expected cv ref for some U. // // If `has_value()` is false, this is effectively a no-op, and the function // returns base::expected(unexpect, std::forward(error())); // // `and_then` is overloaded for all possible forms of const and ref // qualifiers. template = 0> constexpr auto and_then(F&& f) & noexcept { return internal::AndThen(*this, std::forward(f)); } template = 0> constexpr auto and_then(F&& f) const& noexcept { return internal::AndThen(*this, std::forward(f)); } template = 0> constexpr auto and_then(F&& f) && noexcept { return internal::AndThen(std::move(*this), std::forward(f)); } template = 0> constexpr auto and_then(F&& f) const&& noexcept { return internal::AndThen(std::move(*this), std::forward(f)); } // `or_else`: This methods accepts a callable `f` that is invoked with // `error()` in case `has_value()` is false. // // `f`'s return type is required to be a (cvref-qualified) specialization of // base::expected with a matching value_type, i.e. it needs to be cv void. // // If `has_value()` is true, this is effectively a no-op, and the function // returns base::expected(). // // `or_else` is overloaded for all possible forms of const and ref // qualifiers. template constexpr auto or_else(F&& f) & noexcept { return internal::OrElse(*this, std::forward(f)); } template constexpr auto or_else(F&& f) const& noexcept { return internal::OrElse(*this, std::forward(f)); } template constexpr auto or_else(F&& f) && noexcept { return internal::OrElse(std::move(*this), std::forward(f)); } template constexpr auto or_else(F&& f) const&& noexcept { return internal::OrElse(std::move(*this), std::forward(f)); } // `transform`: This methods accepts a callable `f` that is invoked with no // arguments in case `has_value()` is true. // // `f`'s return type U needs to be a valid value_type for expected, i.e. any // type for which `remove_cv_t` is either void, or a complete non-array object // type that is not `absl::in_place_t`, `base::unexpect_t`, or a // specialization of `base::ok` or `base::unexpected`. // // Returns an instance of base::expected, E> that is // constructed with f() if has_value() is true, or unexpected(error()) // otherwise. // // `transform` is overloaded for all possible forms of const and ref // qualifiers. template = 0> constexpr auto transform(F&& f) & noexcept { return internal::Transform(*this, std::forward(f)); } template = 0> constexpr auto transform(F&& f) const& noexcept { return internal::Transform(*this, std::forward(f)); } template = 0> constexpr auto transform(F&& f) && noexcept { return internal::Transform(std::move(*this), std::forward(f)); } template = 0> constexpr auto transform(F&& f) const&& noexcept { return internal::Transform(std::move(*this), std::forward(f)); } // `transform_error`: This methods accepts a callable `f` that is invoked with // `error()` in case `has_value()` is false. // // `f`'s return type G needs to be a valid error_type for expected, i.e. any // type for which `remove_cv_t` is a complete non-array object type that is // not `absl::in_place_t`, `base::unexpect_t`, or a specialization of // `base::ok` or `base::unexpected`. // // Returns an instance of base::expected> that is // constructed with unexpected(f(error())) if there is no value, or default // constructed otherwise. // // `transform_error` is overloaded for all possible forms of const and ref // qualifiers. template constexpr auto transform_error(F&& f) & noexcept { return internal::TransformError(*this, std::forward(f)); } template constexpr auto transform_error(F&& f) const& noexcept { return internal::TransformError(*this, std::forward(f)); } template constexpr auto transform_error(F&& f) && noexcept { return internal::TransformError(std::move(*this), std::forward(f)); } template constexpr auto transform_error(F&& f) const&& noexcept { return internal::TransformError(std::move(*this), std::forward(f)); } private: // Note: Since we can't store void types we use absl::monostate instead. using Impl = internal::ExpectedImpl; static constexpr auto kErrTag = Impl::kErrTag; Impl impl_; }; // [expected.object.eq], equality operators // [expected.void.eq], equality operators template constexpr bool operator==(const expected& x, const expected& y) noexcept { auto equal_values = [](const auto& x, const auto& y) { // Values for expected void types always compare equal. if constexpr (IsVoid) { return true; } else { return x.value() == y.value(); } }; return x.has_value() == y.has_value() && (x.has_value() ? equal_values(x, y) : x.error() == y.error()); } template = 0> constexpr bool operator==(const expected& x, const U& v) noexcept { return x.has_value() && x.value() == v; } template = 0> constexpr bool operator==(const U& v, const expected& x) noexcept { return x == v; } template = 0> constexpr bool operator==(const expected& x, const ok& o) noexcept { return x.has_value() && x.value() == o.value(); } template = 0> constexpr bool operator==(const ok& o, const expected& x) noexcept { return x == o; } template constexpr bool operator==(const expected& x, const unexpected& e) noexcept { return !x.has_value() && x.error() == e.error(); } template constexpr bool operator==(const unexpected& e, const expected& x) noexcept { return x == e; } template constexpr bool operator!=(const expected& x, const expected& y) noexcept { return !(x == y); } template = 0> constexpr bool operator!=(const expected& x, const U& v) noexcept { return !(x == v); } template = 0> constexpr bool operator!=(const U& v, const expected& x) noexcept { return !(v == x); } template = 0> constexpr bool operator!=(const expected& x, const ok& o) noexcept { return !(x == o); } template = 0> constexpr bool operator!=(const ok& o, const expected& x) noexcept { return !(o == x); } template constexpr bool operator!=(const expected& x, const unexpected& e) noexcept { return !(x == e); } template constexpr bool operator!=(const unexpected& e, const expected& x) noexcept { return !(e == x); } } // namespace base #endif // BASE_TYPES_EXPECTED_H_