From 4ebfcc24659f8b6a8551826c97a1fbdaf524ffc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Fri, 21 Nov 2025 15:21:35 +0100 Subject: [PATCH 1/7] Add tests. --- tests/std/tests/P0323R12_expected/test.cpp | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/std/tests/P0323R12_expected/test.cpp b/tests/std/tests/P0323R12_expected/test.cpp index f9cdbdaa04f..19f1614ca7a 100644 --- a/tests/std/tests/P0323R12_expected/test.cpp +++ b/tests/std/tests/P0323R12_expected/test.cpp @@ -2524,6 +2524,34 @@ static_assert(!is_constructible_v, unexpect_t>); static_assert(!is_constructible_v, const unexpect_t>); +// Test LWG-4366 "Heterogeneous comparison of expected may be ill-formed" +// Test taken from an example in the issue text +namespace test_lwg_4366 { + + struct E1 {}; + struct E2 {}; + + struct Bool { + constexpr operator bool() const { + return false; + } + constexpr explicit operator bool() = delete; + }; + + constexpr Bool operator==(E1, E2) { + return {}; + } + + constexpr void test() { + unexpected e1{E1{}}; + unexpected e2{E2{}}; + (void)(expected{e1} == e2); + (void)(expected{e1} == e2); + (void)(e1 == e2); + (void)(expected{e1} == expected{e2}); + } +} // namespace test_lwg_4366 + int main() { test_unexpected::test_all(); static_assert(test_unexpected::test_all()); From ce99452c006330b5af8b7413248d7a4b52f31185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Fri, 21 Nov 2025 15:22:20 +0100 Subject: [PATCH 2/7] Remove explicit cast to bool from heterogeneous comparisons of expected. --- stl/inc/expected | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/stl/inc/expected b/stl/inc/expected index 92f5590c529..a714e2446f5 100644 --- a/stl/inc/expected +++ b/stl/inc/expected @@ -1159,9 +1159,9 @@ public: template _NODISCARD friend constexpr bool operator==(const expected& _Left, const _Uty& _Right) - noexcept(noexcept(static_cast(_Left._Value == _Right))) /* strengthened */ { + noexcept(noexcept(_Left._Value == _Right)) /* strengthened */ { if (_Left._Has_value) { - return static_cast(_Left._Value == _Right); + return _Left._Value == _Right; } else { return false; } @@ -1169,11 +1169,11 @@ public: template _NODISCARD friend constexpr bool operator==(const expected& _Left, const unexpected<_UErr>& _Right) - noexcept(noexcept(static_cast(_Left._Unexpected == _Right.error()))) /* strengthened */ { + noexcept(noexcept(_Left._Unexpected == _Right.error())) /* strengthened */ { if (_Left._Has_value) { return false; } else { - return static_cast(_Left._Unexpected == _Right.error()); + return _Left._Unexpected == _Right.error(); } } @@ -1890,21 +1890,25 @@ public: template requires is_void_v<_Uty> _NODISCARD friend constexpr bool operator==(const expected& _Left, const expected<_Uty, _UErr>& _Right) - noexcept(noexcept(static_cast(_Left._Unexpected == _Right.error()))) /* strengthened */ { + noexcept(noexcept(_Left._Unexpected == _Right.error())) /* strengthened */ { if (_Left._Has_value != _Right.has_value()) { return false; } else { - return _Left._Has_value || static_cast(_Left._Unexpected == _Right.error()); + if (_Left._Has_value) { + return true; + } else { + return _Left._Unexpected == _Right.error(); + } } } template _NODISCARD friend constexpr bool operator==(const expected& _Left, const unexpected<_UErr>& _Right) - noexcept(noexcept(static_cast(_Left._Unexpected == _Right.error()))) /* strengthened */ { + noexcept(noexcept(_Left._Unexpected == _Right.error())) /* strengthened */ { if (_Left._Has_value) { return false; } else { - return static_cast(_Left._Unexpected == _Right.error()); + return _Left._Unexpected == _Right.error(); } } From a9fe5c257f9757237692d926b23353024d81c935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Fri, 21 Nov 2025 15:25:49 +0100 Subject: [PATCH 3/7] Test all combinations of expected, expected and unexpected as operands of operator== --- tests/std/tests/P0323R12_expected/test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/std/tests/P0323R12_expected/test.cpp b/tests/std/tests/P0323R12_expected/test.cpp index 19f1614ca7a..506a8c7f118 100644 --- a/tests/std/tests/P0323R12_expected/test.cpp +++ b/tests/std/tests/P0323R12_expected/test.cpp @@ -2549,6 +2549,7 @@ namespace test_lwg_4366 { (void)(expected{e1} == e2); (void)(e1 == e2); (void)(expected{e1} == expected{e2}); + (void)(expected{e1} == expected{e2}); } } // namespace test_lwg_4366 From 53fa6df49900a8deba76f75f2637138f3fa5e728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Fri, 21 Nov 2025 15:29:26 +0100 Subject: [PATCH 4/7] Correct formatting of tests. --- tests/std/tests/P0323R12_expected/test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/std/tests/P0323R12_expected/test.cpp b/tests/std/tests/P0323R12_expected/test.cpp index 506a8c7f118..e6a88fadf88 100644 --- a/tests/std/tests/P0323R12_expected/test.cpp +++ b/tests/std/tests/P0323R12_expected/test.cpp @@ -2545,11 +2545,11 @@ namespace test_lwg_4366 { constexpr void test() { unexpected e1{E1{}}; unexpected e2{E2{}}; - (void)(expected{e1} == e2); - (void)(expected{e1} == e2); - (void)(e1 == e2); - (void)(expected{e1} == expected{e2}); - (void)(expected{e1} == expected{e2}); + (void) (expected{e1} == e2); + (void) (expected{e1} == e2); + (void) (e1 == e2); + (void) (expected{e1} == expected{e2}); + (void) (expected{e1} == expected{e2}); } } // namespace test_lwg_4366 From 235a8d9837d5cb3193e65b04c1364da262bb1dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Sun, 23 Nov 2025 19:54:09 +0100 Subject: [PATCH 5/7] Test noexcept-ness of implicit conversion from operator=='s result to bool. --- stl/inc/expected | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stl/inc/expected b/stl/inc/expected index a714e2446f5..2f093ce3a86 100644 --- a/stl/inc/expected +++ b/stl/inc/expected @@ -1159,7 +1159,7 @@ public: template _NODISCARD friend constexpr bool operator==(const expected& _Left, const _Uty& _Right) - noexcept(noexcept(_Left._Value == _Right)) /* strengthened */ { + noexcept(noexcept(_STD _Fake_copy_init(_Left._Value == _Right))) /* strengthened */ { if (_Left._Has_value) { return _Left._Value == _Right; } else { @@ -1169,7 +1169,7 @@ public: template _NODISCARD friend constexpr bool operator==(const expected& _Left, const unexpected<_UErr>& _Right) - noexcept(noexcept(_Left._Unexpected == _Right.error())) /* strengthened */ { + noexcept(noexcept(_STD _Fake_copy_init(_Left._Unexpected == _Right.error()))) /* strengthened */ { if (_Left._Has_value) { return false; } else { @@ -1890,7 +1890,7 @@ public: template requires is_void_v<_Uty> _NODISCARD friend constexpr bool operator==(const expected& _Left, const expected<_Uty, _UErr>& _Right) - noexcept(noexcept(_Left._Unexpected == _Right.error())) /* strengthened */ { + noexcept(noexcept(_STD _Fake_copy_init(_Left._Unexpected == _Right.error()))) /* strengthened */ { if (_Left._Has_value != _Right.has_value()) { return false; } else { @@ -1904,7 +1904,7 @@ public: template _NODISCARD friend constexpr bool operator==(const expected& _Left, const unexpected<_UErr>& _Right) - noexcept(noexcept(_Left._Unexpected == _Right.error())) /* strengthened */ { + noexcept(noexcept(_STD _Fake_copy_init(_Left._Unexpected == _Right.error()))) /* strengthened */ { if (_Left._Has_value) { return false; } else { From be72118baf55ff54323605169b6086a9a559465d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Sun, 23 Nov 2025 21:59:03 +0100 Subject: [PATCH 6/7] Test that noexcept-ness of operator== is properly determined from underlying functions. --- tests/std/tests/P0323R12_expected/test.cpp | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/std/tests/P0323R12_expected/test.cpp b/tests/std/tests/P0323R12_expected/test.cpp index e6a88fadf88..738edcd12d9 100644 --- a/tests/std/tests/P0323R12_expected/test.cpp +++ b/tests/std/tests/P0323R12_expected/test.cpp @@ -2551,6 +2551,44 @@ namespace test_lwg_4366 { (void) (expected{e1} == expected{e2}); (void) (expected{e1} == expected{e2}); } + + template + struct bool_with_noexcept { + constexpr operator bool() const noexcept(has_noexcept_operator_bool) { + return false; + } + }; + + // Test that operator== has correct noexcept specification (based on noexcept specs of underlying expressions) + + struct E3 {}; + template + struct E4 {}; + + template + constexpr bool_with_noexcept operator==(const E3, const E4) noexcept { + return {}; + } + + template + constexpr void test2() { + unexpected e3{E3{}}; + unexpected e4{E4{}}; + + (void) (expected{e3} == e4); + (void) (expected{e3} == e4); + (void) (e3 == e4); + (void) (expected{e3} == expected>{e4}); + (void) (expected{e3} == expected>{e4}); + + static_assert(has_noexcept_operator_bool == noexcept(expected{e3} == e4)); + static_assert(has_noexcept_operator_bool == noexcept(expected{e3} == e4)); + static_assert(has_noexcept_operator_bool == noexcept(e3 == e4)); + static_assert(has_noexcept_operator_bool + == noexcept(expected{e3} == expected>{e4})); + static_assert(has_noexcept_operator_bool + == noexcept(expected{e3} == expected>{e4})); + } } // namespace test_lwg_4366 int main() { @@ -2572,4 +2610,8 @@ int main() { test_lwg_3886(); test_lwg_3886_volatile(); test_inherited_constructors(); + + test_lwg_4366::test(); + test_lwg_4366::test2(); + test_lwg_4366::test2(); } From 14357f3ebbd1ab7edaeda1908f1c642850751d0f Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 24 Nov 2025 13:45:25 -0800 Subject: [PATCH 7/7] Code review feedback. --- tests/std/tests/P0323R12_expected/test.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/std/tests/P0323R12_expected/test.cpp b/tests/std/tests/P0323R12_expected/test.cpp index 738edcd12d9..a67913c784c 100644 --- a/tests/std/tests/P0323R12_expected/test.cpp +++ b/tests/std/tests/P0323R12_expected/test.cpp @@ -2527,7 +2527,6 @@ static_assert(!is_constructible_v - constexpr bool_with_noexcept operator==(const E3, const E4) noexcept { + constexpr bool_with_noexcept operator==(E3, E4) noexcept { return {}; }