diff options
author | yronglin <yronglin777@gmail.com> | 2025-05-08 03:25:00 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-07 15:25:00 -0400 |
commit | 13c464be84d9715f0825387f30e455eea7ef75f7 (patch) | |
tree | 17aceb7147208efed92268909cfedc9ef829078d | |
parent | fc281e1b4fcd32f78ed202fbdc92c1816a80e078 (diff) | |
download | llvm-13c464be84d9715f0825387f30e455eea7ef75f7.zip llvm-13c464be84d9715f0825387f30e455eea7ef75f7.tar.gz llvm-13c464be84d9715f0825387f30e455eea7ef75f7.tar.bz2 |
[libc++] Implement P3379R0 Constrain `std::expected` equality operators (#135759)
Closes #118135
Co-authored-by: A. Jiang <de34@live.cn>
9 files changed, 110 insertions, 42 deletions
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv index e230002..3809446 100644 --- a/libcxx/docs/Status/Cxx2cPapers.csv +++ b/libcxx/docs/Status/Cxx2cPapers.csv @@ -78,7 +78,7 @@ "","","","","","" "`P3136R1 <https://wg21.link/P3136R1>`__","Retiring niebloids","2024-11 (Wrocław)","|Complete|","14","" "`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)","","","" diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h index 03bbd16..6b3d335 100644 --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -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> @@ -1139,8 +1140,15 @@ public: // [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 { @@ -1153,12 +1161,24 @@ public: } 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()); } }; @@ -1851,7 +1871,13 @@ public: // [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 { @@ -1860,7 +1886,13 @@ public: } 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()); } }; diff --git a/libcxx/include/__type_traits/is_core_convertible.h b/libcxx/include/__type_traits/is_core_convertible.h index 93e23d2..ca3a346 100644 --- a/libcxx/include/__type_traits/is_core_convertible.h +++ b/libcxx/include/__type_traits/is_core_convertible.h @@ -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 diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp index bc8b9de..25eb97a 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.T2.pass.cpp @@ -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); @@ -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); diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp index 9325c6c..f0f549b 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.other_expected.pass.cpp @@ -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() { diff --git a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp index a8c469d..6c7d2f3 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/equality/equality.unexpected.pass.cpp @@ -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); @@ -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); diff --git a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp index 8b24875..b6c3d8d 100644 --- a/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/equality/equality.other_expected.pass.cpp @@ -18,10 +18,7 @@ #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>); @@ -29,8 +26,18 @@ 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() diff --git a/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp index 4500971..f37f38b 100644 --- a/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/equality/equality.unexpected.pass.cpp @@ -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<void, EqualityComparable>, std::unexpected<int>>); +static_assert(CanCompare<std::expected<void, int>, std::unexpected<EqualityComparable>>); +static_assert(!CanCompare<std::expected<void, NonComparable>, std::unexpected<int>>); +#endif 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); @@ -37,7 +38,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); diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index df73ebd..11473ca 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -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 |