On Thu, Nov 20, 2025 at 6:01 PM Jonathan Wakely <[email protected]> wrote:

> This modifies the equality comparisons for std::expected so that they do
> not use explicit conversions to bool, to match the constraints which are
> specified in terms of "convertible to" which implies implicitly
> convertible. As a result of those changes, we cannot use logical
> expressions with && or || that involve comparisons of the contained
> values, because x && (*y == *z) might do the wrong thing if *y == *z
> does not return bool.
>
> Also add [[nodiscard]] attributes which were missing.
>
> The new lwg4366.cc testcase is a dg-do run test not dg-do compile,
> because the original example won't compile with libstdc++ even after
> these fixes. We constrain the std::expected comparison operators with
> std::convertible_to<bool> and the pathological Bool type in the issue
> doesn't satisfy that concept. So the new test replaces the deleted
> explicit conversion oeprator in the issue with one that isn't deleted
> but terminates if called. This ensures we don't call it, thus ensuring
> that std::expected's comparisons do implicit conversions only.
>
> It's unclear to me whether using the convertible_to concept in
> std::expected comparisons is conforming, or if we should switch to an
> __implicitly_convertible_to<bool> concept which only uses
> std::is_convertible_v<T, bool> and doesn't check for explicit
> conversions. That can be addressed separately from this change.
>
I think it should be conforming, and if it's not we should change the
standard
and not. So totally agree on a separate issue.

>
> libstdc++-v3/ChangeLog:
>
>         * include/std/expected (operator==): Use implicit conversion to
>         bool and do not use logical && and || with operands of unknown
>         types. Add nodiscard attributes.
>         * testsuite/20_util/expected/equality.cc: Test some missing
>         cases which were not covered previously.
>         * testsuite/20_util/expected/lwg4366.cc: New test.
> ---
>
> Tested x86_64-linux.
>
LGTM

>
>  libstdc++-v3/include/std/expected             | 37 ++++++++++++++-----
>  .../testsuite/20_util/expected/equality.cc    | 26 +++++++++++++
>  .../testsuite/20_util/expected/lwg4366.cc     | 33 +++++++++++++++++
>  3 files changed, 87 insertions(+), 9 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/20_util/expected/lwg4366.cc
>
> diff --git a/libstdc++-v3/include/std/expected
> b/libstdc++-v3/include/std/expected
> index 591fc72a4388..a03a7d3f722f 100644
> --- a/libstdc++-v3/include/std/expected
> +++ b/libstdc++-v3/include/std/expected
> @@ -1163,15 +1163,17 @@ namespace __expected
>             { __t == __u } -> convertible_to<bool>;
>             { __e == __e2 } -> convertible_to<bool>;
>           }
> +       [[nodiscard]]
>         friend constexpr bool
>         operator==(const expected& __x, const expected<_Up, _Er2>& __y)
>         noexcept(noexcept(bool(*__x == *__y))
>                   && noexcept(bool(__x.error() == __y.error())))
>         {
> +         if (__x.has_value() != __y.has_value())
> +           return false;
>           if (__x.has_value())
> -           return __y.has_value() && bool(*__x == *__y);
> -         else
> -           return !__y.has_value() && bool(__x.error() == __y.error());
> +           return *__x == *__y;
> +         return __x.error() == __y.error();
>         }
>
>        template<typename _Up, same_as<_Tp> _Vp>
> @@ -1179,19 +1181,29 @@ namespace __expected
>           && requires (const _Tp& __t, const _Up& __u) {
>             { __t == __u } -> convertible_to<bool>;
>           }
> +       [[nodiscard]]
>         friend constexpr bool
>         operator==(const expected<_Vp, _Er>& __x, const _Up& __v)
>         noexcept(noexcept(bool(*__x == __v)))
> -       { return __x.has_value() && bool(*__x == __v); }
> +       {
> +         if (__x.has_value())
> +           return *__x == __v;
> +         return false;
> +       }
>
>        template<typename _Er2>
>         requires requires (const _Er& __e, const _Er2& __e2) {
>           { __e == __e2 } -> convertible_to<bool>;
>         }
> +       [[nodiscard]]
>         friend constexpr bool
>         operator==(const expected& __x, const unexpected<_Er2>& __e)
>         noexcept(noexcept(bool(__x.error() == __e.error())))
> -       { return !__x.has_value() && bool(__x.error() == __e.error()); }
> +       {
> +         if (!__x.has_value())
> +           return __x.error() == __e.error();
> +         return false;
> +       }
>
>        friend constexpr void
>        swap(expected& __x, expected& __y)
> @@ -1841,24 +1853,31 @@ namespace __expected
>           && requires (const _Er& __e, const _Er2& __e2) {
>             { __e == __e2 } -> convertible_to<bool>;
>           }
> +       [[nodiscard]]
>         friend constexpr bool
>         operator==(const expected& __x, const expected<_Up, _Er2>& __y)
>
We

