[libcxx-commits] [libcxx] [libc++] Implement LWG4072: std::optional comparisons: constrain harder (PR #209968)

Dan Shan via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jul 28 22:24:20 PDT 2026


https://github.com/HumanThe2nd updated https://github.com/llvm/llvm-project/pull/209968

>From e6fec6cfe95c527721d6c5478e3c28678d975788 Mon Sep 17 00:00:00 2001
From: Dan <danielshan824 at gmail.com>
Date: Thu, 16 Jul 2026 00:12:41 -0400
Subject: [PATCH] [libc++][optional] Constrain heterogeneous comparison
 operators for nested optional

Resolves LWG4072: std::optional comparisons: constrain harder (#118345)

The heterogeneous operators (==, !=, <, <=, >, >=) for arguments T and U don't check that T or U are not a std::optional themselves

This allowed the operators to cause ambiguous overload instead of falling back to optional's own operators.

Added !__is_std_optional<_Up>::value, int> and its reverse to all twelve operators.
---
 libcxx/include/optional                       | 24 +++++++++----------
 .../optional/optional.relops/equal.pass.cpp   |  3 +++
 .../optional.relops/greater_equal.pass.cpp    |  3 +++
 .../optional.relops/greater_than.pass.cpp     |  3 +++
 .../optional.relops/less_equal.pass.cpp       |  3 +++
 .../optional.relops/less_than.pass.cpp        |  3 +++
 .../optional.relops/not_equal.pass.cpp        |  3 +++
 7 files changed, 30 insertions(+), 12 deletions(-)

diff --git a/libcxx/include/optional b/libcxx/include/optional
index ffa071c2304ba..72d3d98eb2424 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -1544,7 +1544,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr strong_ordering operator<=>(const optional<_Tp>&
 template <
     class _Tp,
     class _Up,
-    enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() == std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Up> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() == std::declval<const _Up&>()), bool>,
                 int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const optional<_Tp>& __x, const _Up& __v) {
   if (__x.has_value())
@@ -1555,7 +1555,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const optional<_Tp>& __x, const
 template <
     class _Tp,
     class _Up,
-    enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() == std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Tp> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() == std::declval<const _Up&>()), bool>,
                 int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const _Tp& __v, const optional<_Up>& __x) {
   if (__x.has_value())
@@ -1566,7 +1566,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator==(const _Tp& __v, const optional<_
 template <
     class _Tp,
     class _Up,
-    enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() != std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Up> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() != std::declval<const _Up&>()), bool>,
                 int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const optional<_Tp>& __x, const _Up& __v) {
   if (__x.has_value())
@@ -1577,7 +1577,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const optional<_Tp>& __x, const
 template <
     class _Tp,
     class _Up,
-    enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() != std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Tp> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() != std::declval<const _Up&>()), bool>,
                 int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const _Tp& __v, const optional<_Up>& __x) {
   if (__x.has_value())
@@ -1587,7 +1587,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator!=(const _Tp& __v, const optional<_
 
 template < class _Tp,
            class _Up,
-           enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() < std::declval<const _Up&>()), bool>,
+           enable_if_t<!__is_std_optional_v<_Up> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() < std::declval<const _Up&>()), bool>,
                        int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const optional<_Tp>& __x, const _Up& __v) {
   if (__x.has_value())
@@ -1597,7 +1597,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const optional<_Tp>& __x, const _
 
 template < class _Tp,
            class _Up,
-           enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() < std::declval<const _Up&>()), bool>,
+           enable_if_t<!__is_std_optional_v<_Tp> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() < std::declval<const _Up&>()), bool>,
                        int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator<(const _Tp& __v, const optional<_Up>& __x) {
   if (__x.has_value())
@@ -1608,7 +1608,7 @@ _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<decltype(std::declval<const _Tp&>() <= std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Up> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() <= std::declval<const _Up&>()), bool>,
                 int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const optional<_Tp>& __x, const _Up& __v) {
   if (__x.has_value())
@@ -1619,7 +1619,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const optional<_Tp>& __x, const
 template <
     class _Tp,
     class _Up,
-    enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() <= std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Tp> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() <= std::declval<const _Up&>()), bool>,
                 int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const _Tp& __v, const optional<_Up>& __x) {
   if (__x.has_value())
@@ -1629,7 +1629,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator<=(const _Tp& __v, const optional<_
 
 template < class _Tp,
            class _Up,
-           enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() > std::declval<const _Up&>()), bool>,
+           enable_if_t<!__is_std_optional_v<_Up> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() > std::declval<const _Up&>()), bool>,
                        int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const optional<_Tp>& __x, const _Up& __v) {
   if (__x.has_value())
@@ -1639,7 +1639,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const optional<_Tp>& __x, const _
 
 template < class _Tp,
            class _Up,
-           enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() > std::declval<const _Up&>()), bool>,
+           enable_if_t<!__is_std_optional_v<_Tp> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() > std::declval<const _Up&>()), bool>,
                        int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator>(const _Tp& __v, const optional<_Up>& __x) {
   if (__x.has_value())
@@ -1650,7 +1650,7 @@ _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<decltype(std::declval<const _Tp&>() >= std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Up> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() >= std::declval<const _Up&>()), bool>,
                 int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const optional<_Tp>& __x, const _Up& __v) {
   if (__x.has_value())
@@ -1661,7 +1661,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool operator>=(const optional<_Tp>& __x, const
 template <
     class _Tp,
     class _Up,
-    enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() >= std::declval<const _Up&>()), bool>,
+    enable_if_t<!__is_std_optional_v<_Tp> && __is_core_convertible_v<decltype(std::declval<const _Tp&>() >= std::declval<const _Up&>()), 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.relops/equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/equal.pass.cpp
index 6915b604d4ef4..dba9cbd78146c 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/equal.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/equal.pass.cpp
@@ -29,6 +29,9 @@ static_assert(HasOperatorEqual<std::optional<EqualityComparable>, std::optional<
 static_assert(!HasOperatorEqual<std::optional<NonComparable>>);
 static_assert(!HasOperatorEqual<std::optional<EqualityComparable>, std::optional<NonComparable>>);
 
+static_assert(!HasOperatorEqual<std::optional<void*>, std::optional<int>>);
+static_assert(!HasOperatorEqual<std::optional<int>, std::optional<void*>>);
+
 #endif
 
 using std::optional;
diff --git a/libcxx/test/std/utilities/optional/optional.relops/greater_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/greater_equal.pass.cpp
index d14e7df6abc80..b31f5047f1090 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/greater_equal.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/greater_equal.pass.cpp
@@ -26,6 +26,9 @@ static_assert(!HasOperatorGreaterThanEqual<std::optional<NonComparable>>);
 static_assert(!HasOperatorGreaterThanEqual<std::optional<EqualityComparable>>);
 static_assert(!HasOperatorGreaterThanEqual<std::optional<ThreeWayComparable>, std::optional<NonComparable>>);
 
+static_assert(!HasOperatorGreaterThanEqual<std::optional<void*>, std::optional<int>>);
+static_assert(!HasOperatorGreaterThanEqual<std::optional<int>, std::optional<void*>>);
+
 #endif
 
 using std::optional;
diff --git a/libcxx/test/std/utilities/optional/optional.relops/greater_than.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/greater_than.pass.cpp
index f19ea23e8ae78..dd0e039d1d342 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/greater_than.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/greater_than.pass.cpp
@@ -26,6 +26,9 @@ static_assert(!HasOperatorGreaterThan<std::optional<NonComparable>>);
 static_assert(!HasOperatorGreaterThan<std::optional<EqualityComparable>>);
 static_assert(!HasOperatorGreaterThan<std::optional<ThreeWayComparable>, std::optional<NonComparable>>);
 
+static_assert(!HasOperatorGreaterThan<std::optional<void*>, std::optional<int>>);
+static_assert(!HasOperatorGreaterThan<std::optional<int>, std::optional<void*>>);
+
 #endif
 
 using std::optional;
diff --git a/libcxx/test/std/utilities/optional/optional.relops/less_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/less_equal.pass.cpp
index bd81a79f1265e..9e472e158e3b5 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/less_equal.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/less_equal.pass.cpp
@@ -26,6 +26,9 @@ static_assert(!HasOperatorLessThanEqual<std::optional<NonComparable>>);
 static_assert(!HasOperatorLessThanEqual<std::optional<EqualityComparable>>);
 static_assert(!HasOperatorLessThanEqual<std::optional<ThreeWayComparable>, std::optional<NonComparable>>);
 
+static_assert(!HasOperatorLessThanEqual<std::optional<void*>, std::optional<int>>);
+static_assert(!HasOperatorLessThanEqual<std::optional<int>, std::optional<void*>>);
+
 #endif
 
 using std::optional;
diff --git a/libcxx/test/std/utilities/optional/optional.relops/less_than.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/less_than.pass.cpp
index c87bfd6f73e54..9c231dff56f51 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/less_than.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/less_than.pass.cpp
@@ -26,6 +26,9 @@ static_assert(!HasOperatorLessThan<std::optional<NonComparable>>);
 static_assert(!HasOperatorLessThan<std::optional<EqualityComparable>>);
 static_assert(!HasOperatorLessThan<std::optional<ThreeWayComparable>, std::optional<NonComparable>>);
 
+static_assert(!HasOperatorLessThan<std::optional<void*>, std::optional<int>>);
+static_assert(!HasOperatorLessThan<std::optional<int>, std::optional<void*>>);
+
 #endif
 
 using std::optional;
diff --git a/libcxx/test/std/utilities/optional/optional.relops/not_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/not_equal.pass.cpp
index 1b7d2621c9193..6507a61426f87 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/not_equal.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/not_equal.pass.cpp
@@ -29,6 +29,9 @@ static_assert(HasOperatorNotEqual<std::optional<EqualityComparable>, std::option
 static_assert(!HasOperatorNotEqual<std::optional<NonComparable>>);
 static_assert(!HasOperatorNotEqual<std::optional<EqualityComparable>, std::optional<NonComparable>>);
 
+static_assert(!HasOperatorNotEqual<std::optional<void*>, std::optional<int>>);
+static_assert(!HasOperatorNotEqual<std::optional<int>, std::optional<void*>>);
+
 #endif
 
 using std::optional;



More information about the libcxx-commits mailing list