Title
Heterogeneous comparison of `expected` may be ill-formed
Status
new
Section
[expected.object.eq][expected.void.eq]
Submitter
Hewill Kang

Created on 2025-09-06.00:00:00 last changed 2 days ago

Messages

Date: 2025-09-15.13:04:02

Proposed resolution:

This wording is relative to N5014.

  1. Modify [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 [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`.

Date: 2025-09-06.00:00:00

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.

History
Date User Action Args
2025-09-15 13:04:02adminsetmessages: + msg15049
2025-09-06 00:00:00admincreate