-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[libc++] Implement P3379R0 Constrain std::expected
equality operators
#135759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-libcxx Author: None (yronglin) ChangesThis PR implement P3379R0 Constrain std::expected equality operators. Full diff: https://github.com/llvm/llvm-project/pull/135759.diff 8 Files Affected:
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index b40d9eb58c24e..be591c9b6a587 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -78,7 +78,7 @@
"","","","","",""
"`P3136R1 <https://wg21.link/P3136R1>`__","Retiring niebloids","2024-11 (Wrocław)","","",""
"`P3138R5 <https://wg21.link/P3138R5>`__","``views::cache_latest``","2024-11 (Wrocław)","","",""
-"`P3379R0 <https://wg21.link/P3379R0>`__","Constrain ``std::expected`` equality operators","2024-11 (Wrocław)","","",""
+"`P3379R0 <https://wg21.link/P3379R0>`__","Constrain ``std::expected`` equality operators","2024-11 (Wrocław)","|Complete|","21",""
"`P2862R1 <https://wg21.link/P2862R1>`__","``text_encoding::name()`` should never return null values","2024-11 (Wrocław)","","",""
"`P2897R7 <https://wg21.link/P2897R7>`__","``aligned_accessor``: An ``mdspan`` accessor expressing pointer over-alignment","2024-11 (Wrocław)","","",""
"`P3355R1 <https://wg21.link/P3355R1>`__","Fix ``submdspan`` for C++26","2024-11 (Wrocław)","","",""
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 03bbd1623ed5c..f8af6429c05ae 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -10,6 +10,7 @@
#define _LIBCPP___EXPECTED_EXPECTED_H
#include <__assert>
+#include <__concepts/boolean_testable.h>
#include <__config>
#include <__expected/bad_expected_access.h>
#include <__expected/unexpect.h>
@@ -1139,8 +1140,12 @@ class expected : private __expected_base<_Tp, _Err> {
// [expected.object.eq], equality operators
template <class _T2, class _E2>
- requires(!is_void_v<_T2>)
- _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
+ requires(!is_void_v<_T2>) && requires {
+ { *__x == *__y } -> __boolean_testable;
+ { __x.error() == __y.error() } -> __boolean_testable;
+ }
+ {
if (__x.__has_val() != __y.__has_val()) {
return false;
} else {
@@ -1153,12 +1158,20 @@ class expected : private __expected_base<_Tp, _Err> {
}
template <class _T2>
- _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v) {
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v)
+ requires(!__is_std_expected<_T2>::value) && requires {
+ { *__x == __v } -> __boolean_testable;
+ }
+ {
return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
}
template <class _E2>
- _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e) {
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e)
+ requires requires {
+ { __x.error() == __e.error() } -> __boolean_testable;
+ }
+ {
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
}
};
@@ -1851,7 +1864,11 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
// [expected.void.eq], equality operators
template <class _T2, class _E2>
requires is_void_v<_T2>
- _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
+ requires requires {
+ { __x.error() == __y.error() } -> __boolean_testable;
+ }
+ {
if (__x.__has_val() != __y.__has_val()) {
return false;
} else {
@@ -1860,7 +1877,11 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
}
template <class _E2>
- _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y) {
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y)
+ requires requires {
+ { __x.error() == __y.error() } -> __boolean_testable;
+ }
+ {
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
}
};
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
index bc8b9de97e4d2..2959834a85dd1 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp
@@ -17,18 +17,17 @@
#include <utility>
#include "test_macros.h"
+#include "../../types.h"
-struct Data {
- int i;
- constexpr Data(int ii) : i(ii) {}
-
- friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
-};
+// https://wg21.link/P3379R0
+static_assert(CanCompare<std::expected<int, int>, int>);
+static_assert(CanCompare<std::expected<int, int>, EqualityComparable>);
+static_assert(!CanCompare<std::expected<int, int>, NonComparable>);
constexpr bool test() {
// x.has_value()
{
- const std::expected<Data, int> e1(std::in_place, 5);
+ const std::expected<EqualityComparable, int> e1(std::in_place, 5);
int i2 = 10;
int i3 = 5;
assert(e1 != i2);
@@ -37,7 +36,7 @@ constexpr bool test() {
// !x.has_value()
{
- const std::expected<Data, int> e1(std::unexpect, 5);
+ const std::expected<EqualityComparable, int> e1(std::unexpect, 5);
int i2 = 10;
int i3 = 5;
assert(e1 != i2);
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
index 9325c6c61ad2d..e2da668728e0d 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp
@@ -18,19 +18,21 @@
#include <utility>
#include "test_macros.h"
+#include "../../types.h"
// Test constraint
-template <class T1, class T2>
-concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
-
-struct Foo{};
-static_assert(!CanCompare<Foo, Foo>);
+static_assert(!CanCompare<NonComparable, NonComparable>);
static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
static_assert(CanCompare<std::expected<int, int>, std::expected<short, short>>);
-// Note this is true because other overloads are unconstrained
-static_assert(CanCompare<std::expected<int, int>, std::expected<void, int>>);
+// https://wg21.link/P3379R0
+static_assert(!CanCompare<std::expected<int, int>, std::expected<void, int>>);
+static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
+static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<NonComparable, int>>);
+static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<int, NonComparable>>);
+static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<int, NonComparable>>);
+static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<NonComparable, int>>);
constexpr bool test() {
// x.has_value() && y.has_value()
diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
index a8c469d01be28..cd2db0efc48b1 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp
@@ -17,18 +17,17 @@
#include <utility>
#include "test_macros.h"
+#include "../../types.h"
-struct Data {
- int i;
- constexpr Data(int ii) : i(ii) {}
-
- friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
-};
+// https://wg21.link/P3379R0
+static_assert(CanCompare<std::expected<EqualityComparable, EqualityComparable>, std::unexpected<int>>);
+static_assert(CanCompare<std::expected<EqualityComparable, int>, std::unexpected<EqualityComparable>>);
+static_assert(!CanCompare<std::expected<EqualityComparable, NonComparable>, std::unexpected<int>>);
constexpr bool test() {
// x.has_value()
{
- const std::expected<Data, Data> e1(std::in_place, 5);
+ const std::expected<EqualityComparable, EqualityComparable> e1(std::in_place, 5);
std::unexpected<int> un2(10);
std::unexpected<int> un3(5);
assert(e1 != un2);
@@ -37,7 +36,7 @@ constexpr bool test() {
// !x.has_value()
{
- const std::expected<Data, Data> e1(std::unexpect, 5);
+ const std::expected<EqualityComparable, EqualityComparable> e1(std::unexpect, 5);
std::unexpected<int> un2(10);
std::unexpected<int> un3(5);
assert(e1 != un2);
diff --git a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
index 8b24875586852..224cbc610e78b 100644
--- a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp
@@ -18,10 +18,7 @@
#include <utility>
#include "test_macros.h"
-
-// Test constraint
-template <class T1, class T2>
-concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
+#include "../../types.h"
struct Foo{};
static_assert(!CanCompare<Foo, Foo>);
@@ -29,8 +26,13 @@ static_assert(!CanCompare<Foo, Foo>);
static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
static_assert(CanCompare<std::expected<void, int>, std::expected<void, short>>);
-// Note this is true because other overloads in expected<non-void> are unconstrained
-static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
+// https://wg21.link/P3379R0
+static_assert(!CanCompare<std::expected<void, int>, std::expected<int, int>>);
+static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
+static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
+static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, NonComparable>>);
+static_assert(!CanCompare<std::expected<void, int>, std::expected<void, NonComparable>>);
+static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, int>>);
constexpr bool test() {
// x.has_value() && y.has_value()
diff --git a/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp
index 4500971131b65..4d9afaf24e3a6 100644
--- a/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp
@@ -17,18 +17,17 @@
#include <utility>
#include "test_macros.h"
+#include "../../types.h"
-struct Data {
- int i;
- constexpr Data(int ii) : i(ii) {}
-
- friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
-};
+// https://wg21.link/P3379R0
+static_assert(CanCompare<std::expected<EqualityComparable, EqualityComparable>, std::unexpected<int>>);
+static_assert(CanCompare<std::expected<EqualityComparable, int>, std::unexpected<EqualityComparable>>);
+static_assert(!CanCompare<std::expected<EqualityComparable, NonComparable>, std::unexpected<int>>);
constexpr bool test() {
// x.has_value()
{
- const std::expected<void, Data> e1;
+ const std::expected<void, EqualityComparable> e1;
std::unexpected<int> un2(10);
std::unexpected<int> un3(5);
assert(e1 != un2);
@@ -37,7 +36,7 @@ constexpr bool test() {
// !x.has_value()
{
- const std::expected<void, Data> e1(std::unexpect, 5);
+ const std::expected<void, EqualityComparable> e1(std::unexpect, 5);
std::unexpected<int> un2(10);
std::unexpected<int> un3(5);
assert(e1 != un2);
diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h
index df73ebdfe495e..11473ca3d97de 100644
--- a/libcxx/test/std/utilities/expected/types.h
+++ b/libcxx/test/std/utilities/expected/types.h
@@ -336,4 +336,17 @@ struct CheckForInvalidWrites : public CheckForInvalidWritesBase<WithPaddedExpect
}
};
+struct NonComparable {};
+
+struct EqualityComparable {
+ int i;
+ constexpr EqualityComparable(int ii) : i(ii) {}
+
+ friend constexpr bool operator==(const EqualityComparable& data, int ii) { return data.i == ii; }
+};
+
+// Test constraint
+template <class T1, class T2>
+concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
+
#endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H
|
std::expected
equality operators
@yronglin A few comments:
|
Thanks for your review!
Thanks for reminding me, I didn't aware the change in the process before.
Yes, I'd like to implement the rest of P2944R3.
I reuse the test data between the types because it's very similar, but I have no preference. Should we revert the reuses things? |
Signed-off-by: yronglin <[email protected]>
…26 feature Signed-off-by: yronglin <[email protected]>
@yronglin Please add
to the top comment in the PR to link this PR to the GitHub issue as @frederick-vs-ja suggested above. |
Co-authored-by: A. Jiang <[email protected]>
…tible.h Signed-off-by: yronglin <[email protected]>
✅ With the latest revision this PR passed the C/C++ code formatter. |
Signed-off-by: yronglin <[email protected]>
Signed-off-by: yronglin <[email protected]>
libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp
Outdated
Show resolved
Hide resolved
Signed-off-by: yronglin <[email protected]>
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) { | ||
# if _LIBCPP_STD_VER >= 26 | ||
&& requires { | ||
{ *__x == *__y } -> __core_convertible_to<bool>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the reason why you don't use convertible_to<bool>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've told the reason in #117664 (comment).
convertible_to
requires both implicit and explicit convertibility, where the standard wording only requires the latter. Moreover, due to explicit object member functions and CWG2813, we can invent a class whose prvalue is implicitly convertible to bool
but xvalue isn't.
Example (Godbolt link):
#include <type_traits>
#include <concepts>
struct X {
X() = default;
X(const X&) = delete;
X& operator=(const X&) = delete;
template<class = void>
constexpr operator bool(this X) { return true; }
explicit operator bool(this X) = delete;
};
static_assert(!std::is_convertible_v<X, bool>);
static_assert(!std::convertible_to<X, bool>);
constexpr bool b = X{};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say it is very contrived and I would hardly believe the standard is designed to be in this form. IMO, implicit conversion should be a superset of explicit conversion. Implicitly conversion should imply explicit conversion.
Having something is implicitly convertible to, but not explicitly convertible to , is , just , a foot gun.
This similar to non-const vs const. The example above is in the same spirit of having
void f(const Foo&);
void f(Foo&) = delete;
I would say, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's intended to allow such a return type. However, the core language rules "accidentally" permits such a weird return type when there's no additional constraints/static_assert
's.
IMO, P3379R0 and P2944R3 (for optional
, variant
, and expected
) should only add constraints that reflects the "mandates" in the return
statements. It seems that the original intent of operators was that natural mandates in the return
statements should work for these operators, and thus no additional static_assert
would be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, even when there's no issue with explicit conversion, the prvalue VS xvalue problem still blocks use of convertible_to
. Godbolt link.
#include <type_traits>
#include <concepts>
struct Y {
Y() = default;
Y(const Y&) = delete;
Y& operator=(const Y&) = delete;
constexpr operator bool(this Y) { return true; }
};
static_assert(!std::is_convertible_v<Y, bool>);
static_assert(!std::convertible_to<Y, bool>);
constexpr bool b1 = Y{};
constexpr bool b2(Y{});
Signed-off-by: yronglin <[email protected]>
…36672) Implements https://wg21.link/P2944R3 (partially): - [pairs.spec](https://eel.is/c++draft/pairs.spec) Related issues: - Related to #105424 - Related to #118135 - PR #135759 - PR #117664 Closes: [#136763](#136763) # References - https://eel.is/c++draft/concept.booleantestable - https://eel.is/c++draft/concept.equalitycomparable --------- Co-authored-by: Hristo Hristov <[email protected]> Co-authored-by: Nikolas Klauser <[email protected]>
…vm#136672) Implements https://wg21.link/P2944R3 (partially): - [pairs.spec](https://eel.is/c++draft/pairs.spec) Related issues: - Related to llvm#105424 - Related to llvm#118135 - PR llvm#135759 - PR llvm#117664 Closes: [llvm#136763](llvm#136763) # References - https://eel.is/c++draft/concept.booleantestable - https://eel.is/c++draft/concept.equalitycomparable --------- Co-authored-by: Hristo Hristov <[email protected]> Co-authored-by: Nikolas Klauser <[email protected]>
…vm#136672) Implements https://wg21.link/P2944R3 (partially): - [pairs.spec](https://eel.is/c++draft/pairs.spec) Related issues: - Related to llvm#105424 - Related to llvm#118135 - PR llvm#135759 - PR llvm#117664 Closes: [llvm#136763](llvm#136763) # References - https://eel.is/c++draft/concept.booleantestable - https://eel.is/c++draft/concept.equalitycomparable --------- Co-authored-by: Hristo Hristov <[email protected]> Co-authored-by: Nikolas Klauser <[email protected]>
…vm#136672) Implements https://wg21.link/P2944R3 (partially): - [pairs.spec](https://eel.is/c++draft/pairs.spec) Related issues: - Related to llvm#105424 - Related to llvm#118135 - PR llvm#135759 - PR llvm#117664 Closes: [llvm#136763](llvm#136763) # References - https://eel.is/c++draft/concept.booleantestable - https://eel.is/c++draft/concept.equalitycomparable --------- Co-authored-by: Hristo Hristov <[email protected]> Co-authored-by: Nikolas Klauser <[email protected]>
…perator (#136672) Implements https://wg21.link/P2944R3 (partially): - [pairs.spec](https://eel.is/c++draft/pairs.spec) Related issues: - Related to #105424 - Related to #118135 - PR llvm/llvm-project#135759 - PR llvm/llvm-project#117664 Closes: [#136763](llvm/llvm-project#136763) # References - https://eel.is/c++draft/concept.booleantestable - https://eel.is/c++draft/concept.equalitycomparable --------- Co-authored-by: Hristo Hristov <[email protected]> Co-authored-by: Nikolas Klauser <[email protected]>
…vm#136672) Implements https://wg21.link/P2944R3 (partially): - [pairs.spec](https://eel.is/c++draft/pairs.spec) Related issues: - Related to llvm#105424 - Related to llvm#118135 - PR llvm#135759 - PR llvm#117664 Closes: [llvm#136763](llvm#136763) # References - https://eel.is/c++draft/concept.booleantestable - https://eel.is/c++draft/concept.equalitycomparable --------- Co-authored-by: Hristo Hristov <[email protected]> Co-authored-by: Nikolas Klauser <[email protected]>
Closes #118135
This PR implement P3379R0 Constrain std::expected equality operators.