Skip to content

Commit 13c464b

Browse files
[libc++] Implement P3379R0 Constrain std::expected equality operators (#135759)
Closes #118135 Co-authored-by: A. Jiang <[email protected]>
1 parent fc281e1 commit 13c464b

File tree

9 files changed

+110
-42
lines changed

9 files changed

+110
-42
lines changed

libcxx/docs/Status/Cxx2cPapers.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
"","","","","",""
7979
"`P3136R1 <https://wg21.link/P3136R1>`__","Retiring niebloids","2024-11 (Wrocław)","|Complete|","14",""
8080
"`P3138R5 <https://wg21.link/P3138R5>`__","``views::cache_latest``","2024-11 (Wrocław)","","",""
81-
"`P3379R0 <https://wg21.link/P3379R0>`__","Constrain ``std::expected`` equality operators","2024-11 (Wrocław)","","",""
81+
"`P3379R0 <https://wg21.link/P3379R0>`__","Constrain ``std::expected`` equality operators","2024-11 (Wrocław)","|Complete|","21",""
8282
"`P2862R1 <https://wg21.link/P2862R1>`__","``text_encoding::name()`` should never return null values","2024-11 (Wrocław)","","",""
8383
"`P2897R7 <https://wg21.link/P2897R7>`__","``aligned_accessor``: An ``mdspan`` accessor expressing pointer over-alignment","2024-11 (Wrocław)","|Complete|","21",""
8484
"`P3355R1 <https://wg21.link/P3355R1>`__","Fix ``submdspan`` for C++26","2024-11 (Wrocław)","","",""

libcxx/include/__expected/expected.h

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <__type_traits/is_assignable.h>
2626
#include <__type_traits/is_constructible.h>
2727
#include <__type_traits/is_convertible.h>
28+
#include <__type_traits/is_core_convertible.h>
2829
#include <__type_traits/is_function.h>
2930
#include <__type_traits/is_nothrow_assignable.h>
3031
#include <__type_traits/is_nothrow_constructible.h>
@@ -1139,8 +1140,15 @@ class expected : private __expected_base<_Tp, _Err> {
11391140

11401141
// [expected.object.eq], equality operators
11411142
template <class _T2, class _E2>
1143+
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
11421144
requires(!is_void_v<_T2>)
1143-
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
1145+
# if _LIBCPP_STD_VER >= 26
1146+
&& requires {
1147+
{ *__x == *__y } -> __core_convertible_to<bool>;
1148+
{ __x.error() == __y.error() } -> __core_convertible_to<bool>;
1149+
}
1150+
# endif
1151+
{
11441152
if (__x.__has_val() != __y.__has_val()) {
11451153
return false;
11461154
} else {
@@ -1153,12 +1161,24 @@ class expected : private __expected_base<_Tp, _Err> {
11531161
}
11541162

11551163
template <class _T2>
1156-
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v) {
1164+
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v)
1165+
# if _LIBCPP_STD_VER >= 26
1166+
requires(!__is_std_expected<_T2>::value) && requires {
1167+
{ *__x == __v } -> __core_convertible_to<bool>;
1168+
}
1169+
# endif
1170+
{
11571171
return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
11581172
}
11591173

11601174
template <class _E2>
1161-
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e) {
1175+
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e)
1176+
# if _LIBCPP_STD_VER >= 26
1177+
requires requires {
1178+
{ __x.error() == __e.error() } -> __core_convertible_to<bool>;
1179+
}
1180+
# endif
1181+
{
11621182
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
11631183
}
11641184
};
@@ -1851,7 +1871,13 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
18511871
// [expected.void.eq], equality operators
18521872
template <class _T2, class _E2>
18531873
requires is_void_v<_T2>
1854-
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
1874+
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
1875+
# if _LIBCPP_STD_VER >= 26
1876+
requires requires {
1877+
{ __x.error() == __y.error() } -> __core_convertible_to<bool>;
1878+
}
1879+
# endif
1880+
{
18551881
if (__x.__has_val() != __y.__has_val()) {
18561882
return false;
18571883
} else {
@@ -1860,7 +1886,13 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
18601886
}
18611887

18621888
template <class _E2>
1863-
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y) {
1889+
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y)
1890+
# if _LIBCPP_STD_VER >= 26
1891+
requires requires {
1892+
{ __x.error() == __y.error() } -> __core_convertible_to<bool>;
1893+
}
1894+
# endif
1895+
{
18641896
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
18651897
}
18661898
};

