[libcxx-commits] [libcxx] [libc++] LWG4497: `std::nullopt_t` should be comparable (PR #195549)
Mohamed Atef via libcxx-commits
libcxx-commits at lists.llvm.org
Sat May 9 07:03:50 PDT 2026
https://github.com/elhewaty updated https://github.com/llvm/llvm-project/pull/195549
>From e646e821d30cb8e9bbad38549c29e3e59f5109cb Mon Sep 17 00:00:00 2001
From: Mohamed Atef <mohamedatef1698 at gmail.com>
Date: Sun, 3 May 2026 23:22:57 +0300
Subject: [PATCH 1/5] [libcxx] Make std::nullopt_t comparable
---
libcxx/docs/Status/Cxx2cIssues.csv | 2 +-
libcxx/include/optional | 3 ++
.../optional.nullopt/nullopt_t.pass.cpp | 49 +++++++++++++------
3 files changed, 38 insertions(+), 16 deletions(-)
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index c875f75528894..84330900aaa8e 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -309,7 +309,7 @@
"`LWG4492 <https://wg21.link/LWG4492>`__","``std::generate`` and ``std::ranges::generate`` wording is unclear for parallel algorithms","2026-03 (Croydon)","","","`#189851 <https://github.com/llvm/llvm-project/issues/189851>`__",""
"`LWG4493 <https://wg21.link/LWG4493>`__","Specification for some functions of bit reference types seems missing","2026-03 (Croydon)","","","`#189852 <https://github.com/llvm/llvm-project/issues/189852>`__",""
"`LWG4496 <https://wg21.link/LWG4496>`__","Precedes vs Reachable in [meta.reflection]","2026-03 (Croydon)","","","`#189853 <https://github.com/llvm/llvm-project/issues/189853>`__",""
-"`LWG4497 <https://wg21.link/LWG4497>`__","``std::nullopt_t`` should be comparable","2026-03 (Croydon)","","","`#189854 <https://github.com/llvm/llvm-project/issues/189854>`__",""
+"`LWG4497 <https://wg21.link/LWG4497>`__","``std::nullopt_t`` should be comparable","2026-03 (Croydon)","|Complete|","23","`#189854 <https://github.com/llvm/llvm-project/issues/189854>`__",""
"`LWG4499 <https://wg21.link/LWG4499>`__","``flat_set::insert_range`` specification may be problematic","2026-03 (Croydon)","","","`#189855 <https://github.com/llvm/llvm-project/issues/189855>`__",""
"`LWG4500 <https://wg21.link/LWG4500>`__","``constant_wrapper`` wording problems","2026-03 (Croydon)","|Complete|","23","`#189856 <https://github.com/llvm/llvm-project/issues/189856>`__",""
"`LWG4504 <https://wg21.link/LWG4504>`__","Wording problem in ``{simple_}counting_scope``","2026-03 (Croydon)","","","`#189857 <https://github.com/llvm/llvm-project/issues/189857>`__",""
diff --git a/libcxx/include/optional b/libcxx/include/optional
index f7e12f84beba0..7705ba49c49e7 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -362,6 +362,9 @@ struct nullopt_t {
explicit __secret_tag() = default;
};
_LIBCPP_HIDE_FROM_ABI constexpr explicit nullopt_t(__secret_tag, __secret_tag) noexcept {}
+# if _LIBCPP_STD_VER >= 26
+ _LIBCPP_HIDE_FROM_ABI constexpr strong_ordering operator<=>(const nullopt_t&) const noexcept = default;
+# endif
};
inline constexpr nullopt_t nullopt{nullopt_t::__secret_tag{}, nullopt_t::__secret_tag{}};
diff --git a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
index 842b75893cbe4..a580348e0da9a 100644
--- a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
@@ -13,31 +13,50 @@
// inline constexpr nullopt_t nullopt(unspecified);
// [optional.nullopt]/2:
-// Type nullopt_t shall not have a default constructor or an initializer-list
-// constructor, and shall not be an aggregate.
+// Type nullopt_t does not have a default constructor or an initializer-list
+// constructor, and is not an aggregate. nullopt_t models copyable and
+// three_way_comparable<strong_ordering>.
#include <optional>
#include <type_traits>
+#if _LIBCPP_STD_VER >= 26
+# include <vector>
+# include <ranges>
+# include <cassert>
+# include <algorithm>
+#endif
#include "test_macros.h"
-using std::nullopt_t;
using std::nullopt;
+using std::nullopt_t;
-constexpr bool test()
-{
- nullopt_t foo{nullopt};
- (void)foo;
- return true;
+constexpr bool test() {
+ nullopt_t foo{nullopt};
+ (void)foo;
+ return true;
}
-int main(int, char**)
-{
- static_assert(std::is_empty_v<nullopt_t>);
- static_assert(!std::is_default_constructible_v<nullopt_t>);
-
- static_assert(std::is_same_v<const nullopt_t, decltype(nullopt)>);
- static_assert(test());
+int main(int, char**) {
+ static_assert(std::is_empty_v<nullopt_t>);
+ static_assert(!std::is_default_constructible_v<nullopt_t>);
+
+ static_assert(std::is_same_v<const nullopt_t, decltype(nullopt)>);
+ static_assert(test());
+#if TEST_STD_VER >= 26
+ // Test comparisons between nullopt_t
+ static_assert(nullopt == nullopt);
+ static_assert(!(nullopt != nullopt));
+ static_assert(nullopt <= nullopt);
+ static_assert(nullopt >= nullopt);
+ static_assert((nullopt <=> nullopt) == std::strong_ordering::equal);
+
+ // Test ranges::find with nullopt
+ std::vector<std::optional<int>> v = {1, 2, nullopt, 4, 5};
+ auto itr = std::ranges::find(v, nullopt);
+ assert(itr != v.end());
+ assert(*itr == nullopt);
+#endif
return 0;
}
>From b50af237fadeaf71132cabec302b88d607f95f89 Mon Sep 17 00:00:00 2001
From: Mohamed Atef <mohamedatef1698 at gmail.com>
Date: Wed, 6 May 2026 00:30:34 +0300
Subject: [PATCH 2/5] Address comments
---
libcxx/include/optional | 10 ++++++++--
.../optional/optional.nullopt/nullopt_t.pass.cpp | 10 ++++++----
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 7705ba49c49e7..1bdc808190ba4 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -362,8 +362,14 @@ struct nullopt_t {
explicit __secret_tag() = default;
};
_LIBCPP_HIDE_FROM_ABI constexpr explicit nullopt_t(__secret_tag, __secret_tag) noexcept {}
-# if _LIBCPP_STD_VER >= 26
- _LIBCPP_HIDE_FROM_ABI constexpr strong_ordering operator<=>(const nullopt_t&) const noexcept = default;
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator==(const nullopt_t&, const nullopt_t) { return true; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator!=(const nullopt_t&, const nullopt_t) { return false; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>(const nullopt_t&, const nullopt_t) { return false; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<(const nullopt_t&, const nullopt_t) { return false; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>=(const nullopt_t&, const nullopt_t) { return true; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<=(const nullopt_t&, const nullopt_t) { return true; }
+# if _LIBCPP_STD_VER >= 20
+ _LIBCPP_HIDE_FROM_ABI constexpr friend strong_ordering operator<=>(const nullopt_t&, const nullopt_t&) = default;
# endif
};
diff --git a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
index a580348e0da9a..54b352f4f167b 100644
--- a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
@@ -19,7 +19,7 @@
#include <optional>
#include <type_traits>
-#if _LIBCPP_STD_VER >= 26
+#if _LIBCPP_STD_VER >= 20
# include <vector>
# include <ranges>
# include <cassert>
@@ -43,14 +43,16 @@ int main(int, char**) {
static_assert(std::is_same_v<const nullopt_t, decltype(nullopt)>);
static_assert(test());
-#if TEST_STD_VER >= 26
- // Test comparisons between nullopt_t
+
static_assert(nullopt == nullopt);
static_assert(!(nullopt != nullopt));
static_assert(nullopt <= nullopt);
static_assert(nullopt >= nullopt);
- static_assert((nullopt <=> nullopt) == std::strong_ordering::equal);
+ static_assert(!(nullopt > nullopt));
+ static_assert(!(nullopt < nullopt));
+#if TEST_STD_VER >= 20
+ static_assert((nullopt <=> nullopt) == std::strong_ordering::equal);
// Test ranges::find with nullopt
std::vector<std::optional<int>> v = {1, 2, nullopt, 4, 5};
auto itr = std::ranges::find(v, nullopt);
>From 898f82308944b8e46c4490b6ce28be479a9c2ca4 Mon Sep 17 00:00:00 2001
From: Mohamed Atef <mohamedatef1698 at gmail.com>
Date: Wed, 6 May 2026 10:29:02 +0300
Subject: [PATCH 3/5] Enable only <=> operator in c++20 and later
---
libcxx/include/optional | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 1bdc808190ba4..55301ae7f331b 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -362,14 +362,15 @@ struct nullopt_t {
explicit __secret_tag() = default;
};
_LIBCPP_HIDE_FROM_ABI constexpr explicit nullopt_t(__secret_tag, __secret_tag) noexcept {}
+# if _LIBCPP_STD_VER >= 20
+ _LIBCPP_HIDE_FROM_ABI constexpr friend strong_ordering operator<=>(const nullopt_t&, const nullopt_t&) = default;
+# else
_LIBCPP_HIDE_FROM_ABI constexpr friend bool operator==(const nullopt_t&, const nullopt_t) { return true; }
_LIBCPP_HIDE_FROM_ABI constexpr friend bool operator!=(const nullopt_t&, const nullopt_t) { return false; }
_LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>(const nullopt_t&, const nullopt_t) { return false; }
_LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<(const nullopt_t&, const nullopt_t) { return false; }
_LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>=(const nullopt_t&, const nullopt_t) { return true; }
_LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<=(const nullopt_t&, const nullopt_t) { return true; }
-# if _LIBCPP_STD_VER >= 20
- _LIBCPP_HIDE_FROM_ABI constexpr friend strong_ordering operator<=>(const nullopt_t&, const nullopt_t&) = default;
# endif
};
>From 75f9c15db4b556fba5c8b840c28eecb01c41a548 Mon Sep 17 00:00:00 2001
From: Mohamed Atef <mohamedatef1698 at gmail.com>
Date: Fri, 8 May 2026 23:40:54 +0300
Subject: [PATCH 4/5] Address comments
---
libcxx/include/optional | 16 +++---
.../compare.three_way.pass.cpp | 53 +++++++++++++++++++
.../optional.nullopt/nullopt_t.pass.cpp | 27 +---------
3 files changed, 63 insertions(+), 33 deletions(-)
create mode 100644 libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 55301ae7f331b..b25e780a54a8a 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -362,15 +362,15 @@ struct nullopt_t {
explicit __secret_tag() = default;
};
_LIBCPP_HIDE_FROM_ABI constexpr explicit nullopt_t(__secret_tag, __secret_tag) noexcept {}
-# if _LIBCPP_STD_VER >= 20
- _LIBCPP_HIDE_FROM_ABI constexpr friend strong_ordering operator<=>(const nullopt_t&, const nullopt_t&) = default;
+# if _LIBCPP_STD_VER <= 17
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator==(nullopt_t, nullopt_t) { return true; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator!=(nullopt_t, nullopt_t) { return false; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>(nullopt_t, nullopt_t) { return false; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<(nullopt_t, nullopt_t) { return false; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>=(nullopt_t, nullopt_t) { return true; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<=(nullopt_t, nullopt_t) { return true; }
# else
- _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator==(const nullopt_t&, const nullopt_t) { return true; }
- _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator!=(const nullopt_t&, const nullopt_t) { return false; }
- _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>(const nullopt_t&, const nullopt_t) { return false; }
- _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<(const nullopt_t&, const nullopt_t) { return false; }
- _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator>=(const nullopt_t&, const nullopt_t) { return true; }
- _LIBCPP_HIDE_FROM_ABI constexpr friend bool operator<=(const nullopt_t&, const nullopt_t) { return true; }
+ _LIBCPP_HIDE_FROM_ABI constexpr friend strong_ordering operator<=>(nullopt_t, nullopt_t) = default;
# endif
};
diff --git a/libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp b/libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp
new file mode 100644
index 0000000000000..291d219bbd6b1
--- /dev/null
+++ b/libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14
+// <optional>
+
+// struct nullopt_t{see below};
+// inline constexpr nullopt_t nullopt(unspecified);
+
+// [optional.nullopt]/2:
+// nullopt_t models copyable and three_way_comparable<strong_ordering>.
+
+#include <ratio>
+#include <vector>
+#include <ranges>
+#include <cassert>
+#include <algorithm>
+#include <optional>
+
+#include "test_macros.h"
+
+using std::nullopt;
+
+constexpr bool test() {
+ static_assert(nullopt == nullopt);
+ static_assert(!(nullopt != nullopt));
+ static_assert(nullopt <= nullopt);
+ static_assert(nullopt >= nullopt);
+ static_assert(!(nullopt > nullopt));
+ static_assert(!(nullopt < nullopt));
+
+#if TEST_STD_VER > 17
+ static_assert((nullopt <=> nullopt) == std::strong_ordering::equal);
+ // Test ranges::find with nullopt
+ std::vector<std::optional<int>> v = {1, 2, nullopt, 4, 5};
+ auto itr = std::ranges::find(v, nullopt);
+ assert(itr != v.end());
+ assert(*itr == nullopt);
+#endif
+
+ return true;
+}
+
+int main(int, char**) {
+ static_assert(test());
+
+ return 0;
+}
diff --git a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
index 54b352f4f167b..47ffa262ee7c2 100644
--- a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
@@ -13,18 +13,11 @@
// inline constexpr nullopt_t nullopt(unspecified);
// [optional.nullopt]/2:
-// Type nullopt_t does not have a default constructor or an initializer-list
-// constructor, and is not an aggregate. nullopt_t models copyable and
-// three_way_comparable<strong_ordering>.
+// Type nullopt_t shall not have a default constructor or an initializer-list
+// constructor, and shall not be an aggregate.
#include <optional>
#include <type_traits>
-#if _LIBCPP_STD_VER >= 20
-# include <vector>
-# include <ranges>
-# include <cassert>
-# include <algorithm>
-#endif
#include "test_macros.h"
@@ -44,21 +37,5 @@ int main(int, char**) {
static_assert(std::is_same_v<const nullopt_t, decltype(nullopt)>);
static_assert(test());
- static_assert(nullopt == nullopt);
- static_assert(!(nullopt != nullopt));
- static_assert(nullopt <= nullopt);
- static_assert(nullopt >= nullopt);
- static_assert(!(nullopt > nullopt));
- static_assert(!(nullopt < nullopt));
-
-#if TEST_STD_VER >= 20
- static_assert((nullopt <=> nullopt) == std::strong_ordering::equal);
- // Test ranges::find with nullopt
- std::vector<std::optional<int>> v = {1, 2, nullopt, 4, 5};
- auto itr = std::ranges::find(v, nullopt);
- assert(itr != v.end());
- assert(*itr == nullopt);
-#endif
-
return 0;
}
>From 1bfa5477c5e37e47092a0ebaf6dacea84fe88a35 Mon Sep 17 00:00:00 2001
From: Mohamed Atef <mohamedatef1698 at gmail.com>
Date: Sat, 9 May 2026 15:58:46 +0300
Subject: [PATCH 5/5] Finalize comparable nullopt
---
.../compare.three_way.pass.cpp | 16 +++++++++-----
.../optional.nullopt/nullopt_t.pass.cpp | 22 ++++++++++---------
2 files changed, 23 insertions(+), 15 deletions(-)
diff --git a/libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp b/libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp
index 291d219bbd6b1..476c2f5fd33b8 100644
--- a/libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.nullopt/compare.three_way.pass.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14
+// REQUIRES: std-at-least-c++17
// <optional>
// struct nullopt_t{see below};
@@ -15,17 +15,21 @@
// [optional.nullopt]/2:
// nullopt_t models copyable and three_way_comparable<strong_ordering>.
-#include <ratio>
-#include <vector>
-#include <ranges>
-#include <cassert>
#include <algorithm>
+#include <cassert>
+#include <concepts>
#include <optional>
+#include <vector>
#include "test_macros.h"
using std::nullopt;
+#if TEST_STD_VER >= 20
+static_assert(std::copyable<std::nullopt_t>);
+static_assert(std::three_way_comparable<std::nullopt_t, std::strong_ordering>);
+#endif
+
constexpr bool test() {
static_assert(nullopt == nullopt);
static_assert(!(nullopt != nullopt));
@@ -47,6 +51,8 @@ constexpr bool test() {
}
int main(int, char**) {
+ test();
+
static_assert(test());
return 0;
diff --git a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
index 47ffa262ee7c2..842b75893cbe4 100644
--- a/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.nullopt/nullopt_t.pass.cpp
@@ -21,21 +21,23 @@
#include "test_macros.h"
-using std::nullopt;
using std::nullopt_t;
+using std::nullopt;
-constexpr bool test() {
- nullopt_t foo{nullopt};
- (void)foo;
- return true;
+constexpr bool test()
+{
+ nullopt_t foo{nullopt};
+ (void)foo;
+ return true;
}
-int main(int, char**) {
- static_assert(std::is_empty_v<nullopt_t>);
- static_assert(!std::is_default_constructible_v<nullopt_t>);
+int main(int, char**)
+{
+ static_assert(std::is_empty_v<nullopt_t>);
+ static_assert(!std::is_default_constructible_v<nullopt_t>);
- static_assert(std::is_same_v<const nullopt_t, decltype(nullopt)>);
- static_assert(test());
+ static_assert(std::is_same_v<const nullopt_t, decltype(nullopt)>);
+ static_assert(test());
return 0;
}
More information about the libcxx-commits
mailing list