>         noexcept(noexcept(bool(__x.error() == __y.error())))

        {
> +         if (__x.has_value() != __y.has_value())
> +           return false;
>           if (__x.has_value())
> -           return __y.has_value();
> -         else
> -           return !__y.has_value() && bool(__x.error() == __y.error());
> +           return true;
> +         return __x.error() == __y.error();
>         }
>
>        template<typename _Er2>
>         requires requires (const _Er& __e, const _Er2& __e2) {
>           { __e == __e2 } -> convertible_to<bool>;
>         }
> +       [[nodiscard]]
>         friend constexpr bool
>         operator==(const expected& __x, const unexpected<_Er2>& __e)
>         noexcept(noexcept(bool(__x.error() == __e.error())))
> -       { return !__x.has_value() && bool(__x.error() == __e.error()); }
> +       {
> +         if (!__x.has_value())
> +           return __x.error() == __e.error();
> +         return false;
> +       }
>
>        friend constexpr void
>        swap(expected& __x, expected& __y)
> diff --git a/libstdc++-v3/testsuite/20_util/expected/equality.cc
> b/libstdc++-v3/testsuite/20_util/expected/equality.cc
> index db19b1510a73..cc122f469082 100644
> --- a/libstdc++-v3/testsuite/20_util/expected/equality.cc
> +++ b/libstdc++-v3/testsuite/20_util/expected/equality.cc
> @@ -28,19 +28,45 @@ test_eq()
>    std::expected<int, int> e2;
>    VERIFY(e2 == e2);
>    VERIFY(e1 == e2);
> +  VERIFY(e2 == e1);
>    VERIFY(e1 != std::unexpected<int>(1));
> +
>    e1 = std::unexpected<int>(1);
>    VERIFY(e1 == std::unexpected<int>(1));
>    VERIFY(e1 != std::unexpected<int>(2));
>    VERIFY(e1 != e2);
> +  VERIFY(e2 != e1);
> +  VERIFY(e1 != 1);
> +
> +  e2 = std::unexpected<int>(1);
> +  VERIFY(e1 == e2);
> +  VERIFY(e2 == e1);
> +
> +  e2 = std::unexpected<int>(2);
> +  VERIFY(e1 != e2);
> +  VERIFY(e2 != e1);
>
>    std::expected<void, int> e3;
>    VERIFY(e3 == e3);
>    VERIFY(e3 != std::unexpected<int>(1));
> +  std::expected<const void, long> e4;
> +  VERIFY(e3 == e4);
> +  VERIFY(e4 == e3);
> +
>    e3 = std::unexpected<int>(1);
>    VERIFY(e3 == e3);
>    VERIFY(e3 == std::unexpected<int>(1));
>    VERIFY(e3 != std::unexpected<int>(2));
> +  VERIFY(e3 != e4);
> +  VERIFY(e4 != e3);
> +
> +  e4 = e3;
> +  VERIFY(e3 == e4);
> +  VERIFY(e4 == e3);
> +
> +  e4 = std::unexpected<int>(4);
> +  VERIFY(e3 != e4);
> +  VERIFY(e4 != e3);
>
>    return true;
>  }
> diff --git a/libstdc++-v3/testsuite/20_util/expected/lwg4366.cc
> b/libstdc++-v3/testsuite/20_util/expected/lwg4366.cc
> new file mode 100644
> index 000000000000..35d53714f036
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/expected/lwg4366.cc
> @@ -0,0 +1,33 @@
> +// { dg-do run { target c++23 } }
> +
> +// LWG 4366. Heterogeneous comparison of expected may be ill-formed
> +
> +#include <expected>
> +#include <testsuite_hooks.h>
> +
> +struct Bool
> +{
> +  operator bool() const { return true; }
> +  explicit operator bool() { throw; }
> +};
> +
> +struct E1 {
> +  friend Bool operator==(E1, E1) { return {}; }
> +} e1;
> +
> +struct E2 {
> +  friend Bool operator==(E1, E2) { return {}; }
> +} e2;
> +
> +int main()
> +{
> +  std::expected<int, E1> u1(std::unexpect, e1);
> +  VERIFY(u1 == u1);
> +
> +  std::unexpected<E2> u2(e2);
> +  VERIFY(u1 == u2);
> +
> +  std::expected<void, E1> u3(std::unexpect, e1);
> +  VERIFY(u3 == u3);
> +  VERIFY(u3 == u2);
> +}
> --
> 2.51.1
>
>

Reply via email to