Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[libc++] P3379R1: Constrain std::expected equality operators #117664

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

Closed
wants to merge 7 commits into from

Conversation

xiaoyang-sde
Copy link
Member

@xiaoyang-sde xiaoyang-sde commented Nov 26, 2024

Introduction

This patch attempts to implement P3379R1. P3379R1 proposes adding constraints to the equality operators for std::expected, aligning it with other standard library types like pair, tuple, optional, and variant.

Reference

@xiaoyang-sde xiaoyang-sde self-assigned this Nov 26, 2024
@xiaoyang-sde xiaoyang-sde added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Nov 26, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 26, 2024

@llvm/pr-subscribers-libcxx

Author: Xiaoyang Liu (xiaoyang-sde)

Changes

Full diff: https://github.com/llvm/llvm-project/pull/117664.diff

1 Files Affected:

  • (modified) libcxx/include/__expected/expected.h (+20-2)
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 3d3f11967ee746..7dc0248fcd72f9 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/convertible_to.h>
 #include <__config>
 #include <__expected/bad_expected_access.h>
 #include <__expected/unexpect.h>
@@ -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>;
+               { __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;
@@ -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());
   }
@@ -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;
@@ -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());
   }

@frederick-vs-ja
Copy link
Contributor

Should we backport this to C++23? If so, perhaps we should also backport the constraints introduced by P2944R3 to old modes.

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?

@Zingam
Copy link
Contributor

Zingam commented Nov 26, 2024

Should we backport this to C++23? If so, perhaps we should also backport the constraints introduced by P2944R3 to old modes.

@frederick-vs-ja
Do you mean the second part of the paper regarding the constrained equality operators which is not yet implemented?

@frederick-vs-ja
Copy link
Contributor

Should we backport this to C++23? If so, perhaps we should also backport the constraints introduced by P2944R3 to old modes.

@frederick-vs-ja Do you mean the second part of the paper regarding the constrained equality operators which is not yet implemented?

Yes. I think these changes should be handled consistently.

@Zingam
Copy link
Contributor

Zingam commented Apr 4, 2025

Should we backport this to C++23? If so, perhaps we should also backport the constraints introduced by P2944R3 to old modes.

@frederick-vs-ja @mordante To continue on the above: Do you think "constrained equality operators" as defined in https://wg21.link/P3379R1 and https://wg21.link/p2944r3 should be implemented as DR?

@mordante
Copy link
Member

mordante commented Apr 9, 2025

Should we backport this to C++23? If so, perhaps we should also backport the constraints introduced by P2944R3 to old modes.

@frederick-vs-ja @mordante To continue on the above: Do you think "constrained equality operators" as defined in https://wg21.link/P3379R1 and https://wg21.link/p2944r3 should be implemented as DR?

The paper was not voted in as a DR so we should not apply it as a DR.

Copy link
Member

@mordante mordante left a comment

Choose a reason for hiding this comment

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

@xiaoyang-sde do you still want to work on this PR?

@@ -45,6 +45,7 @@ Implemented Papers
- ``std::jthread`` and ``<stop_token>`` are not guarded behind ``-fexperimental-library`` anymore
- P2674R1: A trait for implicit lifetime types (`Github <https://github.com/llvm/llvm-project/issues/105259>`__)
- P0429R9: A Standard ``flat_map`` is partially implemented and ``flat_map`` is provided (`Github <https://github.com/llvm/llvm-project/issues/105190>`__)
- P3379R1: Constrain ``std::expected`` equality operators
Copy link
Member

Choose a reason for hiding this comment

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

The paper number is R0, please adjust all references in the PR

Suggested change
- P3379R1: Constrain ``std::expected`` equality operators
- P3379R0: Constrain ``std::expected`` equality operators

Copy link
Contributor

@jwakely jwakely Apr 10, 2025

Choose a reason for hiding this comment

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

There was a D3379R1 draft but I forgot to publish it, and we voted on R0. The only difference is that R1 said to add __cpp_lib_constrained_equality in <expected>. That addition got done editorially: cplusplus/draft#7449

It looks like this PR neither bumps the macro value nor adds the macro to <expected>.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've submitted brevzin/sd6#28 to update SD-6.

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Apr 10, 2025

Should we backport this to C++23? If so, perhaps we should also backport the constraints introduced by P2944R3 to old modes.

@frederick-vs-ja @mordante To continue on the above: Do you think "constrained equality operators" as defined in https://wg21.link/P3379R1 and https://wg21.link/p2944r3 should be implemented as DR?

The paper was not voted in as a DR so we should not apply it as a DR.

In https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115939#c3 @jwakely said that

I'll get to work on the std::expected proposal (which I expect will get treated as a DR for C++23 by all implementers, certainly in libstdc++).

And P3379R0 is the paper. libstdc++ treated it as DR against C++23 (gcc-mirror/gcc@0515b24).

@jwakely
Copy link
Contributor

jwakely commented Apr 10, 2025

And P3379R0 is the paper. libstdc++ treated it as DR against C++23 (gcc-mirror/gcc@0515b24).

I think the constrained comparisons for expected should be consistent with the constrained comparisons for tuple, optional and variant added by P2944R3, i.e. all those constrained comparisons should both be treated as DRs or none of them.

For libstdc++ we also treated the tuple/optional/variant parts of P2944 as a DR against C++20. I didn't apply them further back because without defaulted comparisons I don't think it really matters whether the comparisons are constrained (and because I'm lazy and I didn't want to implement the constraints without concepts and spaceship). So for consistency, I treated the expected constrained comparisons as a DR too (but only to C++23 because expected isn't in C++20).

I did not add comparisons for reference_wrapper as a DR. That's a more significant API change, but I consider constraining existing comparisons to be just a nice QoI improvement (we already constrained some of the optional comparisons even before P2944).

P2944 should probably have been two separate papers: one paper adding new comparisons and another paper constraining existing comparisons, instead of doing the latter as a drive-by fix.

@xiaoyang-sde
Copy link
Member Author

@xiaoyang-sde do you still want to work on this PR?

Sorry! I've been a bit busy lately, so feel free to take this over!

@Zingam
Copy link
Contributor

Zingam commented May 2, 2025

@xiaoyang-sde Since there is a more up-to-date PR, let's close this. I'll wait a couple of days and I'll close it if nobody objects.

IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request May 6, 2025
…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]>
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
@xiaoyang-sde xiaoyang-sde deleted the P3379R1 branch May 7, 2025 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants