From 53a75b04346f49ad628dcd67956fe6045258c6f7 Mon Sep 17 00:00:00 2001 From: Dan <3shandan@gmail.com> Date: Sun, 6 Sep 2026 20:59:14 -0400 Subject: [PATCH 1/3] [libc++][optional] Constrain heterogeneous comparison operators for nested optional --- libcxx/include/__optional/comparison.h | 65 ++++++++++++------- .../optional.comp_with_t/equal.pass.cpp | 4 ++ .../optional.comp_with_t/greater.pass.cpp | 4 ++ .../greater_equal.pass.cpp | 4 ++ .../optional.comp_with_t/less_equal.pass.cpp | 4 ++ .../optional.comp_with_t/less_than.pass.cpp | 4 ++ .../optional.comp_with_t/not_equal.pass.cpp | 4 ++ 7 files changed, 65 insertions(+), 24 deletions(-) diff --git a/libcxx/include/__optional/comparison.h b/libcxx/include/__optional/comparison.h index ba5cddb1acbfb..20e7c68632ce2 100644 --- a/libcxx/include/__optional/comparison.h +++ b/libcxx/include/__optional/comparison.h @@ -13,6 +13,7 @@ #include <__compare/ordering.h> #include <__compare/three_way_comparable.h> #include <__config> +#include <__optional/common.h> #include <__optional/nullopt_t.h> #include <__type_traits/enable_if.h> #include <__type_traits/is_constructible.h> @@ -201,7 +202,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr strong_ordering operator<=>(const optional<_Tp>& template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() == std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() == std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const optional<_Tp>& __x, const _Up& __v) { if (__x.has_value()) @@ -212,7 +214,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const optional<_Tp>& __x, const template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() == std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() == std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const _Tp& __v, const optional<_Up>& __x) { if (__x.has_value()) @@ -223,7 +226,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const _Tp& __v, const optional<_ template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() != std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() != std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const optional<_Tp>& __x, const _Up& __v) { if (__x.has_value()) @@ -234,7 +238,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const optional<_Tp>& __x, const template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() != std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() != std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const _Tp& __v, const optional<_Up>& __x) { if (__x.has_value()) @@ -242,20 +247,24 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const _Tp& __v, const optional<_ return true; } -template < class _Tp, - class _Up, - enable_if_t<__is_core_convertible_v() < std::declval()), bool>, - int> = 0> +template < + class _Tp, + class _Up, + enable_if_t && + __is_core_convertible_v() < std::declval()), bool>, + int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const optional<_Tp>& __x, const _Up& __v) { if (__x.has_value()) return *__x < __v; return true; } -template < class _Tp, - class _Up, - enable_if_t<__is_core_convertible_v() < std::declval()), bool>, - int> = 0> +template < + class _Tp, + class _Up, + enable_if_t && + __is_core_convertible_v() < std::declval()), bool>, + int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const _Tp& __v, const optional<_Up>& __x) { if (__x.has_value()) return __v < *__x; @@ -265,7 +274,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const _Tp& __v, const optional<_U template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() <= std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() <= std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const optional<_Tp>& __x, const _Up& __v) { if (__x.has_value()) @@ -276,7 +286,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const optional<_Tp>& __x, const template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() <= std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() <= std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const _Tp& __v, const optional<_Up>& __x) { if (__x.has_value()) @@ -284,20 +295,24 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const _Tp& __v, const optional<_ return false; } -template < class _Tp, - class _Up, - enable_if_t<__is_core_convertible_v() > std::declval()), bool>, - int> = 0> +template < + class _Tp, + class _Up, + enable_if_t && + __is_core_convertible_v() > std::declval()), bool>, + int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const optional<_Tp>& __x, const _Up& __v) { if (__x.has_value()) return *__x > __v; return false; } -template < class _Tp, - class _Up, - enable_if_t<__is_core_convertible_v() > std::declval()), bool>, - int> = 0> +template < + class _Tp, + class _Up, + enable_if_t && + __is_core_convertible_v() > std::declval()), bool>, + int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const _Tp& __v, const optional<_Up>& __x) { if (__x.has_value()) return __v > *__x; @@ -307,7 +322,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const _Tp& __v, const optional<_U template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() >= std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() >= std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const optional<_Tp>& __x, const _Up& __v) { if (__x.has_value()) @@ -318,7 +334,8 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const optional<_Tp>& __x, const template < class _Tp, class _Up, - enable_if_t<__is_core_convertible_v() >= std::declval()), bool>, + enable_if_t && + __is_core_convertible_v() >= std::declval()), bool>, int> = 0> _LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const _Tp& __v, const optional<_Up>& __x) { if (__x.has_value()) diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp index 54965b270dcce..831027148ac58 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp @@ -35,6 +35,10 @@ static_assert(HasOperatorEqual, EqualityCompar static_assert(!HasOperatorEqual, NonComparable>); static_assert(!HasOperatorEqual, NonComparable>); +// LWG4072: avoid ambiguity with optional's own comparison operators +static_assert(!HasOperatorEqual, std::optional>); +static_assert(!HasOperatorEqual, std::optional>); + #endif using std::optional; diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp index b63b85b000930..6af588d171d05 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp @@ -34,6 +34,10 @@ static_assert(!HasOperatorGreaterThan>); static_assert(!HasOperatorGreaterThan>); +// LWG4072: avoid ambiguity with optional's own comparison operators +static_assert(!HasOperatorGreaterThan, std::optional>); +static_assert(!HasOperatorGreaterThan, std::optional>); + #endif using std::optional; diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp index bb1a8c551d3d5..3ae7f4536e0ae 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp @@ -34,6 +34,10 @@ static_assert(!HasOperatorGreaterThanEqual>); static_assert(!HasOperatorGreaterThanEqual>); +// LWG4072: avoid ambiguity with optional's own comparison operators +static_assert(!HasOperatorGreaterThanEqual, std::optional>); +static_assert(!HasOperatorGreaterThanEqual, std::optional>); + #endif using std::optional; diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp index 8b08ec3deea5a..43313e679b1f7 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp @@ -34,6 +34,10 @@ static_assert(!HasOperatorLessThanEqual>); static_assert(!HasOperatorLessThanEqual>); +// LWG4072: avoid ambiguity with optional's own comparison operators +static_assert(!HasOperatorLessThanEqual, std::optional>); +static_assert(!HasOperatorLessThanEqual, std::optional>); + #endif using std::optional; diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp index 3ef23a3224520..6f3ccf4db8bf4 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp @@ -34,6 +34,10 @@ static_assert(!HasOperatorLessThan>) static_assert(!HasOperatorLessThan>); static_assert(!HasOperatorLessThan>); +// LWG4072: avoid ambiguity with optional's own comparison operators +static_assert(!HasOperatorLessThan, std::optional>); +static_assert(!HasOperatorLessThan, std::optional>); + #endif using std::optional; diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp index 97fb8ee447393..74f1658625d7d 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp @@ -35,6 +35,10 @@ static_assert(HasOperatorNotEqual, EqualityCom static_assert(!HasOperatorNotEqual, NonComparable>); static_assert(!HasOperatorNotEqual, NonComparable>); +// LWG4072: avoid ambiguity with optional's own comparison operators +static_assert(!HasOperatorNotEqual, std::optional>); +static_assert(!HasOperatorNotEqual, std::optional>); + #endif using std::optional; From 15af8d4c4f57061b09e037659f6c1a8408c0305b Mon Sep 17 00:00:00 2001 From: Dan <3shandan@gmail.com> Date: Sun, 6 Sep 2026 20:59:15 -0400 Subject: [PATCH 2/3] Mark LWG4072 as complete in Cxx26Issues.csv --- libcxx/docs/Status/Cxx26Issues.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv index cf30ba8a45ca5..5c5fba32de855 100644 --- a/libcxx/docs/Status/Cxx26Issues.csv +++ b/libcxx/docs/Status/Cxx26Issues.csv @@ -87,7 +87,7 @@ "`LWG4027 `__","``possibly-const-range`` should prefer returning ``const R&``","2024-11 (Wrocław)","","","`#118342 `__","" "`LWG4044 `__","Confusing requirements for ``std::print`` on POSIX platforms","2024-11 (Wrocław)","","","`#118343 `__","" "`LWG4064 `__","Clarify that ``std::launder`` is not needed when using the result of ``std::memcpy``","2024-11 (Wrocław)","","","`#118344 `__","" -"`LWG4072 `__","``std::optional`` comparisons: constrain harder","2024-11 (Wrocław)","","","`#118345 `__","" +"`LWG4072 `__","``std::optional`` comparisons: constrain harder","2024-11 (Wrocław)","|Complete|","24","`#118345 `__","" "`LWG4084 `__","``std::fixed`` ignores ``std::uppercase``","2024-11 (Wrocław)","|Complete|","","`#118346 `__","" "`LWG4085 `__","``ranges::generate_random``'s helper lambda should specify the return type","2024-11 (Wrocław)","","","`#118347 `__","" "`LWG4088 `__","``println`` ignores the locale imbued in ``std::ostream``","2024-11 (Wrocław)","|Complete|","18","`#118348 `__","" From f6b7a55692bf1882d91f1529c3f405649c557ad6 Mon Sep 17 00:00:00 2001 From: Dan <3shandan@gmail.com> Date: Sun, 6 Sep 2026 21:11:36 -0400 Subject: [PATCH 3/3] restore accidental rollback --- .../optional.comp_with_t/equal.pass.cpp | 23 +++++++++++++++++-- .../optional.comp_with_t/greater.pass.cpp | 23 +++++++++++++++++-- .../greater_equal.pass.cpp | 23 +++++++++++++++++-- .../optional.comp_with_t/less_equal.pass.cpp | 23 +++++++++++++++++-- .../optional.comp_with_t/less_than.pass.cpp | 23 +++++++++++++++++-- .../optional.comp_with_t/not_equal.pass.cpp | 23 +++++++++++++++++-- 6 files changed, 126 insertions(+), 12 deletions(-) diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp index 831027148ac58..99813dae89031 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp @@ -19,6 +19,25 @@ #if TEST_STD_VER >= 26 +struct tester {}; + +template , int> = 0> // intentionally underconstrained +constexpr bool operator==(const tester& a, const T& t) { + return t.compare(a) == 0; +} +template , int> = 0> // intentionally underconstrained +constexpr bool operator==(const T& t, const tester& a) { + return t.compare(a) == 0; +} + +template +constexpr std::optional try_cmp_eq(const T& t, const U& u) { + if constexpr (requires { t == u; }) + return t == u; + else + return {}; +} + // Test SFINAE. static_assert(HasOperatorEqual>); @@ -36,8 +55,8 @@ static_assert(!HasOperatorEqual, NonComparable>); static_assert(!HasOperatorEqual, NonComparable>); // LWG4072: avoid ambiguity with optional's own comparison operators -static_assert(!HasOperatorEqual, std::optional>); -static_assert(!HasOperatorEqual, std::optional>); +static_assert(try_cmp_eq(std::optional{}, std::optional{}) == std::optional{}); +static_assert(try_cmp_eq(std::optional{}, std::optional{}) == std::optional{true}); #endif diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp index 6af588d171d05..a945cf4b54002 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp @@ -19,6 +19,25 @@ #if TEST_STD_VER >= 26 +struct tester {}; + +template , int> = 0> // intentionally underconstrained +constexpr bool operator>(const tester& a, const T& t) { + return t.compare(a) < 0; +} +template , int> = 0> // intentionally underconstrained +constexpr bool operator>(const T& t, const tester& a) { + return t.compare(a) > 0; +} + +template +constexpr std::optional try_cmp_gt(const T& t, const U& u) { + if constexpr (requires { t > u; }) + return t > u; + else + return {}; +} + // Test SFINAE. static_assert(HasOperatorGreaterThan, int>); static_assert(HasOperatorGreaterThan, ThreeWayComparable>); @@ -35,8 +54,8 @@ static_assert(!HasOperatorGreaterThan>); // LWG4072: avoid ambiguity with optional's own comparison operators -static_assert(!HasOperatorGreaterThan, std::optional>); -static_assert(!HasOperatorGreaterThan, std::optional>); +static_assert(try_cmp_gt(std::optional{}, std::optional{}) == std::optional{}); +static_assert(try_cmp_gt(std::optional{}, std::optional{}) == std::optional{false}); #endif diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp index 3ae7f4536e0ae..8e18568bfe6d7 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp @@ -19,6 +19,25 @@ #if TEST_STD_VER >= 26 +struct tester {}; + +template , int> = 0> // intentionally underconstrained +constexpr bool operator>=(const tester& a, const T& t) { + return t.compare(a) <= 0; +} +template , int> = 0> // intentionally underconstrained +constexpr bool operator>=(const T& t, const tester& a) { + return t.compare(a) >= 0; +} + +template +constexpr std::optional try_cmp_gteq(const T& t, const U& u) { + if constexpr (requires { t >= u; }) + return t >= u; + else + return {}; +} + // Test SFINAE. static_assert(HasOperatorGreaterThanEqual, int>); static_assert(HasOperatorGreaterThanEqual, ThreeWayComparable>); @@ -35,8 +54,8 @@ static_assert(!HasOperatorGreaterThanEqual>); // LWG4072: avoid ambiguity with optional's own comparison operators -static_assert(!HasOperatorGreaterThanEqual, std::optional>); -static_assert(!HasOperatorGreaterThanEqual, std::optional>); +static_assert(try_cmp_gteq(std::optional{}, std::optional{}) == std::optional{}); +static_assert(try_cmp_gteq(std::optional{}, std::optional{}) == std::optional{true}); #endif diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp index 43313e679b1f7..c937d7b2a5faf 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp @@ -19,6 +19,25 @@ #if TEST_STD_VER >= 26 +struct tester {}; + +template , int> = 0> // intentionally underconstrained +constexpr bool operator<=(const tester& a, const T& t) { + return t.compare(a) >= 0; +} +template , int> = 0> // intentionally underconstrained +constexpr bool operator<=(const T& t, const tester& a) { + return t.compare(a) <= 0; +} + +template +constexpr std::optional try_cmp_lteq(const T& t, const U& u) { + if constexpr (requires { t <= u; }) + return t <= u; + else + return {}; +} + // Test SFINAE. static_assert(HasOperatorLessThanEqual, int>); static_assert(HasOperatorLessThanEqual, ThreeWayComparable>); @@ -35,8 +54,8 @@ static_assert(!HasOperatorLessThanEqual>); // LWG4072: avoid ambiguity with optional's own comparison operators -static_assert(!HasOperatorLessThanEqual, std::optional>); -static_assert(!HasOperatorLessThanEqual, std::optional>); +static_assert(try_cmp_lteq(std::optional{}, std::optional{}) == std::optional{}); +static_assert(try_cmp_lteq(std::optional{}, std::optional{}) == std::optional{true}); #endif diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp index 6f3ccf4db8bf4..9a1aa0c65f272 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp @@ -19,6 +19,25 @@ #if TEST_STD_VER >= 26 +struct tester {}; + +template , int> = 0> // intentionally underconstrained +constexpr bool operator<(const tester& a, const T& t) { + return t.compare(a) > 0; +} +template , int> = 0> // intentionally underconstrained +constexpr bool operator<(const T& t, const tester& a) { + return t.compare(a) < 0; +} + +template +constexpr std::optional try_cmp_lt(const T& t, const U& u) { + if constexpr (requires { t < u; }) + return t < u; + else + return {}; +} + // Test SFINAE. static_assert(HasOperatorLessThan, int>); static_assert(HasOperatorLessThan, ThreeWayComparable>); @@ -35,8 +54,8 @@ static_assert(!HasOperatorLessThan>); // LWG4072: avoid ambiguity with optional's own comparison operators -static_assert(!HasOperatorLessThan, std::optional>); -static_assert(!HasOperatorLessThan, std::optional>); +static_assert(try_cmp_lt(std::optional{}, std::optional{}) == std::optional{}); +static_assert(try_cmp_lt(std::optional{}, std::optional{}) == std::optional{false}); #endif diff --git a/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp index 74f1658625d7d..e7940b9b421a3 100644 --- a/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp +++ b/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp @@ -19,6 +19,25 @@ #if TEST_STD_VER >= 26 +struct tester {}; + +template , int> = 0> // intentionally underconstrained +constexpr bool operator!=(const tester& a, const T& t) { + return t.compare(a) != 0; +} +template , int> = 0> // intentionally underconstrained +constexpr bool operator!=(const T& t, const tester& a) { + return t.compare(a) != 0; +} + +template +constexpr std::optional try_cmp_neq(const T& t, const U& u) { + if constexpr (requires { t != u; }) + return t != u; + else + return {}; +} + // Test SFINAE. static_assert(HasOperatorNotEqual>); @@ -36,8 +55,8 @@ static_assert(!HasOperatorNotEqual, NonComparable>) static_assert(!HasOperatorNotEqual, NonComparable>); // LWG4072: avoid ambiguity with optional's own comparison operators -static_assert(!HasOperatorNotEqual, std::optional>); -static_assert(!HasOperatorNotEqual, std::optional>); +static_assert(try_cmp_neq(std::optional{}, std::optional{}) == std::optional{}); +static_assert(try_cmp_neq(std::optional{}, std::optional{}) == std::optional{false}); #endif