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

Dan Shan via libcxx-commits libcxx-commits at lists.llvm.org
Sun Aug 2 06:47:56 PDT 2026


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

>From 246e54199dca725addfb8c4d8ea5c446bd7801b2 Mon Sep 17 00:00:00 2001
From: Dan <danielshan824 at gmail.com>
Date: Fri, 31 Jul 2026 09:06:01 -0400
Subject: [PATCH] [libc++][optional] Constrain heterogeneous comparison
 operators for nested optional

---
 libcxx/include/__optional/comparison.h        | 65 ++++++++++++-------
 .../optional.comp_with_t/equal.pass.cpp       |  3 +
 .../optional.comp_with_t/greater.pass.cpp     |  3 +
 .../greater_equal.pass.cpp                    |  3 +
 .../optional.comp_with_t/less_equal.pass.cpp  |  3 +
 .../optional.comp_with_t/less_than.pass.cpp   |  3 +
 .../optional.comp_with_t/not_equal.pass.cpp   |  3 +
 7 files changed, 59 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<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())
@@ -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<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())
@@ -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<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())
@@ -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<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())
@@ -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<decltype(std::declval<const _Tp&>() < std::declval<const _Up&>()), bool>,
-                       int> = 0>
+template <
+    class _Tp,
+    class _Up,
+    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())
     return *__x < __v;
   return true;
 }
 
-template < class _Tp,
-           class _Up,
-           enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() < std::declval<const _Up&>()), bool>,
-                       int> = 0>
+template <
+    class _Tp,
+    class _Up,
+    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())
     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<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())
@@ -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<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())
@@ -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<decltype(std::declval<const _Tp&>() > std::declval<const _Up&>()), bool>,
-                       int> = 0>
+template <
+    class _Tp,
+    class _Up,
+    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())
     return *__x > __v;
   return false;
 }
 
-template < class _Tp,
-           class _Up,
-           enable_if_t<__is_core_convertible_v<decltype(std::declval<const _Tp&>() > std::declval<const _Up&>()), bool>,
-                       int> = 0>
+template <
+    class _Tp,
+    class _Up,
+    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())
     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<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())
@@ -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<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.comp_with_t/equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/equal.pass.cpp
index 54965b270dcce..1a3c9b7b869fe 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,9 @@ static_assert(HasOperatorEqual<std::optional<EqualityComparable>, EqualityCompar
 static_assert(!HasOperatorEqual<std::optional<NonComparable>, NonComparable>);
 static_assert(!HasOperatorEqual<std::optional<EqualityComparable>, 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.comp_with_t/greater.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater.pass.cpp
index b63b85b000930..919cda73d88a3 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,9 @@ static_assert(!HasOperatorGreaterThan<NonComparable, std::optional<NonComparable
 static_assert(!HasOperatorGreaterThan<NonComparable, std::optional<ThreeWayComparable>>);
 static_assert(!HasOperatorGreaterThan<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.comp_with_t/greater_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/greater_equal.pass.cpp
index bb1a8c551d3d5..d8626203f3b3c 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,9 @@ static_assert(!HasOperatorGreaterThanEqual<NonComparable, std::optional<NonCompa
 static_assert(!HasOperatorGreaterThanEqual<NonComparable, std::optional<ThreeWayComparable>>);
 static_assert(!HasOperatorGreaterThanEqual<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.comp_with_t/less_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_equal.pass.cpp
index 8b08ec3deea5a..45c5a050ecab9 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,9 @@ static_assert(!HasOperatorLessThanEqual<NonComparable, std::optional<NonComparab
 static_assert(!HasOperatorLessThanEqual<NonComparable, std::optional<ThreeWayComparable>>);
 static_assert(!HasOperatorLessThanEqual<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.comp_with_t/less_than.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/less_than.pass.cpp
index 3ef23a3224520..1933fb80796ae 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,9 @@ static_assert(!HasOperatorLessThan<NonComparable, std::optional<NonComparable>>)
 static_assert(!HasOperatorLessThan<NonComparable, std::optional<ThreeWayComparable>>);
 static_assert(!HasOperatorLessThan<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.comp_with_t/not_equal.pass.cpp b/libcxx/test/std/utilities/optional/optional.comp_with_t/not_equal.pass.cpp
index 97fb8ee447393..a272ecb2cc9c7 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,9 @@ static_assert(HasOperatorNotEqual<std::optional<EqualityComparable>, EqualityCom
 static_assert(!HasOperatorNotEqual<std::optional<NonComparable>, NonComparable>);
 static_assert(!HasOperatorNotEqual<std::optional<EqualityComparable>, 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