This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4366. Heterogeneous comparison of expected may be ill-formed

Section: 22.8.6.8 [expected.object.eq], 22.8.7.8 [expected.void.eq] Status: New Submitter: Hewill Kang Opened: 2025-09-06 Last modified: 2025-09-15

Priority: Not Prioritized

View all issues with New status.

Discussion:

These comparison functions all explicitly static_cast the result of the underlying comparison to bool. However, the Constraints only require the implicit conversion, not the explicit one (i.e., "convertible to bool" rather than "models boolean-testable").

This means that in some pathological cases it will lead to hard errors (demo):

#include <expected>

struct E1 {};
struct E2 {};

struct Bool {
  operator bool() const;
  explicit operator bool() = delete;
};
Bool operator==(E1, E2);

int main() {
  std::unexpected e1{E1{}};
  std::unexpected e2{E2{}};
  return std::expected<int, E1>{e1} == e2; // fire
}

It is reasonable to specify return consistency with actual Constraints.

Proposed resolution:

This wording is relative to N5014.

  1. Modify 22.8.6.8 [expected.object.eq] as indicated:

    template<class T2> friend constexpr bool operator==(const expected& x, const T2& v);
    

    -3- Constraints: T2 is not a specialization of expected. The expression *x == v is well-formed and its result is convertible to bool.

    [Note 1: T need not be Cpp17EqualityComparable. — end note]

    -4- Returns: If x.has_value() is true, && static_cast<bool>(*x == v); otherwise false.

    template<class E2> friend constexpr bool operator==(const expected& x, const unexpected<E2>& e);
    

    -5- Constraints: The expression x.error() == e.error() is well-formed and its result is convertible to bool.

    -6- Returns: If !x.has_value() is true, && static_cast<bool>(x.error() == e.error()); otherwise false.

  2. Modify 22.8.7.8 [expected.void.eq] as indicated:

    template<class T2, class E2> requires is_void_v<T2>
      friend constexpr bool operator==(const expected& x, const expected<T2, E2>& y);
    

    -1- Constraints: The expression x.error() == y.error() is well-formed and its result is convertible to bool.

    -2- Returns: If x.has_value() does not equal y.has_value(), false; otherwise if x.has_value() is true, true; otherwise || static_cast<bool>(x.error() == y.error()).

    template<class E2>
      friend constexpr bool operator==(const expected& x, const unexpected<E2>& e);
    

    -3- Constraints: The expression x.error() == e.error() is well-formed and its result is convertible to bool.

    -4- Returns: If !x.has_value() is true, && static_cast<bool>(x.error() == e.error()) ; otherwise false.