libcxx/include/__type_traits/is_core_convertible.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ template <class _Tp, class _Up>
3030
struct __is_core_convertible<_Tp, _Up, decltype(static_cast<void (*)(_Up)>(0)(static_cast<_Tp (*)()>(0)()))>
3131
: true_type {};
3232

33+
#if _LIBCPP_STD_VER >= 20
34+
35+
template <class _Tp, class _Up>
36+
concept __core_convertible_to = __is_core_convertible<_Tp, _Up>::value;
37+
38+
#endif // _LIBCPP_STD_VER >= 20
39+
3340
_LIBCPP_END_NAMESPACE_STD
3441

3542
#endif // _LIBCPP___TYPE_TRAITS_IS_CORE_CONVERTIBLE_H

libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@
1717
#include <utility>
1818

1919
#include "test_macros.h"
20+
#include "../../types.h"
2021

21-
struct Data {
22-
int i;
23-
constexpr Data(int ii) : i(ii) {}
24-
25-
friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
26-
};
22+
#if TEST_STD_VER >= 26
23+
// https://wg21.link/P3379R0
24+
static_assert(CanCompare<std::expected<int, int>, int>);
25+
static_assert(CanCompare<std::expected<int, int>, EqualityComparable>);
26+
static_assert(!CanCompare<std::expected<int, int>, NonComparable>);
27+
#endif
2728

