[libcxx-commits] [libcxx] [libc++] Resolve LWG4366: Heterogeneous comparison of `expected` may be ill-formed (PR #185342)
William Tran-Viet via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Mar 15 20:36:43 PDT 2026
https://github.com/smallp-o-p updated https://github.com/llvm/llvm-project/pull/185342
>From 748e9c28dc0e0f2f377fd4c4985cf397570dbf3b Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Sun, 8 Mar 2026 22:54:08 -0400
Subject: [PATCH 1/4] Resolve LWG4366
---
libcxx/docs/Status/Cxx2cIssues.csv | 2 +-
libcxx/include/__expected/expected.h | 16 +++++++++++----
.../equality/equality.T2.pass.cpp | 13 ++++++++++++
.../equality/equality.unexpected.pass.cpp | 20 +++++++++++++++++++
libcxx/test/support/test_comparisons.h | 17 ++++++++++++++++
5 files changed, 63 insertions(+), 5 deletions(-)
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 60b1bd6ff70da..008c594d8dd1d 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -201,7 +201,7 @@
"`LWG4351 <https://wg21.link/LWG4351>`__","``integral-constant-like`` needs more ``remove_cvref_t``","2025-11 (Kona)","","","`#171359 <https://github.com/llvm/llvm-project/issues/171359>`__",""
"`LWG4358 <https://wg21.link/LWG4358>`__","§[exec.as.awaitable] is using ""Preconditions:"" when it should probably be described in the constraint","2025-11 (Kona)","","","`#171360 <https://github.com/llvm/llvm-project/issues/171360>`__",""
"`LWG4360 <https://wg21.link/LWG4360>`__","``awaitable-sender`` concept should qualify use of ``awaitable-receiver`` type","2025-11 (Kona)","","","`#171361 <https://github.com/llvm/llvm-project/issues/171361>`__",""
-"`LWG4366 <https://wg21.link/LWG4366>`__","Heterogeneous comparison of ``expected`` may be ill-formed","2025-11 (Kona)","","","`#171362 <https://github.com/llvm/llvm-project/issues/171362>`__",""
+"`LWG4366 <https://wg21.link/LWG4366>`__","Heterogeneous comparison of ``expected`` may be ill-formed","2025-11 (Kona)","|Complete|","22","`#171362 <https://github.com/llvm/llvm-project/issues/171362>`__",""
"`LWG4369 <https://wg21.link/LWG4369>`__","``check-types`` function for ``upon_error`` and ``upon_stopped`` is wrong","2025-11 (Kona)","","","`#171363 <https://github.com/llvm/llvm-project/issues/171363>`__",""
"`LWG4370 <https://wg21.link/LWG4370>`__","Comparison of ``optional<T>`` to ``T`` may be ill-formed","2025-11 (Kona)","|Complete|","22","`#171364 <https://github.com/llvm/llvm-project/issues/171364>`__",""
"`LWG4372 <https://wg21.link/LWG4372>`__","Weaken *Mandates:* for dynamic padding values in padded layouts","2025-11 (Kona)","","","`#171365 <https://github.com/llvm/llvm-project/issues/171365>`__",""
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 24ae33d4e3af8..fc451b6511200 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -1169,7 +1169,9 @@ class expected : private __expected_base<_Tp, _Err> {
}
# endif
{
- return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
+ if (__x.__has_val())
+ return __x.__val() == __v;
+ return false;
}
template <class _E2>
@@ -1180,7 +1182,9 @@ class expected : private __expected_base<_Tp, _Err> {
}
# endif
{
- return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
+ if (!__x.__has_val())
+ return __x.__unex() == __e.error();
+ return false;
}
};
@@ -1881,8 +1885,10 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
{
if (__x.__has_val() != __y.__has_val()) {
return false;
+ } else if (__x.__has_val()) {
+ return true;
} else {
- return __x.__has_val() || static_cast<bool>(__x.__unex() == __y.__unex());
+ return __x.__unex() == __y.__unex();
}
}
@@ -1894,7 +1900,9 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
}
# endif
{
- return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
+ if (!__x.__has_val())
+ return __x.__unex() == __y.error();
+ return false;
}
};
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 16c6986ae670e..ebd8ebabbd2c8 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
@@ -45,6 +45,19 @@ constexpr bool test() {
assert(e1 != i3);
}
+ // LWG4366
+ { // x.has_value()
+ const std::expected<ImplicitBool::E1, int> e1(ImplicitBool::E1{1});
+ assert(e1 == ImplicitBool::E2{1});
+ assert(e1 != ImplicitBool::E2{3});
+ }
+
+ { // !x.has_value()
+ const std::expected<ImplicitBool::E1, int> 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 153cbbddf3062..24a6f3d0e906e 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<int, ImplicitBool::E1> e1(std::in_place, 1);
+ const std::unexpected<ImplicitBool::E2> u2{ImplicitBool::E2{1}};
+ const std::unexpected<ImplicitBool::E2> u3{ImplicitBool::E2{2}};
+
+ assert(e1 != u2);
+ assert(e1 != u3);
+ }
+
+ { // !x.has_value()
+ const std::unexpected<ImplicitBool::E1> u1{ImplicitBool::E1{1}};
+ const std::unexpected<ImplicitBool::E2> u2{ImplicitBool::E2{1}};
+ const std::unexpected<ImplicitBool::E2> u3{ImplicitBool::E2{2}};
+
+ const std::expected<int, ImplicitBool::E1> 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 e37ab44828c70..9d8bf910a2ebc 100644
--- a/libcxx/test/support/test_comparisons.h
+++ b/libcxx/test/support/test_comparisons.h
@@ -332,6 +332,23 @@ static_assert(HasOperatorLessThanEqual<ThreeWayComparable>);
static_assert(HasOperatorNotEqual<ThreeWayComparable>);
static_assert(HasOperatorSpaceship<ThreeWayComparable>);
+// 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
>From 60aaec110a49a1b5b25994de8c7189e307b37899 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Sun, 8 Mar 2026 23:41:06 -0400
Subject: [PATCH 2/4] Typo
---
libcxx/docs/Status/Cxx2cIssues.csv | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 008c594d8dd1d..e90427c8b7386 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -201,7 +201,7 @@
"`LWG4351 <https://wg21.link/LWG4351>`__","``integral-constant-like`` needs more ``remove_cvref_t``","2025-11 (Kona)","","","`#171359 <https://github.com/llvm/llvm-project/issues/171359>`__",""
"`LWG4358 <https://wg21.link/LWG4358>`__","§[exec.as.awaitable] is using ""Preconditions:"" when it should probably be described in the constraint","2025-11 (Kona)","","","`#171360 <https://github.com/llvm/llvm-project/issues/171360>`__",""
"`LWG4360 <https://wg21.link/LWG4360>`__","``awaitable-sender`` concept should qualify use of ``awaitable-receiver`` type","2025-11 (Kona)","","","`#171361 <https://github.com/llvm/llvm-project/issues/171361>`__",""
-"`LWG4366 <https://wg21.link/LWG4366>`__","Heterogeneous comparison of ``expected`` may be ill-formed","2025-11 (Kona)","|Complete|","22","`#171362 <https://github.com/llvm/llvm-project/issues/171362>`__",""
+"`LWG4366 <https://wg21.link/LWG4366>`__","Heterogeneous comparison of ``expected`` may be ill-formed","2025-11 (Kona)","|Complete|","23","`#171362 <https://github.com/llvm/llvm-project/issues/171362>`__",""
"`LWG4369 <https://wg21.link/LWG4369>`__","``check-types`` function for ``upon_error`` and ``upon_stopped`` is wrong","2025-11 (Kona)","","","`#171363 <https://github.com/llvm/llvm-project/issues/171363>`__",""
"`LWG4370 <https://wg21.link/LWG4370>`__","Comparison of ``optional<T>`` to ``T`` may be ill-formed","2025-11 (Kona)","|Complete|","22","`#171364 <https://github.com/llvm/llvm-project/issues/171364>`__",""
"`LWG4372 <https://wg21.link/LWG4372>`__","Weaken *Mandates:* for dynamic padding values in padded layouts","2025-11 (Kona)","","","`#171365 <https://github.com/llvm/llvm-project/issues/171365>`__",""
>From d7db6a84921fb9aef23aaa202e66b716c19b711b Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Sun, 15 Mar 2026 00:14:36 -0400
Subject: [PATCH 3/4] Use a __into_bool helper
---
libcxx/include/__expected/expected.h | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index fc451b6511200..555fdfc06780e 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -446,6 +446,16 @@ class __expected_base {
_LIBCPP_NO_UNIQUE_ADDRESS __conditional_no_unique_address<__allow_reusing_expected_tail_padding, __repr> __repr_;
};
+// Helper to handle cases for LWG4366 Heterogeneous comparison of ``expected`` may be ill-formed
+// Where the comparison may produce a value that is a type that is not bool, and is implicitly
+// convertible to bool, but not explicitly.
+template <typename _ImplictlyBool>
+constexpr bool __into_bool(_ImplictlyBool&& __b)
+ requires __core_convertible_to<_ImplictlyBool, bool>
+{
+ return __b;
+}
+
template <class _Tp, class _Err>
class expected : private __expected_base<_Tp, _Err> {
static_assert(!is_reference_v<_Tp> && !is_function_v<_Tp> && !is_same_v<remove_cv_t<_Tp>, in_place_t> &&
@@ -1169,9 +1179,7 @@ class expected : private __expected_base<_Tp, _Err> {
}
# endif
{
- if (__x.__has_val())
- return __x.__val() == __v;
- return false;
+ return __x.__has_val() && __into_bool(__x.__val() == __v);
}
template <class _E2>
@@ -1182,9 +1190,7 @@ class expected : private __expected_base<_Tp, _Err> {
}
# endif
{
- if (!__x.__has_val())
- return __x.__unex() == __e.error();
- return false;
+ return !__x.__has_val() && __into_bool(__x.__unex() == __e.error());
}
};
@@ -1885,10 +1891,8 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
{
if (__x.__has_val() != __y.__has_val()) {
return false;
- } else if (__x.__has_val()) {
- return true;
} else {
- return __x.__unex() == __y.__unex();
+ return __x.__has_val() || __into_bool(__x.__unex() == __y.__unex());
}
}
@@ -1900,9 +1904,7 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
}
# endif
{
- if (!__x.__has_val())
- return __x.__unex() == __y.error();
- return false;
+ return !__x.__has_val() && __into_bool(__x.__unex() == __y.error());
}
};
>From ada0fc87c079a25eb85f40c8352adde4ca298d9a Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Sun, 15 Mar 2026 23:36:32 -0400
Subject: [PATCH 4/4] Update libcxx/include/__expected/expected.h
Co-authored-by: A. Jiang <de34 at live.cn>
---
libcxx/include/__expected/expected.h | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 555fdfc06780e..c4b1a37c7901a 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -449,10 +449,7 @@ class __expected_base {
// Helper to handle cases for LWG4366 Heterogeneous comparison of ``expected`` may be ill-formed
// Where the comparison may produce a value that is a type that is not bool, and is implicitly
// convertible to bool, but not explicitly.
-template <typename _ImplictlyBool>
-constexpr bool __into_bool(_ImplictlyBool&& __b)
- requires __core_convertible_to<_ImplictlyBool, bool>
-{
+constexpr bool __into_bool(bool __b) noexcept {
return __b;
}
More information about the libcxx-commits
mailing list