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
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ set(files
__concepts/constructible.h
__concepts/convertible_to.h
__concepts/copyable.h
__concepts/core_convertible_to.h
__concepts/derived_from.h
__concepts/destructible.h
__concepts/different_from.h
Expand Down
31 changes: 31 additions & 0 deletions libcxx/include/__concepts/core_convertible_to.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef _LIBCPP___CONCEPTS_CORE_CONVERTIBLE_TO_H
#define _LIBCPP___CONCEPTS_CORE_CONVERTIBLE_TO_H

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER >= 20

// [conv.general]/3 says "E is convertible to T" whenever "T t=E;" is well-formed.
// We can't test for that, but we can test implicit convertibility by passing it
// to a function. Unlike std::convertible_to, __core_convertible_to doesn't test
// static_cast or handle cv void, while accepting move-only types.
//
// This is a conceptual __is_core_convertible.
template <class _Tp, class _Up>
concept __core_convertible_to = requires {
// rejects function and array types which are adjusted to pointer types in parameter lists
static_cast<_Up (*)()>(nullptr)();
static_cast<void (*)(_Up)>(nullptr)(static_cast<_Tp (*)()>(nullptr)());
};

#endif

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___CONCEPTS_CORE_CONVERTIBLE_TO_H
44 changes: 39 additions & 5 deletions libcxx/include/__expected/expected.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define _LIBCPP___EXPECTED_EXPECTED_H

#include <__assert>
#include <__concepts/core_convertible_to.h>
#include <__config>
#include <__expected/bad_expected_access.h>
#include <__expected/unexpect.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
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap.in
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,7 @@ module std [system] {
module constructible { header "__concepts/constructible.h" }
module convertible_to { header "__concepts/convertible_to.h" }
module copyable { header "__concepts/copyable.h" }
module core_convertible_to { header "__concepts/core_convertible_to.h" }
module derived_from { header "__concepts/derived_from.h" }
module destructible { header "__concepts/destructible.h" }
module different_from { header "__concepts/different_from.h" }
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<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
1 change: 1 addition & 0 deletions llvm/utils/gn/secondary/libcxx/include/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ if (current_toolchain == default_toolchain) {
"__concepts/constructible.h",
"__concepts/convertible_to.h",
"__concepts/copyable.h",
"__concepts/core_convertible_to.h",
"__concepts/derived_from.h",
"__concepts/destructible.h",
"__concepts/different_from.h",
Expand Down
Loading