2829
constexpr bool test() {
2930
// x.has_value()
3031
{
31-
const std::expected<Data, int> e1(std::in_place, 5);
32+
const std::expected<EqualityComparable, int> e1(std::in_place, 5);
3233
int i2 = 10;
3334
int i3 = 5;
3435
assert(e1 != i2);
@@ -37,7 +38,7 @@ constexpr bool test() {
3738

3839
// !x.has_value()
3940
{
40-
const std::expected<Data, int> e1(std::unexpect, 5);
41+
const std::expected<EqualityComparable, int> e1(std::unexpect, 5);
4142
int i2 = 10;
4243
int i3 = 5;
4344
assert(e1 != i2);

libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,26 @@
1818
#include <utility>
1919

2020
#include "test_macros.h"
21+
#include "../../types.h"
2122

2223
// Test constraint
23-
template <class T1, class T2>
24-
concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
25-
26-
struct Foo{};
27-
static_assert(!CanCompare<Foo, Foo>);
24+
static_assert(!CanCompare<NonComparable, NonComparable>);
2825

2926
static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
3027
static_assert(CanCompare<std::expected<int, int>, std::expected<short, short>>);
3128

32-
// Note this is true because other overloads are unconstrained
33-
static_assert(CanCompare<std::expected<int, int>, std::expected<void, int>>);
34-
29+
#if TEST_STD_VER >= 26
30+
// https://wg21.link/P3379R0
31+
static_assert(!CanCompare<std::expected<int, int>, std::expected<void, int>>);
32+
static_assert(CanCompare<std::expected<int, int>, std::expected<int, int>>);
33+
static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<NonComparable, int>>);
34+
static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<int, NonComparable>>);
35+
static_assert(!CanCompare<std::expected<NonComparable, int>, std::expected<int, NonComparable>>);
36+
static_assert(!CanCompare<std::expected<int, NonComparable>, std::expected<NonComparable, int>>);
37+
#else
38+
// Note this is true because other overloads in expected<non-void> are unconstrained
39+
static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
40+
#endif
3541
constexpr bool test() {
3642
// x.has_value() && y.has_value()
3743
{

libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@
1717
#include <utility>
1818

1919
#include "test_macros.h"
20+
#include "../../types.h"
2021

21-
struct Data {
22-
int i;
23-
constexpr Data(int ii) : i(ii) {}
24-
25-
friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
26-
};
22+
#if TEST_STD_VER >= 26
23+
// https://wg21.link/P3379R0
24+
static_assert(CanCompare<std::expected<EqualityComparable, EqualityComparable>, std::unexpected<int>>);
25+
static_assert(CanCompare<std::expected<EqualityComparable, int>, std::unexpected<EqualityComparable>>);
26+
static_assert(!CanCompare<std::expected<EqualityComparable, NonComparable>, std::unexpected<int>>);
27+
#endif
2728

2829
constexpr bool test() {
2930
// x.has_value()
3031
{
31-
const std::expected<Data, Data> e1(std::in_place, 5);
32+
const std::expected<EqualityComparable, EqualityComparable> e1(std::in_place, 5);
3233
std::unexpected<int> un2(10);
3334
std::unexpected<int> un3(5);
3435
assert(e1 != un2);
@@ -37,7 +38,7 @@ constexpr bool test() {
3738

3839
// !x.has_value()
3940
{
40-
const std::expected<Data, Data> e1(std::unexpect, 5);
41+
const std::expected<EqualityComparable, EqualityComparable> e1(std::unexpect, 5);
4142
std::unexpected<int> un2(10);
4243
std::unexpected<int> un3(5);
4344
assert(e1 != un2);

libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,26 @@
1818
#include <utility>
1919

2020
#include "test_macros.h"
21-
22-
// Test constraint
23-
template <class T1, class T2>
24-
concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
21+
#include "../../types.h"
2522

2623
struct Foo{};
2724
static_assert(!CanCompare<Foo, Foo>);
2825

2926
static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
3027
static_assert(CanCompare<std::expected<void, int>, std::expected<void, short>>);
3128

29+
#if TEST_STD_VER >= 26
30+
// https://wg21.link/P3379R0
31+
static_assert(!CanCompare<std::expected<void, int>, std::expected<int, int>>);
32+
static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
33+
static_assert(CanCompare<std::expected<void, int>, std::expected<void, int>>);
34+
static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, NonComparable>>);
35+
static_assert(!CanCompare<std::expected<void, int>, std::expected<void, NonComparable>>);
36+
static_assert(!CanCompare<std::expected<void, NonComparable>, std::expected<void, int>>);
37+
#else
3238
// Note this is true because other overloads in expected<non-void> are unconstrained
3339
static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
40+
#endif
3441

3542
constexpr bool test() {
3643
// x.has_value() && y.has_value()

libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@
1717
#include <utility>
1818

1919
#include "test_macros.h"
20+
#include "../../types.h"
2021

21-
struct Data {
22-
int i;
23-
constexpr Data(int ii) : i(ii) {}
24-
25-
friend constexpr bool operator==(const Data& data, int ii) { return data.i == ii; }
26-
};
22+
#if TEST_STD_VER >= 26
23+
// https://wg21.link/P3379R0
24+
static_assert(CanCompare<std::expected<void, EqualityComparable>, std::unexpected<int>>);
25+
static_assert(CanCompare<std::expected<void, int>, std::unexpected<EqualityComparable>>);
26+
static_assert(!CanCompare<std::expected<void, NonComparable>, std::unexpected<int>>);
27+
#endif
2728

2829
constexpr bool test() {
2930
// x.has_value()
3031
{
31-
const std::expected<void, Data> e1;
32+
const std::expected<void, EqualityComparable> e1;
3233
std::unexpected<int> un2(10);
3334
std::unexpected<int> un3(5);
3435
assert(e1 != un2);
@@ -37,7 +38,7 @@ constexpr bool test() {
3738

3839
// !x.has_value()
3940
{
40-
const std::expected<void, Data> e1(std::unexpect, 5);
41+
const std::expected<void, EqualityComparable> e1(std::unexpect, 5);
4142
std::unexpected<int> un2(10);
4243
std::unexpected<int> un3(5);
4344
assert(e1 != un2);

libcxx/test/std/utilities/expected/types.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,4 +336,17 @@ struct CheckForInvalidWrites : public CheckForInvalidWritesBase<WithPaddedExpect
336336
}
337337
};
338338

339+
struct NonComparable {};
340+
341+
struct EqualityComparable {
342+
int i;
343+
constexpr EqualityComparable(int ii) : i(ii) {}
344+
345+
friend constexpr bool operator==(const EqualityComparable& data, int ii) { return data.i == ii; }
346+
};
347+
348+
// Test constraint
349+
template <class T1, class T2>
350+
concept CanCompare = requires(T1 t1, T2 t2) { t1 == t2; };
351+
339352
#endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H

0 commit comments

Comments
 (0)