Skip to content

[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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cPapers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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)","|Complete|","21",""
"`P3355R1 <https://wg21.link/P3355R1>`__","Fix ``submdspan`` for C++26","2024-11 (Wrocław)","","",""
Expand Down
44 changes: 39 additions & 5 deletions libcxx/include/__expected/expected.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <__type_traits/is_assignable.h>
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_convertible.h>
#include <__type_traits/is_core_convertible.h>
#include <__type_traits/is_function.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
Expand Down Expand Up @@ -1139,8 +1140,15 @@ class expected : private __expected_base<_Tp, _Err> {

// [expected.object.eq], equality operators
template <class _T2, class _E2>
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y)
requires(!is_void_v<_T2>)
_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>;
{ __x.error() == __y.error() } -> __core_convertible_to<bool>;
}
# endif
{
if (__x.__has_val() != __y.__has_val()) {
return false;
} else {
Expand All @@ -1153,12 +1161,24 @@ 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)
# if _LIBCPP_STD_VER >= 26
requires(!__is_std_expected<_T2>::value) && requires {
{ *__x == __v } -> __core_convertible_to<bool>;
}
# endif
{
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)
# if _LIBCPP_STD_VER >= 26
requires requires {
{ __x.error() == __e.error() } -> __core_convertible_to<bool>;
}
# endif
{
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
}
};
Expand Down Expand Up @@ -1851,7 +1871,14 @@ 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)
# if _LIBCPP_STD_VER >= 26

requires requires {
{ __x.error() == __y.error() } -> __core_convertible_to<bool>;
}
# endif
{
if (__x.__has_val() != __y.__has_val()) {
return false;
} else {
Expand All @@ -1860,7 +1887,14 @@ 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)
# if _LIBCPP_STD_VER >= 26

requires requires {
{ __x.error() == __y.error() } -> __core_convertible_to<bool>;
}
# endif
{
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
}
};
Expand Down
7 changes: 7 additions & 0 deletions libcxx/include/__type_traits/is_core_convertible.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ template <class _Tp, class _Up>
struct __is_core_convertible<_Tp, _Up, decltype(static_cast<void (*)(_Up)>(0)(static_cast<_Tp (*)()>(0)()))>
: true_type {};

#if _LIBCPP_STD_VER >= 20

template <class _Tp, class _Up>
concept __core_convertible_to = __is_core_convertible<_Tp, _Up>::value;

#endif // _LIBCPP_STD_VER >= 20

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___TYPE_TRAITS_IS_CORE_CONVERTIBLE_H
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@
#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; }
};
#if TEST_STD_VER >= 26
// 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>);
#endif

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);
Expand All @@ -37,7 +38,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@
#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>>);

#if TEST_STD_VER >= 26
// 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>>);
#else
// Note this is true because other overloads in expected<non-void> are unconstrained
static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
#endif
constexpr bool test() {
// x.has_value() && y.has_value()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@
#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; }
};
#if TEST_STD_VER >= 26
// 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>>);
#endif

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);
Expand All @@ -37,7 +38,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@
#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>);

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

#if TEST_STD_VER >= 26
// 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>>);
#else
// Note this is true because other overloads in expected<non-void> are unconstrained
static_assert(CanCompare<std::expected<void, int>, std::expected<int, int>>);
#endif

constexpr bool test() {
// x.has_value() && y.has_value()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions libcxx/test/std/utilities/expected/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading