[libc++] Resolve LWG4366: Heterogeneous comparison of expected may be ill-formed (#185342)

Resolves #171362
- Implement proposed resolution
- Refactor `operator==` code to be more in line with the standard as the
current way was making an explicit `bool()` conversion in the `x.meow()
== y.meow()` cases
- Add test cases
- Update issues paper

---------

Co-authored-by: A. Jiang <de34@live.cn>
This commit is contained in:
William Tran-Viet 2026-07-01 02:49:31 -04:00 committed by GitHub
parent 4c27981352
commit 2b07bbf52c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 58 additions and 5 deletions

View file

@ -201,7 +201,7 @@
"`LWG4351 <https://wg21.link/LWG4351>`__","``integral-constant-like`` needs more ``remove_cvref_t``","2025-11 (Kona)","|Complete|","23","`#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|","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>`__",""

1 Issue # Issue Name Meeting Status First released version GitHub issue Notes
201 `LWG4351 <https://wg21.link/LWG4351>`__ ``integral-constant-like`` needs more ``remove_cvref_t`` 2025-11 (Kona) |Complete| 23 `#171359 <https://github.com/llvm/llvm-project/issues/171359>`__
202 `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>`__
203 `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>`__
204 `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>`__
205 `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>`__
206 `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>`__
207 `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>`__

View file

@ -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 _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> &&
@ -1171,7 +1175,7 @@ public:
}
# endif
{
return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
return __x.__has_val() && std::__into_bool(__x.__val() == __v);
}
template <class _E2>
@ -1182,7 +1186,7 @@ public:
}
# endif
{
return !__x.__has_val() && static_cast<bool>(__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<bool>(__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<bool>(__x.__unex() == __y.error());
return !__x.__has_val() && std::__into_bool(__x.__unex() == __y.error());
}
};

View file

@ -54,6 +54,18 @@ constexpr bool test() {
assert(p1 == p2);
}
#endif
// 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;
}

View file

@ -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;
}

View file

@ -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