[libcxx-commits] [libcxx] [libc++] Resolve LWG4370 (PR #174062)
William Tran-Viet via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Dec 30 23:24:22 PST 2025
https://github.com/smallp-o-p updated https://github.com/llvm/llvm-project/pull/174062
>From a8fcee78fa25676cc55c03d0cb440a5fab296363 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Wed, 31 Dec 2025 01:13:47 -0500
Subject: [PATCH 1/4] Resolve LWG4370
---
libcxx/docs/Status/Cxx2cIssues.csv | 2 +-
libcxx/include/optional | 60 +++++++++++++++----
.../optional.relops/relops.compile.pass.cpp | 42 +++++++++++++
3 files changed, 91 insertions(+), 13 deletions(-)
create mode 100644 libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 32f5117a0198a..8d312cd688daa 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -203,7 +203,7 @@
"`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>`__",""
"`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)","","","`#171364 <https://github.com/llvm/llvm-project/issues/171364>`__",""
+"`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>`__",""
"`LWG4375 <https://wg21.link/LWG4375>`__","``std::simd::bit_ceil`` should not be ``noexcept``","2025-11 (Kona)","","","`#171366 <https://github.com/llvm/llvm-project/issues/171366>`__",""
"`LWG4376 <https://wg21.link/LWG4376>`__","ABI tag in return type of [simd.mask.unary] is overconstrained","2025-11 (Kona)","","","`#171367 <https://github.com/llvm/llvm-project/issues/171367>`__",""
diff --git a/libcxx/include/optional b/libcxx/include/optional
index bea7474d49298..6d21225f1ac45 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -1405,7 +1405,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? *__x == __v : false;
+ if(__x.has_value()) {
+ return *__x == __v;
+ }
+ return false;
}
template <
@@ -1414,7 +1417,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? __v == *__x : false;
+ if(__x.has_value()) {
+ return __v == *__x;
+ }
+ return false;
}
template <
@@ -1423,7 +1429,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? *__x != __v : true;
+ if(__x.has_value()) {
+ return *__x != __v;
+ }
+ return true;
}
template <
@@ -1432,7 +1441,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? __v != *__x : true;
+ if(__x.has_value()) {
+ return __v != *__x;
+ }
+ return true;
}
template < class _Tp,
@@ -1440,7 +1452,10 @@ template < class _Tp,
enable_if_t<__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) {
- return static_cast<bool>(__x) ? *__x < __v : true;
+ if(__x.has_value()) {
+ return *__x < __v;
+ }
+ return true;
}
template < class _Tp,
@@ -1448,7 +1463,10 @@ template < class _Tp,
enable_if_t<__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) {
- return static_cast<bool>(__x) ? __v < *__x : false;
+ if(__x.has_value()) {
+ return __v < *__x;
+ }
+ return false;
}
template <
@@ -1457,7 +1475,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? *__x <= __v : true;
+ if(__x.has_value()) {
+ return *__x <= __v;
+ }
+ return true;
}
template <
@@ -1466,7 +1487,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? __v <= *__x : false;
+ if(__x.has_value()) {
+ return __v <= *__x;
+ }
+ return false;
}
template < class _Tp,
@@ -1474,7 +1498,10 @@ template < class _Tp,
enable_if_t<__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) {
- return static_cast<bool>(__x) ? *__x > __v : false;
+ if(__x.has_value()) {
+ return *__x > __v;
+ }
+ return false;
}
template < class _Tp,
@@ -1482,7 +1509,10 @@ template < class _Tp,
enable_if_t<__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) {
- return static_cast<bool>(__x) ? __v > *__x : true;
+ if(__x.has_value()){
+ return __v > *__x;
+ }
+ return true;
}
template <
@@ -1491,7 +1521,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? *__x >= __v : false;
+ if(__x.has_value()) {
+ return *__x >= __v;
+ }
+ return false;
}
template <
@@ -1500,7 +1533,10 @@ template <
enable_if_t<__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) {
- return static_cast<bool>(__x) ? __v >= *__x : true;
+ if(__x.has_value()) {
+ return __v >= *__x;
+ }
+ return true;
}
# if _LIBCPP_STD_VER >= 20
diff --git a/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
new file mode 100644
index 0000000000000..5babd389cec1e
--- /dev/null
+++ b/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++17
+// <optional>
+
+// Verify that example provided for LWG4370 compiles.
+
+#include <cassert>
+#include <optional>
+
+struct Bool {
+ Bool(bool) {};
+ operator bool() const { return true; };
+};
+
+struct S {
+ Bool operator==(S) const { return true; }
+ Bool operator!=(S) const { return true; }
+ Bool operator<=(S) const { return true; }
+ Bool operator<(S) const { return true; }
+ Bool operator>(S) const { return true; }
+ Bool operator>=(S) const { return true; }
+};
+
+int main() {
+ std::optional<S> s{S{}};
+
+ (void)(s == S{});
+ (void)(s != S{});
+ (void)(s < S{});
+ (void)(s > S{});
+ (void)(s <= S{});
+ (void)(s >= S{});
+
+ return 0;
+}
>From 3dfd9983a287f51b1d75098a1da0e8f81ab78e35 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Wed, 31 Dec 2025 01:35:17 -0500
Subject: [PATCH 2/4] Formatting
---
libcxx/include/optional | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 6d21225f1ac45..d77eb3cbcc3e0 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -1405,7 +1405,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return *__x == __v;
}
return false;
@@ -1417,7 +1417,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return __v == *__x;
}
return false;
@@ -1429,7 +1429,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return *__x != __v;
}
return true;
@@ -1441,7 +1441,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return __v != *__x;
}
return true;
@@ -1452,7 +1452,7 @@ template < class _Tp,
enable_if_t<__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()) {
+ if (__x.has_value()) {
return *__x < __v;
}
return true;
@@ -1463,7 +1463,7 @@ template < class _Tp,
enable_if_t<__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()) {
+ if (__x.has_value()) {
return __v < *__x;
}
return false;
@@ -1475,7 +1475,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return *__x <= __v;
}
return true;
@@ -1487,7 +1487,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return __v <= *__x;
}
return false;
@@ -1498,7 +1498,7 @@ template < class _Tp,
enable_if_t<__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()) {
+ if (__x.has_value()) {
return *__x > __v;
}
return false;
@@ -1509,7 +1509,7 @@ template < class _Tp,
enable_if_t<__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()){
+ if (__x.has_value()) {
return __v > *__x;
}
return true;
@@ -1521,7 +1521,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return *__x >= __v;
}
return false;
@@ -1533,7 +1533,7 @@ template <
enable_if_t<__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()) {
+ if (__x.has_value()) {
return __v >= *__x;
}
return true;
>From 04e14558f9e1301159fe4ca413a0cc95fc0aa6e9 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Wed, 31 Dec 2025 02:22:58 -0500
Subject: [PATCH 3/4] Update
libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
Co-authored-by: A. Jiang <de34 at live.cn>
---
.../utilities/optional/optional.relops/relops.compile.pass.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
index 5babd389cec1e..6530d04035286 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
@@ -9,7 +9,8 @@
// REQUIRES: std-at-least-c++17
// <optional>
-// Verify that example provided for LWG4370 compiles.
+// Verify that comparison operators of `optional` accept underlying return types convertible both from and to `bool`
+// as required by LWG4370.
#include <cassert>
#include <optional>
>From d20bc491abe91e39388f796d6d017ca335df51f8 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Wed, 31 Dec 2025 02:24:11 -0500
Subject: [PATCH 4/4] Make it clearer that it's a compile-only test
---
.../optional/optional.relops/relops.compile.pass.cpp | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp b/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
index 6530d04035286..cbe1d035f2008 100644
--- a/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.relops/relops.compile.pass.cpp
@@ -12,7 +12,6 @@
// Verify that comparison operators of `optional` accept underlying return types convertible both from and to `bool`
// as required by LWG4370.
-#include <cassert>
#include <optional>
struct Bool {
@@ -29,7 +28,7 @@ struct S {
Bool operator>=(S) const { return true; }
};
-int main() {
+constexpr void test() {
std::optional<S> s{S{}};
(void)(s == S{});
@@ -38,6 +37,4 @@ int main() {
(void)(s > S{});
(void)(s <= S{});
(void)(s >= S{});
-
- return 0;
}
More information about the libcxx-commits
mailing list