diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv index 1ea6f3ce5bec..10cad5d52e2f 100644 --- a/libcxx/docs/Status/Cxx26Issues.csv +++ b/libcxx/docs/Status/Cxx26Issues.csv @@ -201,7 +201,7 @@ "`LWG4351 `__","``integral-constant-like`` needs more ``remove_cvref_t``","2025-11 (Kona)","|Complete|","23","`#171359 `__","" "`LWG4358 `__","ยง[exec.as.awaitable] is using ""Preconditions:"" when it should probably be described in the constraint","2025-11 (Kona)","","","`#171360 `__","" "`LWG4360 `__","``awaitable-sender`` concept should qualify use of ``awaitable-receiver`` type","2025-11 (Kona)","","","`#171361 `__","" -"`LWG4366 `__","Heterogeneous comparison of ``expected`` may be ill-formed","2025-11 (Kona)","","","`#171362 `__","" +"`LWG4366 `__","Heterogeneous comparison of ``expected`` may be ill-formed","2025-11 (Kona)","|Complete|","23","`#171362 `__","" "`LWG4369 `__","``check-types`` function for ``upon_error`` and ``upon_stopped`` is wrong","2025-11 (Kona)","","","`#171363 `__","" "`LWG4370 `__","Comparison of ``optional`` to ``T`` may be ill-formed","2025-11 (Kona)","|Complete|","22","`#171364 `__","" "`LWG4372 `__","Weaken *Mandates:* for dynamic padding values in padded layouts","2025-11 (Kona)","","","`#171365 `__","" diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h index 32ed81a39270..9b0e0322106b 100644 --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -446,6 +446,10 @@ private: _LIBCPP_NO_UNIQUE_ADDRESS __conditional_no_unique_address<__allow_reusing_expected_tail_padding, __repr> __repr_; }; +// Helper to handle comparisons that produce a value whose type is not bool, +// but allows only implicit (and not explicit) conversions to bool. +constexpr bool __into_bool(bool __b) noexcept { return __b; } + template class expected : private __expected_base<_Tp, _Err> { static_assert(!is_reference_v<_Tp> && !is_function_v<_Tp> && !is_same_v, in_place_t> && @@ -1171,7 +1175,7 @@ public: } # endif { - return __x.__has_val() && static_cast(__x.__val() == __v); + return __x.__has_val() && std::__into_bool(__x.__val() == __v); } template @@ -1182,7 +1186,7 @@ public: } # endif { - return !__x.__has_val() && static_cast(__x.__unex() == __e.error()); + return !__x.__has_val() && std::__into_bool(__x.__unex() == __e.error()); } }; @@ -1884,7 +1888,7 @@ public: if (__x.__has_val() != __y.__has_val()) { return false; } else { - return __x.__has_val() || static_cast(__x.__unex() == __y.__unex()); + return __x.__has_val() || std::__into_bool(__x.__unex() == __y.__unex()); } } @@ -1896,7 +1900,7 @@ public: } # endif { - return !__x.__has_val() && static_cast(__x.__unex() == __y.error()); + return !__x.__has_val() && std::__into_bool(__x.__unex() == __y.error()); } }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp index 6a57dc464335..e0d7457808b8 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp @@ -54,6 +54,18 @@ constexpr bool test() { assert(p1 == p2); } #endif + // LWG4366 + { // x.has_value() + const std::expected e1(ImplicitBool::E1{1}); + assert(e1 == ImplicitBool::E2{1}); + assert(e1 != ImplicitBool::E2{3}); + } + + { // !x.has_value() + const std::expected e1(std::unexpect, 1); + assert(e1 != ImplicitBool::E2{1}); + assert(e1 != ImplicitBool::E2{2}); + } return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp index 153cbbddf306..24a6f3d0e906 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp @@ -45,6 +45,26 @@ constexpr bool test() { assert(e1 == un3); } + // LWG4366 + { // x.has_value() + const std::expected e1(std::in_place, 1); + const std::unexpected u2{ImplicitBool::E2{1}}; + const std::unexpected u3{ImplicitBool::E2{2}}; + + assert(e1 != u2); + assert(e1 != u3); + } + + { // !x.has_value() + const std::unexpected u1{ImplicitBool::E1{1}}; + const std::unexpected u2{ImplicitBool::E2{1}}; + const std::unexpected u3{ImplicitBool::E2{2}}; + + const std::expected e1(u1); + assert(e1 == u2); + assert(e1 != u3); + } + return true; } diff --git a/libcxx/test/support/test_comparisons.h b/libcxx/test/support/test_comparisons.h index e37ab44828c7..9d8bf910a2eb 100644 --- a/libcxx/test/support/test_comparisons.h +++ b/libcxx/test/support/test_comparisons.h @@ -332,6 +332,23 @@ static_assert(HasOperatorLessThanEqual); static_assert(HasOperatorNotEqual); static_assert(HasOperatorSpaceship); +// LWG4366: Heterogeneous comparison of expected may be ill-formed +struct ImplicitBool { + bool val; + constexpr operator bool() const { return val; }; + constexpr explicit operator bool() = delete; + + struct E1 { + int x; + }; + + struct E2 { + int y; + }; +}; + +constexpr ImplicitBool operator==(ImplicitBool::E1 lhs, ImplicitBool::E2 rhs) { return {lhs.x == rhs.y}; } + #endif // TEST_STD_VER >= 20 #endif // TEST_COMPARISONS_H