From patchwork Wed Apr 10 08:45:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 1921851 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=T/kbtuOo; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VDxTW1GHYz1yYS for ; Wed, 10 Apr 2024 18:54:23 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 61CD43858D32 for ; Wed, 10 Apr 2024 08:54:21 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id B383A3858431 for ; Wed, 10 Apr 2024 08:50:56 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B383A3858431 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org B383A3858431 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1712739074; cv=none; b=Xw+Lx5stl5E05hHSccMh2zSImEXvkx8J+NY7daV+HnPq348HQoXpyIQIovwBe54mrGdjLuvPAlP+Iywc6shzk/0JuGwv1PBhY8CPfBg04g7PZctZgJoG/dmtnf5/oLkxsAFmrvc3IlU2AA/mlG6NYTdEC4UkziSqU/pfQJXDmSs= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1712739074; c=relaxed/simple; bh=oBvMMyRRVJ/H4XnsIm+gFT5tZAA+NdbrzNBJtEdmYOI=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=Snz4XMH+6+cSXny3zyWqFT+3G8AVBlMEadB7hlq2OeKZYTeiOTWkiadL2gl4BL9uvoU2aB5m6U0un6CGuqbCKJEzedagaNbwSgCOQUy5nGF50k/80KnXya/NCtbKXZs08yIurr6bC+U+TBpcb/e5DiEb1CCHBUFoORQu/wvhbaA= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1712739056; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bRK0nxGZ+fNse58AebqMWkjQ3ab8tqt1PUX4sxD/K84=; b=T/kbtuOooJUwWEg7ATjRXSc4zLzjB1xOh/WAyokCioLyjSotwXkMD/7SO0Uqk6xDtXXLH0 9Ta1G/snxDcJ+uznc+a1DmMRv7b+DgSEgekTyl7JQpgHdSwOrVzrvAPeuz7EsxXqYC8aXR 1h85+wXYbEzNpYANaQ6bDWkgHHiAxBk= Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-679-o0GG1y8MMZyfPLF6nBGBYQ-1; Wed, 10 Apr 2024 04:50:54 -0400 X-MC-Unique: o0GG1y8MMZyfPLF6nBGBYQ-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.rdu2.redhat.com [10.11.54.7]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 15E2F29ABA0D; Wed, 10 Apr 2024 08:50:54 +0000 (UTC) Received: from localhost (unknown [10.42.28.163]) by smtp.corp.redhat.com (Postfix) with ESMTP id 971E61C060A6; Wed, 10 Apr 2024 08:50:53 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [PATCH 3/4] libstdc++: Constrain equality ops for std::pair, std::tuple, std::variant Date: Wed, 10 Apr 2024 09:45:55 +0100 Message-ID: <20240410085039.267589-3-jwakely@redhat.com> In-Reply-To: <20240410085039.267589-1-jwakely@redhat.com> References: <20240410085039.267589-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.7 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-13.1 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org Tested x86_64-linux. Since this only affects C++20 and later (except for adding [[nodiscard]] to relational ops) it seems OK for trunk now. -- >8 -- Implement the changes from P2944R3 which add constraints to the comparison operators of std::pair, std::tuple, and std::variant. The paper also changes std::optional, but we already constrain its comparisons using SFINAE on the return type. However, we need some additional constraints on the [optional.comp.with.t] operators that compare an optional with a value. The paper doesn't say to do that, but I think it's needed because otherwise when the comparison for two optional objects fails its constraints, the two overloads that are supposed to be for comparing to a non-optional become the best overload candidates, but are ambiguous (and we don't even get as far as checking the constraints for satisfaction). The paper does not change std::expected, but probably should have done. I'll submit an LWG issue about that and implement it separately. Also add [[nodiscard]] to all these comparison operators. libstdc++-v3/ChangeLog: * include/bits/stl_pair.h (operator==): Add constraint. * include/bits/version.def (constrained_equality): Define. * include/bits/version.h: Regenerate. * include/std/optional: Define feature test macro. (__optional_rep_op_t): Use is_convertible_v instead of is_convertible. * include/std/tuple: Define feature test macro. (operator==, __tuple_cmp, operator<=>): Reimplement C++20 comparisons using lambdas. Add constraints. * include/std/utility: Define feature test macro. * include/std/variant: Define feature test macro. (_VARIANT_RELATION_FUNCTION_TEMPLATE): Add constraints. (variant): Remove unnecessary friend declarations for comparison operators. * testsuite/20_util/optional/relops/constrained.cc: New test. * testsuite/20_util/pair/comparison_operators/constrained.cc: New test. * testsuite/20_util/tuple/comparison_operators/constrained.cc: New test. * testsuite/20_util/variant/relops/constrained.cc: New test. * testsuite/20_util/tuple/comparison_operators/overloaded.cc: Disable for C++20 and later. * testsuite/20_util/tuple/comparison_operators/overloaded2.cc: Remove dg-error line for target c++20. --- libstdc++-v3/include/bits/stl_pair.h | 16 +- libstdc++-v3/include/bits/version.def | 9 + libstdc++-v3/include/bits/version.h | 10 + libstdc++-v3/include/std/optional | 48 +++- libstdc++-v3/include/std/tuple | 102 ++++--- libstdc++-v3/include/std/utility | 1 + libstdc++-v3/include/std/variant | 28 +- .../20_util/optional/relops/constrained.cc | 258 ++++++++++++++++++ .../pair/comparison_operators/constrained.cc | 48 ++++ .../tuple/comparison_operators/constrained.cc | 50 ++++ .../tuple/comparison_operators/overloaded.cc | 6 +- .../tuple/comparison_operators/overloaded2.cc | 1 - .../20_util/variant/relops/constrained.cc | 175 ++++++++++++ 13 files changed, 677 insertions(+), 75 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc create mode 100644 libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc create mode 100644 libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc create mode 100644 libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc diff --git a/libstdc++-v3/include/bits/stl_pair.h b/libstdc++-v3/include/bits/stl_pair.h index 45317417c9c..0c1e5719a1a 100644 --- a/libstdc++-v3/include/bits/stl_pair.h +++ b/libstdc++-v3/include/bits/stl_pair.h @@ -1000,14 +1000,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template pair(_T1, _T2) -> pair<_T1, _T2>; #endif -#if __cpp_lib_three_way_comparison && __cpp_lib_concepts +#if __cpp_lib_three_way_comparison // _GLIBCXX_RESOLVE_LIB_DEFECTS // 3865. Sorting a range of pairs /// Two pairs are equal iff their members are equal. template - inline _GLIBCXX_CONSTEXPR bool + [[nodiscard]] + constexpr bool operator==(const pair<_T1, _T2>& __x, const pair<_U1, _U2>& __y) + requires requires { + { __x.first == __y.first } -> __detail::__boolean_testable; + { __x.second == __y.second } -> __detail::__boolean_testable; + } { return __x.first == __y.first && __x.second == __y.second; } /** Defines a lexicographical order for pairs. @@ -1018,6 +1023,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * less than `Q.second`. */ template + [[nodiscard]] constexpr common_comparison_category_t<__detail::__synth3way_t<_T1, _U1>, __detail::__synth3way_t<_T2, _U2>> operator<=>(const pair<_T1, _T2>& __x, const pair<_U1, _U2>& __y) @@ -1029,6 +1035,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #else /// Two pairs of the same type are equal iff their members are equal. template + _GLIBCXX_NODISCARD inline _GLIBCXX_CONSTEXPR bool operator==(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y) { return __x.first == __y.first && __x.second == __y.second; } @@ -1041,6 +1048,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * than `Q.second`. */ template + _GLIBCXX_NODISCARD inline _GLIBCXX_CONSTEXPR bool operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y) { return __x.first < __y.first @@ -1048,24 +1056,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION /// Uses @c operator== to find the result. template + _GLIBCXX_NODISCARD inline _GLIBCXX_CONSTEXPR bool operator!=(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y) { return !(__x == __y); } /// Uses @c operator< to find the result. template + _GLIBCXX_NODISCARD inline _GLIBCXX_CONSTEXPR bool operator>(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y) { return __y < __x; } /// Uses @c operator< to find the result. template + _GLIBCXX_NODISCARD inline _GLIBCXX_CONSTEXPR bool operator<=(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y) { return !(__y < __x); } /// Uses @c operator< to find the result. template + _GLIBCXX_NODISCARD inline _GLIBCXX_CONSTEXPR bool operator>=(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y) { return !(__x < __y); } diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 5c0477fb61e..f0ba4f2bb3d 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1237,6 +1237,15 @@ ftms = { }; }; +ftms = { + name = constrained_equality; + values = { + v = 202403; + cxxmin = 20; + extra_cond = "__glibcxx_three_way_comparison"; + }; +}; + ftms = { name = erase_if; values = { diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 65e708c73fb..f30f51dcedc 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -1373,6 +1373,16 @@ #endif /* !defined(__cpp_lib_constexpr_vector) && defined(__glibcxx_want_constexpr_vector) */ #undef __glibcxx_want_constexpr_vector +#if !defined(__cpp_lib_constrained_equality) +# if (__cplusplus >= 202002L) && (__glibcxx_three_way_comparison) +# define __glibcxx_constrained_equality 202403L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_constrained_equality) +# define __cpp_lib_constrained_equality 202403L +# endif +# endif +#endif /* !defined(__cpp_lib_constrained_equality) && defined(__glibcxx_want_constrained_equality) */ +#undef __glibcxx_want_constrained_equality + #if !defined(__cpp_lib_erase_if) # if (__cplusplus >= 202002L) && _GLIBCXX_HOSTED # define __glibcxx_erase_if 202002L diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index 3507c36a4d8..e245e76e7a0 100644 --- a/libstdc++-v3/include/std/optional +++ b/libstdc++-v3/include/std/optional @@ -34,6 +34,7 @@ #define __glibcxx_want_freestanding_optional #define __glibcxx_want_optional +#define __glibcxx_want_constrained_equality #include #ifdef __cpp_lib_optional // C++ >= 17 @@ -1194,7 +1195,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template using __optional_relop_t = - enable_if_t::value, bool>; + enable_if_t, bool>; template using __optional_eq_t = __optional_relop_t< @@ -1279,6 +1280,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #ifdef __cpp_lib_three_way_comparison template _Up> + [[nodiscard]] constexpr compare_three_way_result_t<_Tp, _Up> operator<=>(const optional<_Tp>& __x, const optional<_Up>& __y) { @@ -1288,12 +1290,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Comparisons with nullopt. template + [[nodiscard]] constexpr bool operator==(const optional<_Tp>& __lhs, nullopt_t) noexcept { return !__lhs; } #ifdef __cpp_lib_three_way_comparison template + [[nodiscard]] constexpr strong_ordering operator<=>(const optional<_Tp>& __x, nullopt_t) noexcept { return bool(__x) <=> false; } @@ -1354,76 +1358,94 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return !__rhs; } #endif // three-way-comparison +#if __cpp_lib_concepts +# define _REQUIRES_NOT_OPTIONAL(T) requires (!__is_optional_v) +#else +# define _REQUIRES_NOT_OPTIONAL(T) +#endif + // Comparisons with value type. template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator==(const optional<_Tp>& __lhs, const _Up& __rhs) + operator== [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs) -> __optional_eq_t<_Tp, _Up> { return __lhs && *__lhs == __rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto operator==(const _Up& __lhs, const optional<_Tp>& __rhs) -> __optional_eq_t<_Up, _Tp> { return __rhs && __lhs == *__rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator!=(const optional<_Tp>& __lhs, const _Up& __rhs) + operator!= [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs) -> __optional_ne_t<_Tp, _Up> { return !__lhs || *__lhs != __rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator!=(const _Up& __lhs, const optional<_Tp>& __rhs) + operator!= [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs) -> __optional_ne_t<_Up, _Tp> { return !__rhs || __lhs != *__rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator<(const optional<_Tp>& __lhs, const _Up& __rhs) + operator< [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs) -> __optional_lt_t<_Tp, _Up> { return !__lhs || *__lhs < __rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator<(const _Up& __lhs, const optional<_Tp>& __rhs) + operator< [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs) -> __optional_lt_t<_Up, _Tp> { return __rhs && __lhs < *__rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator>(const optional<_Tp>& __lhs, const _Up& __rhs) + operator> [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs) -> __optional_gt_t<_Tp, _Up> { return __lhs && *__lhs > __rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator>(const _Up& __lhs, const optional<_Tp>& __rhs) + operator> [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs) -> __optional_gt_t<_Up, _Tp> { return !__rhs || __lhs > *__rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator<=(const optional<_Tp>& __lhs, const _Up& __rhs) + operator<= [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs) -> __optional_le_t<_Tp, _Up> { return !__lhs || *__lhs <= __rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator<=(const _Up& __lhs, const optional<_Tp>& __rhs) + operator<= [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs) -> __optional_le_t<_Up, _Tp> { return __rhs && __lhs <= *__rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator>=(const optional<_Tp>& __lhs, const _Up& __rhs) + operator>= [[nodiscard]] (const optional<_Tp>& __lhs, const _Up& __rhs) -> __optional_ge_t<_Tp, _Up> { return __lhs && *__lhs >= __rhs; } template + _REQUIRES_NOT_OPTIONAL(_Up) constexpr auto - operator>=(const _Up& __lhs, const optional<_Tp>& __rhs) + operator>= [[nodiscard]] (const _Up& __lhs, const optional<_Tp>& __rhs) -> __optional_ge_t<_Up, _Tp> { return !__rhs || __lhs >= *__rhs; } @@ -1432,7 +1454,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION requires (!__is_optional_v<_Up>) && three_way_comparable_with<_Up, _Tp> constexpr compare_three_way_result_t<_Tp, _Up> - operator<=>(const optional<_Tp>& __x, const _Up& __v) + operator<=> [[nodiscard]] (const optional<_Tp>& __x, const _Up& __v) { return bool(__x) ? *__x <=> __v : strong_ordering::less; } #endif diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple index 3065058e184..df3f6e38eeb 100644 --- a/libstdc++-v3/include/std/tuple +++ b/libstdc++-v3/include/std/tuple @@ -51,6 +51,7 @@ #define __glibcxx_want_make_from_tuple #define __glibcxx_want_ranges_zip #define __glibcxx_want_tuple_like +#define __glibcxx_want_constrained_equality #include namespace std _GLIBCXX_VISIBILITY(default) @@ -250,17 +251,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #if __cpp_lib_tuple_like // >= C++23 struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; }; - // These forward declarations are used by the operator<=> overload for + // This forward declaration is used by the operator<=> overload for // tuple-like types. - template + template constexpr _Cat - __tuple_cmp(const _Tp&, const _Up&, index_sequence<>); - - template - constexpr _Cat - __tuple_cmp(const _Tp& __t, const _Up& __u, - index_sequence<_Idx0, _Idxs...>); + __tuple_cmp(const _Tp& __t, const _Up& __u, _IndexSeq); #endif // C++23 /** @@ -1848,7 +1843,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<__tuple_like _UTuple> requires (!__is_tuple_v<_UTuple>) friend constexpr bool - operator==(const tuple& __t, const _UTuple& __u) + operator== [[nodiscard]] (const tuple& __t, const _UTuple& __u) { static_assert(sizeof...(_Elements) == tuple_size_v<_UTuple>, "tuple objects can only be compared if they have equal sizes."); @@ -2521,6 +2516,58 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } #endif +#if __cpp_lib_three_way_comparison + template + requires (sizeof...(_Tps) == sizeof...(_Ups)) + && (requires (const _Tps& __t, const _Ups& __u) { + { __t == __u } -> __detail::__boolean_testable; + } && ...) + constexpr bool + operator== [[nodiscard]] (const tuple<_Tps...>& __t, + const tuple<_Ups...>& __u) + { + return [&](index_sequence<_Inds...>) { + // Fold == over the tuples until non-equal elements are found. + return ((std::get<_Inds>(__t) == std::get<_Inds>(__u)) && ...); + }(index_sequence_for<_Tps...>{}); + } + + template + [[nodiscard]] + constexpr _Cat + __tuple_cmp(const _Tp& __t, const _Up& __u, _IndexSeq __indices) + { + _Cat __c = _Cat::equivalent; + + // Set __c to the comparison result of two corresponding elements. + // Return true they are equivalent. + auto __cmp = [&](integral_constant) { + __c = __detail::__synth3way(std::get<_Ind>(__t), std::get<_Ind>(__u)); + return __c == 0; + }; + + [&](index_sequence<_Inds...>) { + // Fold __cmp over the tuples until non-equivalent elements are found. + (void)(__cmp(integral_constant{}) && ...); + }(__indices); + + return __c; + } + + template + requires (sizeof...(_Tps) == sizeof...(_Ups)) + && (requires { typename __detail::__synth3way_t<_Tps, _Ups>; } && ...) + constexpr + common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...> + operator<=> [[nodiscard]] (const tuple<_Tps...>& __t, + const tuple<_Ups...>& __u) + { + using _Cat + = common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...>; + return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Tps...>()); + } +#else + // This class performs the comparison operations on tuples template struct __tuple_compare @@ -2552,6 +2599,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; template + _GLIBCXX_NODISCARD constexpr bool operator==(const tuple<_TElements...>& __t, const tuple<_UElements...>& __u) @@ -2564,36 +2612,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return __compare::__eq(__t, __u); } -#if __cpp_lib_three_way_comparison - template - constexpr _Cat - __tuple_cmp(const _Tp&, const _Up&, index_sequence<>) - { return _Cat::equivalent; } - - template - constexpr _Cat - __tuple_cmp(const _Tp& __t, const _Up& __u, - index_sequence<_Idx0, _Idxs...>) - { - auto __c - = __detail::__synth3way(std::get<_Idx0>(__t), std::get<_Idx0>(__u)); - if (__c != 0) - return __c; - return std::__tuple_cmp<_Cat>(__t, __u, index_sequence<_Idxs...>()); - } - - template - constexpr - common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...> - operator<=>(const tuple<_Tps...>& __t, const tuple<_Ups...>& __u) - { - using _Cat - = common_comparison_category_t<__detail::__synth3way_t<_Tps, _Ups>...>; - return std::__tuple_cmp<_Cat>(__t, __u, index_sequence_for<_Tps...>()); - } -#else template + _GLIBCXX_NODISCARD constexpr bool operator<(const tuple<_TElements...>& __t, const tuple<_UElements...>& __u) @@ -2607,24 +2627,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } template + _GLIBCXX_NODISCARD constexpr bool operator!=(const tuple<_TElements...>& __t, const tuple<_UElements...>& __u) { return !(__t == __u); } template + _GLIBCXX_NODISCARD constexpr bool operator>(const tuple<_TElements...>& __t, const tuple<_UElements...>& __u) { return __u < __t; } template + _GLIBCXX_NODISCARD constexpr bool operator<=(const tuple<_TElements...>& __t, const tuple<_UElements...>& __u) { return !(__u < __t); } template + _GLIBCXX_NODISCARD constexpr bool operator>=(const tuple<_TElements...>& __t, const tuple<_UElements...>& __u) diff --git a/libstdc++-v3/include/std/utility b/libstdc++-v3/include/std/utility index 212513f6f48..56467160a2f 100644 --- a/libstdc++-v3/include/std/utility +++ b/libstdc++-v3/include/std/utility @@ -93,6 +93,7 @@ #define __glibcxx_want_tuples_by_type #define __glibcxx_want_unreachable #define __glibcxx_want_tuple_like +#define __glibcxx_want_constrained_equality #include namespace std _GLIBCXX_VISIBILITY(default) diff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant index f79d95db7a8..5ba6d9d42e3 100644 --- a/libstdc++-v3/include/std/variant +++ b/libstdc++-v3/include/std/variant @@ -33,6 +33,7 @@ #define __glibcxx_want_freestanding_variant #define __glibcxx_want_variant +#define __glibcxx_want_constrained_equality #include #ifdef __cpp_lib_variant // C++ >= 17 @@ -1236,9 +1237,19 @@ namespace __variant struct monostate { }; +#if __cpp_lib_concepts +# define _VARIANT_RELATION_FUNCTION_CONSTRAINTS(TYPES, OP) \ + requires ((requires (const TYPES& __t) { \ + { __t OP __t } -> __detail::__boolean_testable; }) && ...) +#else +# define _VARIANT_RELATION_FUNCTION_CONSTRAINTS(TYPES, OP) +#endif + #define _VARIANT_RELATION_FUNCTION_TEMPLATE(__OP, __NAME) \ template \ - constexpr bool operator __OP(const variant<_Types...>& __lhs, \ + _VARIANT_RELATION_FUNCTION_CONSTRAINTS(_Types, __OP) \ + constexpr bool \ + operator __OP [[nodiscard]] (const variant<_Types...>& __lhs, \ const variant<_Types...>& __rhs) \ { \ bool __ret = true; \ @@ -1690,21 +1701,6 @@ namespace __variant template friend constexpr decltype(auto) __detail::__variant::__get(_Vp&& __v) noexcept; - -#define _VARIANT_RELATION_FUNCTION_TEMPLATE(__OP) \ - template \ - friend constexpr bool \ - operator __OP(const variant<_Tp...>& __lhs, \ - const variant<_Tp...>& __rhs); - - _VARIANT_RELATION_FUNCTION_TEMPLATE(<) - _VARIANT_RELATION_FUNCTION_TEMPLATE(<=) - _VARIANT_RELATION_FUNCTION_TEMPLATE(==) - _VARIANT_RELATION_FUNCTION_TEMPLATE(!=) - _VARIANT_RELATION_FUNCTION_TEMPLATE(>=) - _VARIANT_RELATION_FUNCTION_TEMPLATE(>) - -#undef _VARIANT_RELATION_FUNCTION_TEMPLATE }; template diff --git a/libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc b/libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc new file mode 100644 index 00000000000..0e325618008 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/optional/relops/constrained.cc @@ -0,0 +1,258 @@ +// { dg-do compile { target c++20 } } + +#include + +#ifndef __cpp_lib_constrained_equality +# error "Feature-test macro for constrained_equality missing" +#elif __cpp_lib_constrained_equality != 202403 +# error "Feature-test macro for constrained_equality has wrong value" +#endif + +template +concept eq_comparable += requires (const std::optional& t, const std::optional& u) { + t == u; + *t == u; + t == *u; +}; + +template +concept ne_comparable += requires (const std::optional& t, const std::optional& u) { + t != u; + *t != u; + t != *u; +}; + +template +concept lt_comparable += requires (const std::optional& t, const std::optional& u) { + t < u; + *t < u; + t < *u; +}; + +template +concept le_comparable += requires (const std::optional& t, const std::optional& u) { + t <= u; + *t <= u; + t <= *u; +}; + +template +concept gt_comparable += requires (const std::optional& t, const std::optional& u) { + t > u; + *t > u; + t > *u; +}; + +template +concept ge_comparable += requires (const std::optional& t, const std::optional& u) { + t >= u; + *t >= u; + t >= *u; +}; + +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); + +struct A { }; +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct B { }; +void operator==(B, B); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct C { }; +bool operator==(C, C); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); + +struct D { }; +int operator==(D, D); +bool operator!=(D, D) = delete; +static_assert( eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( eq_comparable ); +static_assert( ! ne_comparable ); + +struct E { }; +bool operator==(/* not-const */ E&, const E&); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( ! eq_comparable ); +static_assert( ! eq_comparable ); + +struct F { }; +bool operator<(F, F); +void operator>(F, F); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( lt_comparable ); +static_assert( lt_comparable ); + +struct G { }; +bool operator<=(G, G); +void operator<(G, G); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( le_comparable ); +static_assert( le_comparable ); + +struct H { }; +bool operator>(H, H); +void operator>=(H, H); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( gt_comparable ); +static_assert( gt_comparable ); + +struct I { }; +bool operator>=(I, I); +void operator<=(I, I); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ge_comparable ); +static_assert( ge_comparable ); +static_assert( ge_comparable ); + +struct J { }; +bool operator==(J, J); +std::weak_ordering operator<=>(J, J); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); + +struct K { }; +int operator==(K, K); // non-bool prevents synthesis of != +void operator<=(K, K); +std::weak_ordering operator<=>(K, K); +static_assert( eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( lt_comparable ); +static_assert( ! le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); + +bool operator==(A, B); +static_assert( eq_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +int operator==(C, D); // non-bool prevents synthesis of != and reversed args +static_assert( eq_comparable ); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +std::weak_ordering operator<=>(E, F); +static_assert( ! eq_comparable ); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); diff --git a/libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc b/libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc new file mode 100644 index 00000000000..a35dbd265a7 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/pair/comparison_operators/constrained.cc @@ -0,0 +1,48 @@ +// { dg-do compile { target c++20 } } + +#include + +#ifndef __cpp_lib_constrained_equality +# error "Feature-test macro for constrained_equality missing" +#elif __cpp_lib_constrained_equality != 202403 +# error "Feature-test macro for constrained_equality has wrong value" +#endif + +template +concept equality_comparable = requires (const T& t) { t == t; t != t; }; + +static_assert( equality_comparable> ); + +struct A { }; +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct B { }; +void operator==(B, B); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct C { }; +int operator==(C, C); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct D { }; +bool operator==(D, D); +bool operator!=(D, D) = delete; +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct E { }; +bool operator==(/* not-const */ E&, const E&); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); diff --git a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc new file mode 100644 index 00000000000..47035ab18ba --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/constrained.cc @@ -0,0 +1,50 @@ +// { dg-do compile { target c++20 } } + +#include + +#ifndef __cpp_lib_constrained_equality +# error "Feature-test macro for constrained_equality missing" +#elif __cpp_lib_constrained_equality != 202403 +# error "Feature-test macro for constrained_equality has wrong value" +#endif + +template +concept equality_comparable = requires (const T& t) { t == t; t != t; }; + +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); + +struct A { }; +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct B { }; +void operator==(B, B); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct C { }; +int operator==(C, C); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct D { }; +bool operator==(D, D); +bool operator!=(D, D) = delete; +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( equality_comparable> ); +static_assert( ! equality_comparable> ); + +struct E { }; +bool operator==(/* not-const */ E&, const E&); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); +static_assert( ! equality_comparable> ); diff --git a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc index 9f8a0f91785..7ae7f42d1a1 100644 --- a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc +++ b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded.cc @@ -1,4 +1,5 @@ -// { dg-do compile { target c++11 } } +// { dg-do compile { target { c++11 && c++17_down } } } +// Not valid in C++20, because TwistedLogic doesn't model boolean-testable. // Copyright (C) 2014-2024 Free Software Foundation, Inc. // @@ -49,8 +50,5 @@ TwistedLogic operator<(const Compares&, const Compares&) { return {false}; } auto a = std::make_tuple(nullptr, Compares{}, 2, 'U'); auto b = a == a; -#if ! __cpp_lib_three_way_comparison -// Not valid in C++20, because TwistedLogic doesn't model boolean-testable. auto c = std::make_tuple("", Compares{}, 2, 'U'); auto d = c < c; -#endif diff --git a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc index 19617a5676e..8ea7ed0b797 100644 --- a/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc +++ b/libstdc++-v3/testsuite/20_util/tuple/comparison_operators/overloaded2.cc @@ -50,5 +50,4 @@ auto a = std::make_tuple(nullptr, Compares{}, 2, 'U'); auto b = a < a; // { dg-error "no match for 'operator<'" "" { target c++20 } 0 } -// { dg-error "no match for .*_Synth3way|in requirements" "" { target c++20 } 0 } // { dg-error "ordered comparison" "" { target c++17_down } 0 } diff --git a/libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc b/libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc new file mode 100644 index 00000000000..95e8f754d1e --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/variant/relops/constrained.cc @@ -0,0 +1,175 @@ +// { dg-do compile { target c++20 } } + +#include + +#ifndef __cpp_lib_constrained_equality +# error "Feature-test macro for constrained_equality missing" +#elif __cpp_lib_constrained_equality != 202403 +# error "Feature-test macro for constrained_equality has wrong value" +#endif + +template +concept eq_comparable += requires (const std::variant& t) { t == t; }; + +template +concept ne_comparable += requires (const std::variant& t) { t != t; }; + +template +concept lt_comparable += requires (const std::variant& t) { t < t; }; + +template +concept le_comparable += requires (const std::variant& t) { t <= t; }; + +template +concept gt_comparable += requires (const std::variant& t) { t > t; }; + +template +concept ge_comparable += requires (const std::variant& t) { t >= t; }; + +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); + +struct A { }; +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct B { }; +void operator==(B, B); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct C { }; +bool operator==(C, C); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( ne_comparable ); + +struct D { }; +int operator==(D, D); // variant's operator== returns bool despite int here +bool operator!=(D, D) = delete; +static_assert( eq_comparable ); +static_assert( ne_comparable ); // variant's operator== can be used +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct E { }; +bool operator==(/* not-const */ E&, const E&); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct F { }; +bool operator<(F, F); +void operator>(F, F); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct G { }; +bool operator<=(G, G); +void operator<(G, G); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ! ge_comparable ); + +struct H { }; +bool operator>(H, H); +void operator>=(H, H); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( gt_comparable ); +static_assert( ! ge_comparable ); +static_assert( gt_comparable ); +static_assert( gt_comparable ); + +struct I { }; +bool operator>=(I, I); +void operator<=(I, I); +static_assert( ! eq_comparable ); +static_assert( ! ne_comparable ); +static_assert( ! lt_comparable ); +static_assert( ! le_comparable ); +static_assert( ! gt_comparable ); +static_assert( ge_comparable ); +static_assert( ge_comparable ); +static_assert( ge_comparable ); + +struct J { }; +bool operator==(J, J); +std::weak_ordering operator<=>(J, J); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); + +struct K { }; +int operator==(K, K); // variant's operator== returns bool despite int here +void operator<=(K, K); +std::weak_ordering operator<=>(K, K); +static_assert( eq_comparable ); +static_assert( ne_comparable ); // variant's operator== can be used +static_assert( lt_comparable ); +static_assert( ! le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable ); +static_assert( eq_comparable ); +static_assert( ne_comparable ); +static_assert( lt_comparable ); +static_assert( ! le_comparable ); +static_assert( gt_comparable ); +static_assert( ge_comparable );