Skip to content
22 changes: 20 additions & 2 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/convertible_to.h>
#include <__config>
#include <__expected/bad_expected_access.h>
#include <__expected/unexpect.h>
Expand Down Expand Up @@ -1139,7 +1140,11 @@ class expected : private __expected_base<_Tp, _Err> {

// [expected.object.eq], equality operators
template <class _T2, class _E2>
requires(!is_void_v<_T2>)
requires(!is_void_v<_T2> &&
requires(const _Tp& __tp, const _T2& __t2, const _Err& __err, const _E2& __e2) {
{ __tp == __t2 } -> convertible_to<bool>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convertible_to concept is stricter than the plain "(implicitly) convertible to". The latter can be expressed by __is_core_convertible in <__type_traits/is_core_convertible.h>.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can have the following.

Internal concept __core_convertible_to in the <__concepts/core_convertible_to.h> internal header

#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.

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for writing this! I'll take a look.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like to draft a patch for this header if you have time? I can rebase and use your header. This will ensure that you receive credit for your work! Otherwise, I can include it in this patch myself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we used __boolean_testable to implement the constraints for operator== in std::reference_wrapper. (https://github.com/llvm/llvm-project/blob/main/libcxx/include/__functional/reference_wrapper.h#L75-L81)
Should we consider making them consistent?

{ __err == __e2 } -> convertible_to<bool>;
})
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
if (__x.__has_val() != __y.__has_val()) {
return false;
Expand All @@ -1153,11 +1158,18 @@ class expected : private __expected_base<_Tp, _Err> {
}

template <class _T2>
requires(!__is_std_expected<_T2>::value &&
requires(const _Tp& __tp, const _T2& __t2) {
{ __tp == __t2 } -> convertible_to<bool>;
})
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const _T2& __v) {
return __x.__has_val() && static_cast<bool>(__x.__val() == __v);
}

template <class _E2>
requires requires(const _Err& __err, const _E2& __e2) {
{ __err == __e2 } -> convertible_to<bool>;
}
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __e) {
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __e.error());
}
Expand Down Expand Up @@ -1850,7 +1862,10 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {

// [expected.void.eq], equality operators
template <class _T2, class _E2>
requires is_void_v<_T2>
requires(is_void_v<_T2> &&
requires(const _Err& __err, const _E2& __e2) {
{ __err == __e2 } -> convertible_to<bool>;
})
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const expected<_T2, _E2>& __y) {
if (__x.__has_val() != __y.__has_val()) {
return false;
Expand All @@ -1860,6 +1875,9 @@ class expected<_Tp, _Err> : private __expected_void_base<_Err> {
}

template <class _E2>
requires requires(const _Err& __err, const _E2& __e2) {
{ __err == __e2 } -> convertible_to<bool>;
}
_LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const expected& __x, const unexpected<_E2>& __y) {
return !__x.__has_val() && static_cast<bool>(__x.__unex() == __y.error());
}
Expand Down
Loading