Implement C++20 operator<=>.
authorJason Merrill <jason@gcc.gnu.org>
Tue, 5 Nov 2019 23:56:18 +0000 (18:56 -0500)
committerJason Merrill <jason@gcc.gnu.org>
Tue, 5 Nov 2019 23:56:18 +0000 (18:56 -0500)
There are three major pieces to this support: scalar operator<=>,
synthesis of comparison operators, and rewritten/reversed overload
resolution (e.g. a < b becomes 0 > b <=> a).

Unlike other defaulted functions, where we use synthesized_method_walk to
semi-simulate what the definition of the function will be like, this patch
determines the characteristics of a comparison operator by trying to define
it.

My handling of non-dependent rewritten operators in templates can still use
some work: build_min_non_dep_op_overload can't understand the rewrites and
crashes, so I'm avoiding it for now by clearing *overload.  This means we'll
do name lookup again at instantiation time, which can incorrectly mean a
different result.  I'll poke at this more in stage 3.

I'm leaving out a fourth section ("strong structural equality") even though
I've implemented it, because it seems likely to change radically tomorrow.

Thanks to Tim van Deurzen and Jakub for implementing lexing of the <=>
operator, and Jonathan for the initial <compare> header.

gcc/cp/
* cp-tree.h (struct lang_decl_fn): Add maybe_deleted bitfield.
(DECL_MAYBE_DELETED): New.
(enum special_function_kind): Add sfk_comparison.
(LOOKUP_REWRITTEN, LOOKUP_REVERSED): New.
* call.c (struct z_candidate): Add rewritten and reversed methods.
(add_builtin_candidate): Handle SPACESHIP_EXPR.
(add_builtin_candidates): Likewise.
(add_candidates): Don't add a reversed candidate if the parms are
the same.
(add_operator_candidates): Split out from build_new_op_1.  Handle
rewritten and reversed candidates.
(add_candidate): Swap conversions of reversed candidate.
(build_new_op_1): Swap them back.  Build a second operation for
rewritten candidates.
(extract_call_expr): Handle rewritten calls.
(same_fn_or_template): New.
(joust): Handle rewritten and reversed candidates.
* class.c (add_implicitly_declared_members): Add implicit op==.
(classtype_has_op, classtype_has_defaulted_op): New.
* constexpr.c (cxx_eval_binary_expression): Handle SPACESHIP_EXPR.
(cxx_eval_constant_expression, potential_constant_expression_1):
Likewise.
* cp-gimplify.c (genericize_spaceship): New.
(cp_genericize_r): Use it.
* cp-objcp-common.c (cp_common_init_ts): Handle SPACESHIP_EXPR.
* decl.c (finish_function): Handle deleted function.
* decl2.c (grokfield): SET_DECL_FRIEND_CONTEXT on defaulted friend.
(mark_used): Check DECL_MAYBE_DELETED.  Remove assumption that
defaulted functions are non-static members.
* error.c (dump_expr): Handle SPACESHIP_EXPR.
* method.c (type_has_trivial_fn): False for sfk_comparison.
(enum comp_cat_tag, struct comp_cat_info_t): New types.
(comp_cat_cache): New array variable.
(lookup_comparison_result, lookup_comparison_category)
(is_cat, cat_tag_for, spaceship_comp_cat)
(spaceship_type, genericize_spaceship)
(common_comparison_type, early_check_defaulted_comparison)
(comp_info, build_comparison_op): New.
(synthesize_method): Handle sfk_comparison.  Handle deleted.
(get_defaulted_eh_spec, maybe_explain_implicit_delete)
(explain_implicit_non_constexpr, implicitly_declare_fn)
(defaulted_late_check, defaultable_fn_check): Handle sfk_comparison.
* name-lookup.c (get_std_name_hint): Add comparison categories.
* tree.c (special_function_p): Add sfk_comparison.
* typeck.c (cp_build_binary_op): Handle SPACESHIP_EXPR.

2019-11-05  Tim van Deurzen  <tim@kompiler.org>

Add new tree code for the spaceship operator.
gcc/cp/
* cp-tree.def: Add new tree code.
* operators.def: New binary operator.
* parser.c: Add new token and tree code.
libcpp/
* cpplib.h: Add spaceship operator for C++.
* lex.c: Implement conditional lexing of spaceship operator for C++20.

2019-11-05  Jonathan Wakely  <jwakely@redhat.com>

libstdc++-v3/
* libsupc++/compare: New header.
* libsupc++/Makefile.am (std_HEADERS): Add compare.
* include/std/version: Define __cpp_lib_three_way_comparison.
* include/std/functional: #include <compare>.

From-SVN: r277865

56 files changed:
gcc/c-family/c-cppbuiltin.c
gcc/cp/ChangeLog
gcc/cp/call.c
gcc/cp/class.c
gcc/cp/constexpr.c
gcc/cp/cp-gimplify.c
gcc/cp/cp-objcp-common.c
gcc/cp/cp-tree.def
gcc/cp/cp-tree.h
gcc/cp/decl.c
gcc/cp/decl2.c
gcc/cp/error.c
gcc/cp/method.c
gcc/cp/name-lookup.c
gcc/cp/operators.def
gcc/cp/parser.c
gcc/cp/pt.c
gcc/cp/tree.c
gcc/cp/typeck.c
gcc/testsuite/c-c++-common/cpp/spaceship-1.c [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp/spaceship-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/lookup/pr21802.C
gcc/testsuite/g++.old-deja/g++.robertl/eb22.C
libcpp/ChangeLog
libcpp/include/cpplib.h
libcpp/lex.c
libstdc++-v3/ChangeLog
libstdc++-v3/include/Makefile.am
libstdc++-v3/include/std/functional
libstdc++-v3/include/std/version
libstdc++-v3/libsupc++/Makefile.am
libstdc++-v3/libsupc++/compare [new file with mode: 0644]
libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc

index f9cd76b2c86914c5c8a58e84915cdc77c10c55e2..cf3d437fc37065704448984a0b284c916ba73509 100644 (file)
@@ -990,6 +990,7 @@ c_cpp_builtins (cpp_reader *pfile)
          cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806L");
          cpp_define (pfile, "__cpp_impl_destroying_delete=201806L");
          cpp_define (pfile, "__cpp_constexpr_dynamic_alloc=201907L");
+         cpp_define (pfile, "__cpp_impl_three_way_comparison=201907L");
        }
       if (flag_concepts)
         {
index 560896a1dd8154b6968b182b57f88764a85701ad..d0b47f7c5629ed10b298c98a49fb761eced9810d 100644 (file)
@@ -1,3 +1,59 @@
+2019-11-05  Jason Merrill  <jason@redhat.com>
+
+       Implement C++20 operator<=>.
+       * cp-tree.h (struct lang_decl_fn): Add maybe_deleted bitfield.
+       (DECL_MAYBE_DELETED): New.
+       (enum special_function_kind): Add sfk_comparison.
+       (LOOKUP_REWRITTEN, LOOKUP_REVERSED): New.
+       * call.c (struct z_candidate): Add rewritten and reversed methods.
+       (add_builtin_candidate): Handle SPACESHIP_EXPR.
+       (add_builtin_candidates): Likewise.
+       (add_candidates): Don't add a reversed candidate if the parms are
+       the same.
+       (add_operator_candidates): Split out from build_new_op_1.  Handle
+       rewritten and reversed candidates.
+       (add_candidate): Swap conversions of reversed candidate.
+       (build_new_op_1): Swap them back.  Build a second operation for
+       rewritten candidates.
+       (extract_call_expr): Handle rewritten calls.
+       (same_fn_or_template): New.
+       (joust): Handle rewritten and reversed candidates.
+       * class.c (add_implicitly_declared_members): Add implicit op==.
+       (classtype_has_op, classtype_has_defaulted_op): New.
+       * constexpr.c (cxx_eval_binary_expression): Handle SPACESHIP_EXPR.
+       (cxx_eval_constant_expression, potential_constant_expression_1):
+       Likewise.
+       * cp-gimplify.c (genericize_spaceship): New.
+       (cp_genericize_r): Use it.
+       * cp-objcp-common.c (cp_common_init_ts): Handle SPACESHIP_EXPR.
+       * decl.c (finish_function): Handle deleted function.
+       * decl2.c (grokfield): SET_DECL_FRIEND_CONTEXT on defaulted friend.
+       (mark_used): Check DECL_MAYBE_DELETED.  Remove assumption that
+       defaulted functions are non-static members.
+       * error.c (dump_expr): Handle SPACESHIP_EXPR.
+       * method.c (type_has_trivial_fn): False for sfk_comparison.
+       (enum comp_cat_tag, struct comp_cat_info_t): New types.
+       (comp_cat_cache): New array variable.
+       (lookup_comparison_result, lookup_comparison_category)
+       (is_cat, cat_tag_for, spaceship_comp_cat)
+       (spaceship_type, genericize_spaceship)
+       (common_comparison_type, early_check_defaulted_comparison)
+       (comp_info, build_comparison_op): New.
+       (synthesize_method): Handle sfk_comparison.  Handle deleted.
+       (get_defaulted_eh_spec, maybe_explain_implicit_delete)
+       (explain_implicit_non_constexpr, implicitly_declare_fn)
+       (defaulted_late_check, defaultable_fn_check): Handle sfk_comparison.
+       * name-lookup.c (get_std_name_hint): Add comparison categories.
+       * tree.c (special_function_p): Add sfk_comparison.
+       * typeck.c (cp_build_binary_op): Handle SPACESHIP_EXPR.
+
+2019-11-05  Tim van Deurzen  <tim@kompiler.org>
+
+       Add new tree code for the spaceship operator.
+       * cp-tree.def: Add new tree code.
+       * operators.def: New binary operator.
+       * parser.c: Add new token and tree code.
+
 2019-09-15  Jason Merrill  <jason@redhat.com>
 
        * call.c (build_new_op_1): Don't apply any standard conversions to
index 390a4c581e2c922f587b80a3db9973cbd8f6314f..0034c1cee0da8e5061813438007d86e742952bab 100644 (file)
@@ -514,6 +514,9 @@ struct z_candidate {
 
   /* The flags active in add_candidate.  */
   int flags;
+
+  bool rewritten () { return (flags & LOOKUP_REWRITTEN); }
+  bool reversed () { return (flags & LOOKUP_REVERSED); }
 };
 
 /* Returns true iff T is a null pointer constant in the sense of
@@ -2106,6 +2109,11 @@ add_candidate (struct z_candidate **candidates,
   cand->flags = flags;
   *candidates = cand;
 
+  if (convs && cand->reversed ())
+    /* Swap the conversions for comparison in joust; we'll swap them back
+       before build_over_call.  */
+    std::swap (convs[0], convs[1]);
+
   return cand;
 }
 
@@ -2737,6 +2745,16 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
      where  LR  is  the  result of the usual arithmetic conversions between
      types L and R.
 
+     For every integral type T there exists a candidate operator function of
+     the form
+
+       std::strong_ordering operator<=>(T, T);
+
+     For every pair of floating-point types L and R, there exists a candidate
+     operator function of the form
+
+       std::partial_ordering operator<=>(L, R);
+
    14For every pair of types T and I, where T  is  a  cv-qualified  or  cv-
      unqualified  complete  object  type and I is a promoted integral type,
      there exist candidate operator functions of the form
@@ -2758,11 +2776,15 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
             bool    operator>=(T, T);
             bool    operator==(T, T);
             bool    operator!=(T, T);
+            R       operator<=>(T, T);
+
+     where R is the result type specified in [expr.spaceship].
 
    17For every pointer to member type T,  there  exist  candidate  operator
      functions of the form
             bool    operator==(T, T);
-            bool    operator!=(T, T);  */
+            bool    operator!=(T, T);
+            std::strong_equality operator<=>(T, T);  */
 
     case MINUS_EXPR:
       if (TYPE_PTROB_P (type1) && TYPE_PTROB_P (type2))
@@ -2780,6 +2802,11 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
        break;
       return;
 
+      /* This isn't exactly what's specified above for operator<=>, but it's
+        close enough.  In particular, we don't care about the return type
+        specified above; it doesn't participate in overload resolution and it
+        doesn't affect the semantics of the built-in operator.  */
+    case SPACESHIP_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
       if ((TYPE_PTRMEMFUNC_P (type1) && TYPE_PTRMEMFUNC_P (type2))
@@ -3138,6 +3165,7 @@ add_builtin_candidates (struct z_candidate **candidates, enum tree_code code,
     case LE_EXPR:
     case GT_EXPR:
     case GE_EXPR:
+    case SPACESHIP_EXPR:
       enum_p = 1;
       /* Fall through.  */
 
@@ -5740,6 +5768,15 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
          fn_args = non_static_args;
        }
 
+      /* Don't bother reversing an operator with two identical parameters.  */
+      else if (args->length () == 2 && (flags & LOOKUP_REVERSED))
+       {
+         tree parmlist = TYPE_ARG_TYPES (TREE_TYPE (fn));
+         if (same_type_p (TREE_VALUE (parmlist),
+                          TREE_VALUE (TREE_CHAIN (parmlist))))
+           continue;
+       }
+
       if (TREE_CODE (fn) == TEMPLATE_DECL)
        add_template_candidate (candidates,
                                fn,
@@ -5800,6 +5837,178 @@ op_is_ordered (tree_code code)
     }
 }
 
+/* Subroutine of build_new_op_1: Add to CANDIDATES all candidates for the
+   operator indicated by CODE/CODE2.  This function calls itself recursively to
+   handle C++20 rewritten comparison operator candidates.  */
+
+static tree
+add_operator_candidates (z_candidate **candidates,
+                        tree_code code, tree_code code2,
+                        vec<tree, va_gc> *arglist,
+                        int flags, tsubst_flags_t complain)
+{
+  z_candidate *start_candidates = *candidates;
+  bool ismodop = code2 != ERROR_MARK;
+  tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
+
+  /* LOOKUP_REWRITTEN is set when we're looking for the == or <=> operator to
+     rewrite from, and also when we're looking for the e.g. < operator to use
+     on the result of <=>.  In the latter case, we don't want the flag set in
+     the candidate, we just want to suppress looking for rewrites.  */
+  bool rewritten = (flags & LOOKUP_REWRITTEN);
+  if (rewritten && code != EQ_EXPR && code != SPACESHIP_EXPR)
+    flags &= ~LOOKUP_REWRITTEN;
+
+  bool memonly = false;
+  switch (code)
+    {
+      /* =, ->, [], () must be non-static member functions.  */
+    case MODIFY_EXPR:
+      if (code2 != NOP_EXPR)
+       break;
+      /* FALLTHRU */
+    case COMPONENT_REF:
+    case ARRAY_REF:
+      memonly = true;
+      break;
+
+    default:
+      break;
+    }
+
+  /* Add namespace-scope operators to the list of functions to
+     consider.  */
+  if (!memonly)
+    {
+      tree fns = lookup_name_real (fnname, 0, 1, /*block_p=*/true, 0, 0);
+      fns = lookup_arg_dependent (fnname, fns, arglist);
+      add_candidates (fns, NULL_TREE, arglist, NULL_TREE,
+                     NULL_TREE, false, NULL_TREE, NULL_TREE,
+                     flags, candidates, complain);
+    }
+
+  /* Add class-member operators to the candidate set.  */
+  tree arg1_type = TREE_TYPE ((*arglist)[0]);
+  unsigned nargs = arglist->length () > 1 ? 2 : 1;
+  tree arg2_type = nargs > 1 ? TREE_TYPE ((*arglist)[1]) : NULL_TREE;
+  if (CLASS_TYPE_P (arg1_type))
+    {
+      tree fns = lookup_fnfields (arg1_type, fnname, 1);
+      if (fns == error_mark_node)
+       return error_mark_node;
+      if (fns)
+       add_candidates (BASELINK_FUNCTIONS (fns),
+                       NULL_TREE, arglist, NULL_TREE,
+                       NULL_TREE, false,
+                       BASELINK_BINFO (fns),
+                       BASELINK_ACCESS_BINFO (fns),
+                       flags, candidates, complain);
+    }
+  /* Per [over.match.oper]3.2, if no operand has a class type, then
+     only non-member functions that have type T1 or reference to
+     cv-qualified-opt T1 for the first argument, if the first argument
+     has an enumeration type, or T2 or reference to cv-qualified-opt
+     T2 for the second argument, if the second argument has an
+     enumeration type.  Filter out those that don't match.  */
+  else if (! arg2_type || ! CLASS_TYPE_P (arg2_type))
+    {
+      struct z_candidate **candp, **next;
+
+      for (candp = candidates; *candp != start_candidates; candp = next)
+       {
+         unsigned i;
+         z_candidate *cand = *candp;
+         next = &cand->next;
+
+         tree parmlist = TYPE_ARG_TYPES (TREE_TYPE (cand->fn));
+
+         for (i = 0; i < nargs; ++i)
+           {
+             tree parmtype = TREE_VALUE (parmlist);
+             tree argtype = unlowered_expr_type ((*arglist)[i]);
+
+             if (TYPE_REF_P (parmtype))
+               parmtype = TREE_TYPE (parmtype);
+             if (TREE_CODE (argtype) == ENUMERAL_TYPE
+                 && (same_type_ignoring_top_level_qualifiers_p
+                     (argtype, parmtype)))
+               break;
+
+             parmlist = TREE_CHAIN (parmlist);
+           }
+
+         /* No argument has an appropriate type, so remove this
+            candidate function from the list.  */
+         if (i == nargs)
+           {
+             *candp = cand->next;
+             next = candp;
+           }
+       }
+    }
+
+  if (!rewritten)
+    {
+      /* The standard says to rewrite built-in candidates, too,
+        but there's no point.  */
+      add_builtin_candidates (candidates, code, code2, fnname, arglist,
+                             flags, complain);
+
+      /* Maybe add C++20 rewritten comparison candidates.  */
+      tree_code rewrite_code = ERROR_MARK;
+      if (cxx_dialect >= cxx2a
+         && nargs == 2
+         && (OVERLOAD_TYPE_P (arg1_type) || OVERLOAD_TYPE_P (arg2_type)))
+       switch (code)
+         {
+         case LT_EXPR:
+         case LE_EXPR:
+         case GT_EXPR:
+         case GE_EXPR:
+         case SPACESHIP_EXPR:
+           rewrite_code = SPACESHIP_EXPR;
+           break;
+
+         case NE_EXPR:
+         case EQ_EXPR:
+           rewrite_code = EQ_EXPR;
+           break;
+
+         default:;
+         }
+
+      if (rewrite_code)
+       {
+         flags |= LOOKUP_REWRITTEN;
+         if (rewrite_code != code)
+           /* Add rewritten candidates in same order.  */
+           add_operator_candidates (candidates, rewrite_code, ERROR_MARK,
+                                    arglist, flags, complain);
+
+         z_candidate *save_cand = *candidates;
+
+         /* Add rewritten candidates in reverse order.  */
+         flags |= LOOKUP_REVERSED;
+         vec<tree,va_gc> *revlist = make_tree_vector ();
+         revlist->quick_push ((*arglist)[1]);
+         revlist->quick_push ((*arglist)[0]);
+         add_operator_candidates (candidates, rewrite_code, ERROR_MARK,
+                                  revlist, flags, complain);
+
+         /* Release the vec if we didn't add a candidate that uses it.  */
+         for (z_candidate *c = *candidates; c != save_cand; c = c->next)
+           if (c->args == revlist)
+             {
+               revlist = NULL;
+               break;
+             }
+         release_tree_vector (revlist);
+       }
+    }
+
+  return NULL_TREE;
+}
+
 static tree
 build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
                tree arg1, tree arg2, tree arg3, tree *overload,
@@ -5809,7 +6018,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
   vec<tree, va_gc> *arglist;
   tree result = NULL_TREE;
   bool result_valid_p = false;
-  enum tree_code code2 = NOP_EXPR;
+  enum tree_code code2 = ERROR_MARK;
   enum tree_code code_orig_arg1 = ERROR_MARK;
   enum tree_code code_orig_arg2 = ERROR_MARK;
   conversion *conv;
@@ -5828,14 +6037,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
       code2 = TREE_CODE (arg3);
       arg3 = NULL_TREE;
     }
-  tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
 
   tree arg1_type = unlowered_expr_type (arg1);
   tree arg2_type = arg2 ? unlowered_expr_type (arg2) : NULL_TREE;
 
   arg1 = prep_operand (arg1);
 
-  bool memonly = false;
   switch (code)
     {
     case NEW_EXPR:
@@ -5868,16 +6075,6 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
       code_orig_arg2 = TREE_CODE (arg2_type);
       break;
 
-      /* =, ->, [], () must be non-static member functions.  */
-    case MODIFY_EXPR:
-      if (code2 != NOP_EXPR)
-       break;
-      /* FALLTHRU */
-    case COMPONENT_REF:
-    case ARRAY_REF:
-      memonly = true;
-      break;
-
     default:
       break;
     }
@@ -5908,82 +6105,10 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
   /* Get the high-water mark for the CONVERSION_OBSTACK.  */
   p = conversion_obstack_alloc (0);
 
-  /* Add namespace-scope operators to the list of functions to
-     consider.  */
-  if (!memonly)
-    {
-      tree fns = lookup_name_real (fnname, 0, 1, /*block_p=*/true, 0, 0);
-      fns = lookup_arg_dependent (fnname, fns, arglist);
-      add_candidates (fns, NULL_TREE, arglist, NULL_TREE,
-                     NULL_TREE, false, NULL_TREE, NULL_TREE,
-                     flags, &candidates, complain);
-    }
-
-  /* Add class-member operators to the candidate set.  */
-  if (CLASS_TYPE_P (arg1_type))
-    {
-      tree fns;
-
-      fns = lookup_fnfields (arg1_type, fnname, 1);
-      if (fns == error_mark_node)
-       {
-         result = error_mark_node;
-         goto user_defined_result_ready;
-       }
-      if (fns)
-       add_candidates (BASELINK_FUNCTIONS (fns),
-                       NULL_TREE, arglist, NULL_TREE,
-                       NULL_TREE, false,
-                       BASELINK_BINFO (fns),
-                       BASELINK_ACCESS_BINFO (fns),
-                       flags, &candidates, complain);
-    }
-  /* Per [over.match.oper]3.2, if no operand has a class type, then
-     only non-member functions that have type T1 or reference to
-     cv-qualified-opt T1 for the first argument, if the first argument
-     has an enumeration type, or T2 or reference to cv-qualified-opt
-     T2 for the second argument, if the second argument has an
-     enumeration type.  Filter out those that don't match.  */
-  else if (! arg2 || ! CLASS_TYPE_P (arg2_type))
-    {
-      struct z_candidate **candp, **next;
-
-      for (candp = &candidates; *candp; candp = next)
-       {
-         tree parmlist, parmtype;
-         int i, nargs = (arg2 ? 2 : 1);
-
-         cand = *candp;
-         next = &cand->next;
-
-         parmlist = TYPE_ARG_TYPES (TREE_TYPE (cand->fn));
-
-         for (i = 0; i < nargs; ++i)
-           {
-             parmtype = TREE_VALUE (parmlist);
-
-             if (TYPE_REF_P (parmtype))
-               parmtype = TREE_TYPE (parmtype);
-             if (TREE_CODE (unlowered_expr_type ((*arglist)[i])) == ENUMERAL_TYPE
-                 && (same_type_ignoring_top_level_qualifiers_p
-                     (unlowered_expr_type ((*arglist)[i]), parmtype)))
-               break;
-
-             parmlist = TREE_CHAIN (parmlist);
-           }
-
-         /* No argument has an appropriate type, so remove this
-            candidate function from the list.  */
-         if (i == nargs)
-           {
-             *candp = cand->next;
-             next = candp;
-           }
-       }
-    }
-
-  add_builtin_candidates (&candidates, code, code2, fnname, arglist,
-                         flags, complain);
+  result = add_operator_candidates (&candidates, code, code2, arglist,
+                                   flags, complain);
+  if (result == error_mark_node)
+    goto user_defined_result_ready;
 
   switch (code)
     {
@@ -6021,6 +6146,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
             -fpermissive.  */
          else
            {
+             tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
              const char *msg = (flag_permissive) 
                ? G_("no %<%D(int)%> declared for postfix %qs,"
                     " trying prefix operator instead")
@@ -6091,7 +6217,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
          if (resolve_args (arglist, complain) == NULL)
            result = error_mark_node;
          else
-           result = build_over_call (cand, LOOKUP_NORMAL, complain);
+           {
+             if (cand->reversed ())
+               /* We swapped these in add_candidate, swap them back now.  */
+               std::swap (cand->convs[0], cand->convs[1]);
+             result = build_over_call (cand, LOOKUP_NORMAL, complain);
+           }
 
          if (trivial_fn_p (cand->fn))
            /* There won't be a CALL_EXPR.  */;
@@ -6121,6 +6252,73 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
                  break;
                }
            }
+
+         /* If this was a C++20 rewritten comparison, adjust the result.  */
+         if (cand->rewritten ())
+           {
+             /* FIXME build_min_non_dep_op_overload can't handle rewrites.  */
+             if (overload)
+               *overload = NULL_TREE;
+             switch (code)
+               {
+               case EQ_EXPR:
+                 gcc_checking_assert (cand->reversed ());
+                 gcc_fallthrough ();
+               case NE_EXPR:
+                 /* If a rewritten operator== candidate is selected by
+                    overload resolution for an operator @, its return type
+                    shall be cv bool.... */
+                 if (TREE_CODE (TREE_TYPE (result)) != BOOLEAN_TYPE)
+                   {
+                     if (complain & tf_error)
+                       {
+                         auto_diagnostic_group d;
+                         error_at (loc, "return type of %qD is not %qs",
+                                   cand->fn, "bool");
+                         inform (loc, "used as rewritten candidate for "
+                                 "comparison of %qT and %qT",
+                                 arg1_type, arg2_type);
+                       }
+                     result = error_mark_node;
+                   }
+                 else if (code == NE_EXPR)
+                   /* !(y == x) or !(x == y)  */
+                   result = build1_loc (loc, TRUTH_NOT_EXPR,
+                                        boolean_type_node, result);
+                 break;
+
+                 /* If a rewritten operator<=> candidate is selected by
+                    overload resolution for an operator @, x @ y is
+                    interpreted as 0 @ (y <=> x) if the selected candidate is
+                    a synthesized candidate with reversed order of parameters,
+                    or (x <=> y) @ 0 otherwise, using the selected rewritten
+                    operator<=> candidate.  */
+               case SPACESHIP_EXPR:
+                 if (!cand->reversed ())
+                   /* We're in the build_new_op call below for an outer
+                      reversed call; we don't need to do anything more.  */
+                   break;
+                 gcc_fallthrough ();
+               case LT_EXPR:
+               case LE_EXPR:
+               case GT_EXPR:
+               case GE_EXPR:
+                 {
+                   tree lhs = result;
+                   tree rhs = integer_zero_node;
+                   if (cand->reversed ())
+                     std::swap (lhs, rhs);
+                   result = build_new_op (loc, code,
+                                          LOOKUP_NORMAL|LOOKUP_REWRITTEN,
+                                          lhs, rhs, NULL_TREE,
+                                          NULL, complain);
+                 }
+                 break;
+
+               default:
+                 gcc_unreachable ();
+               }
+           }
        }
       else
        {
@@ -6232,6 +6430,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
       if (complain & tf_warning && warn_tautological_compare)
        warn_tautological_cmp (loc, code, arg1, arg2);
       /* Fall through.  */
+    case SPACESHIP_EXPR:
     case PLUS_EXPR:
     case MINUS_EXPR:
     case MULT_EXPR:
@@ -6307,6 +6506,29 @@ extract_call_expr (tree call)
     call = TREE_OPERAND (call, 0);
   if (TREE_CODE (call) == TARGET_EXPR)
     call = TARGET_EXPR_INITIAL (call);
+  if (cxx_dialect >= cxx2a)
+    switch (TREE_CODE (call))
+      {
+       /* C++20 rewritten comparison operators.  */
+      case TRUTH_NOT_EXPR:
+       call = TREE_OPERAND (call, 0);
+       break;
+      case LT_EXPR:
+      case LE_EXPR:
+      case GT_EXPR:
+      case GE_EXPR:
+      case SPACESHIP_EXPR:
+       {
+         tree op0 = TREE_OPERAND (call, 0);
+         if (integer_zerop (op0))
+           call = TREE_OPERAND (call, 1);
+         else
+           call = op0;
+       }
+       break;
+      default:;
+      }
+
   gcc_assert (TREE_CODE (call) == CALL_EXPR
              || TREE_CODE (call) == AGGR_INIT_EXPR
              || call == error_mark_node);
@@ -10772,6 +10994,20 @@ joust_maybe_elide_copy (z_candidate *&cand)
   return false;
 }
 
+/* True if cand1 and cand2 represent the same function or function
+   template.  */
+
+static bool
+same_fn_or_template (z_candidate *cand1, z_candidate *cand2)
+{
+  if (cand1->fn == cand2->fn)
+    return true;
+  if (!cand1->template_decl || !cand2->template_decl)
+    return false;
+  return (most_general_template (TI_TEMPLATE (cand1->template_decl))
+         == most_general_template (TI_TEMPLATE (cand2->template_decl)));
+}
+
 /* Compare two candidates for overloading as described in
    [over.match.best].  Return values:
 
@@ -10798,6 +11034,7 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
   /* If we have two pseudo-candidates for conversions to the same type,
      or two candidates for the same function, arbitrarily pick one.  */
   if (cand1->fn == cand2->fn
+      && cand1->reversed () == cand2->reversed ()
       && (IS_TYPE_OR_DECL_P (cand1->fn)))
     return 1;
 
@@ -10917,6 +11154,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
 
          if (winner && comp != winner)
            {
+             if (same_fn_or_template (cand1, cand2))
+               {
+                 /* Ambiguity between normal and reversed versions of the
+                    same comparison operator; prefer the normal one.
+                    https://lists.isocpp.org/core/2019/10/7438.php  */
+                 if (cand1->reversed ())
+                   winner = -1;
+                 else
+                   {
+                     gcc_checking_assert (cand2->reversed ());
+                     winner = 1;
+                   }
+                 break;
+               }
+
              winner = 0;
              goto tweak;
            }
@@ -11046,6 +11298,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
        return winner;
     }
 
+  /* F2 is a rewritten candidate (12.4.1.2) and F1 is not, or F1 and F2 are
+     rewritten candidates, and F2 is a synthesized candidate with reversed
+     order of parameters and F1 is not.  */
+  if (cand1->rewritten ())
+    {
+      if (!cand2->rewritten ())
+       return -1;
+      if (!cand1->reversed () && cand2->reversed ())
+       return 1;
+      if (cand1->reversed () && !cand2->reversed ())
+       return -1;
+    }
+  else if (cand2->rewritten ())
+    return 1;
+
   /* F1 is generated from a deduction-guide (13.3.1.8) and F2 is not */
   if (deduction_guide_p (cand1->fn))
     {
index 3572707415660b9a09478a2ebf552ad010936c5d..89ed1c040f608e7ada985b1db3c7440530913976 100644 (file)
@@ -3234,6 +3234,17 @@ add_implicitly_declared_members (tree t, tree* access_decls,
      a virtual function from a base class.  */
   declare_virt_assop_and_dtor (t);
 
+  /* If the class definition does not explicitly declare an == operator
+     function, but declares a defaulted three-way comparison operator function,
+     an == operator function is declared implicitly.  */
+  if (!classtype_has_op (t, EQ_EXPR))
+    if (tree space = classtype_has_defaulted_op (t, SPACESHIP_EXPR))
+      {
+       tree eq = implicitly_declare_fn (sfk_comparison, t, false, space,
+                                        NULL_TREE);
+       add_method (t, eq, false);
+      }
+
   while (*access_decls)
     {
       tree using_decl = TREE_VALUE (*access_decls);
@@ -5386,6 +5397,44 @@ classtype_has_depr_implicit_copy (tree t)
   return NULL_TREE;
 }
 
+/* True iff T has a member or friend declaration of operator OP.  */
+
+bool
+classtype_has_op (tree t, tree_code op)
+{
+  tree name = ovl_op_identifier (op);
+  if (get_class_binding (t, name))
+    return true;
+  for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f))
+    if (FRIEND_NAME (f) == name)
+      return true;
+  return false;
+}
+
+
+/* If T has a defaulted member or friend declaration of OP, return it.  */
+
+tree
+classtype_has_defaulted_op (tree t, tree_code op)
+{
+  tree name = ovl_op_identifier (op);
+  for (ovl_iterator oi (get_class_binding (t, name)); oi; ++oi)
+    {
+      tree fn = *oi;
+      if (DECL_DEFAULTED_FN (fn))
+       return fn;
+    }
+  for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f))
+    if (FRIEND_NAME (f) == name)
+      for (tree l = FRIEND_DECLS (f); l; l = TREE_CHAIN (l))
+       {
+         tree fn = TREE_VALUE (l);
+         if (DECL_DEFAULTED_FN (fn))
+           return fn;
+       }
+  return NULL_TREE;
+}
+
 /* Nonzero if we need to build up a constructor call when initializing an
    object of this class, either because it has a user-declared constructor
    or because it doesn't have a default constructor (so we need to give an
index ce910cd5a3be81dc6fdcc19ab4b4338d3f2b6e56..20fddc57825a8f88920507d7f569e9f418007a77 100644 (file)
@@ -2480,6 +2480,12 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t,
   else if (code == POINTER_PLUS_EXPR)
     r = cxx_fold_pointer_plus_expression (ctx, t, lhs, rhs, non_constant_p,
                                          overflow_p);
+  else if (code == SPACESHIP_EXPR)
+    {
+      r = genericize_spaceship (type, lhs, rhs);
+      r = cxx_eval_constant_expression (ctx, r, false, non_constant_p,
+                                       overflow_p);
+    }
 
   if (r == NULL_TREE)
     r = fold_binary_loc (loc, code, type, lhs, rhs);
@@ -5226,6 +5232,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
     case GE_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
     case UNORDERED_EXPR:
     case ORDERED_EXPR:
     case UNLT_EXPR:
@@ -7037,6 +7044,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
     case GE_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
       want_rval = true;
       goto binary;
 
index abd82b3fe424f6c1e970e5cff972fa2eb6766827..744707e792d9f748e9904458bdf05270c688f041 100644 (file)
@@ -1144,6 +1144,17 @@ cp_fold_function (tree fndecl)
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &pset, NULL);
 }
 
+/* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
+
+static tree genericize_spaceship (tree expr)
+{
+  iloc_sentinel s (cp_expr_location (expr));
+  tree type = TREE_TYPE (expr);
+  tree op0 = TREE_OPERAND (expr, 0);
+  tree op1 = TREE_OPERAND (expr, 1);
+  return genericize_spaceship (type, op0, op1);
+}
+
 /* Perform any pre-gimplification lowering of C++ front end trees to
    GENERIC.  */
 
@@ -1574,6 +1585,10 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
       genericize_break_stmt (stmt_p);
       break;
 
+    case SPACESHIP_EXPR:
+      *stmt_p = genericize_spaceship (*stmt_p);
+      break;
+
     case OMP_FOR:
     case OMP_SIMD:
     case OMP_DISTRIBUTE:
index 60dcbe441056e5fb3860a1018d30b74a5abe8951..b9bc2c6592cb2a2a58a3c9938a399ee813494ecf 100644 (file)
@@ -518,6 +518,7 @@ cp_common_init_ts (void)
   MARK_TS_EXP (VEC_DELETE_EXPR);
   MARK_TS_EXP (VEC_INIT_EXPR);
   MARK_TS_EXP (VEC_NEW_EXPR);
+  MARK_TS_EXP (SPACESHIP_EXPR);
 
   /* Fold expressions.  */
   MARK_TS_EXP (BINARY_LEFT_FOLD_EXPR);
index 845a7e251f376c53860931bb073534ca84019ce8..4e798e3b614abdbf237ca5b47e1f9f8fc782261e 100644 (file)
@@ -255,6 +255,7 @@ DEFTREECODE (IMPLICIT_CONV_EXPR, "implicit_conv_expr", tcc_unary, 1)
 DEFTREECODE (DOTSTAR_EXPR, "dotstar_expr", tcc_expression, 2)
 DEFTREECODE (TYPEID_EXPR, "typeid_expr", tcc_expression, 1)
 DEFTREECODE (NOEXCEPT_EXPR, "noexcept_expr", tcc_unary, 1)
+DEFTREECODE (SPACESHIP_EXPR, "spaceship_expr", tcc_expression, 2)
 
 /* A placeholder for an expression that is not type-dependent, but
    does occur in a template.  When an expression that is not
index 58d7d016197d44dab79b34e33aec60fd29ea415c..2b45d62ce21bf64d41f0c8104bad371097c7aeba 100644 (file)
@@ -2695,7 +2695,8 @@ struct GTY(()) lang_decl_fn {
   unsigned omp_declare_reduction_p : 1;
   unsigned has_dependent_explicit_spec_p : 1;
   unsigned immediate_fn_p : 1;
-  unsigned spare : 11;
+  unsigned maybe_deleted : 1;
+  unsigned spare : 10;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3137,6 +3138,11 @@ struct GTY(()) lang_decl {
 #define DECL_HAS_DEPENDENT_EXPLICIT_SPEC_P(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->has_dependent_explicit_spec_p)
 
+/* Nonzero for a defaulted FUNCTION_DECL for which we haven't decided yet if
+   it's deleted.  */
+#define DECL_MAYBE_DELETED(NODE) \
+  (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5203,6 +5209,7 @@ enum special_function_kind {
                              destroyed.  */
   sfk_conversion,         /* A conversion operator.  */
   sfk_deduction_guide,    /* A class template deduction guide.  */
+  sfk_comparison,         /* A comparison operator (e.g. ==, <, <=>).  */
   sfk_virtual_destructor   /* Used by member synthesis fns.  */
 };
 
@@ -5565,6 +5572,17 @@ enum overload_flags { NO_SPECIAL = 0, DTOR_FLAG, TYPENAME_FLAG };
 #define LOOKUP_ALLOW_FLEXARRAY_INIT (LOOKUP_DELEGATING_CONS << 1)
 /* Require constant initialization of a non-constant variable.  */
 #define LOOKUP_CONSTINIT (LOOKUP_ALLOW_FLEXARRAY_INIT << 1)
+/* We're looking for either a rewritten comparison operator candidate or the
+   operator to use on the former's result.  We distinguish between the two by
+   knowing that comparisons other than == and <=> must be the latter, as must
+   a <=> expression trying to rewrite to <=> without reversing.  */
+#define LOOKUP_REWRITTEN (LOOKUP_CONSTINIT << 1)
+/* Reverse the order of the two arguments for comparison rewriting.  First we
+   swap the arguments in add_operator_candidates, then we swap the conversions
+   in add_candidate (so that they correspond to the original order of the
+   args), then we swap the conversions back in build_new_op_1 (so they
+   correspond to the order of the args in the candidate).  */
+#define LOOKUP_REVERSED (LOOKUP_REWRITTEN << 1)
 
 #define LOOKUP_NAMESPACES_ONLY(F)  \
   (((F) & LOOKUP_PREFER_NAMESPACES) && !((F) & LOOKUP_PREFER_TYPES))
@@ -6371,6 +6389,8 @@ extern bool type_has_virtual_destructor           (tree);
 extern bool classtype_has_move_assign_or_move_ctor_p (tree, bool user_declared);
 extern bool classtype_has_non_deleted_move_ctor (tree);
 extern tree classtype_has_depr_implicit_copy   (tree);
+extern bool classtype_has_op (tree, tree_code);
+extern tree classtype_has_defaulted_op (tree, tree_code);
 extern bool type_build_ctor_call               (tree);
 extern bool type_build_dtor_call               (tree);
 extern void explain_non_literal_class          (tree);
@@ -7544,6 +7564,8 @@ extern tree composite_pointer_type                (const op_location_t &,
 extern tree merge_types                                (tree, tree);
 extern tree strip_array_domain                 (tree);
 extern tree check_return_expr                  (tree, bool *);
+extern tree spaceship_type                     (tree, tsubst_flags_t = tf_warning_or_error);
+extern tree genericize_spaceship               (tree, tree, tree);
 extern tree cp_build_binary_op                  (const op_location_t &,
                                                 enum tree_code, tree, tree,
                                                 tsubst_flags_t);
index 07112aad092561f2d1c334bffefad9bf34e7203d..3bfcfb2c6b73fec3dc38588175507fd46b784679 100644 (file)
@@ -16793,6 +16793,13 @@ finish_function (bool inline_p)
        }
     }
 
+  if (DECL_DELETED_FN (fndecl))
+    {
+      DECL_INITIAL (fndecl) = error_mark_node;
+      DECL_SAVED_TREE (fndecl) = NULL_TREE;
+      goto cleanup;
+    }
+
   // If this is a concept, check that the definition is reasonable.
   if (DECL_DECLARED_CONCEPT_P (fndecl))
     check_function_concept (fndecl);
@@ -16939,6 +16946,7 @@ finish_function (bool inline_p)
   if (!processing_template_decl && !DECL_IMMEDIATE_FUNCTION_P (fndecl))
     cp_genericize (fndecl);
 
+ cleanup:
   /* We're leaving the context of this function, so zap cfun.  It's still in
      DECL_STRUCT_FUNCTION, and we'll restore it in tree_rest_of_compilation.  */
   set_cfun (NULL);
index 4dc548111229f129ed2d28fbee36f4cc610993b6..4f0b2161db1fdca8bed6571db793e03ecc0c0d98 100644 (file)
@@ -927,6 +927,10 @@ grokfield (const cp_declarator *declarator,
            }
          else if (init == ridpointers[(int)RID_DEFAULT])
            {
+             if (friendp)
+               /* ??? do_friend doesn't set this because funcdef_flag is false
+                  for in-class defaulted functions.  So set it here.  */
+               SET_DECL_FRIEND_CONTEXT (value, current_class_type);
              if (defaultable_fn_check (value))
                {
                  DECL_DEFAULTED_FN (value) = 1;
@@ -5471,6 +5475,17 @@ mark_used (tree decl, tsubst_flags_t complain)
   if (TREE_CODE (decl) == CONST_DECL)
     used_types_insert (DECL_CONTEXT (decl));
 
+  if (TREE_CODE (decl) == FUNCTION_DECL
+      && DECL_MAYBE_DELETED (decl))
+    {
+      /* ??? Switch other defaulted functions to use DECL_MAYBE_DELETED?  */
+      gcc_assert (special_function_p (decl) == sfk_comparison);
+
+      ++function_depth;
+      synthesize_method (decl);
+      --function_depth;
+    }
+
   if (TREE_CODE (decl) == FUNCTION_DECL
       && !maybe_instantiate_noexcept (decl, complain))
     return false;
@@ -5592,7 +5607,6 @@ mark_used (tree decl, tsubst_flags_t complain)
 
   /* Is it a synthesized method that needs to be synthesized?  */
   if (TREE_CODE (decl) == FUNCTION_DECL
-      && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
       && DECL_DEFAULTED_FN (decl)
       /* A function defaulted outside the class is synthesized either by
         cp_finish_decl or instantiate_decl.  */
index d104a4d574c40e5ff04b0be3e898970900536b4d..c06776f565a0c4830eaafaaccc347fe273995762 100644 (file)
@@ -2286,6 +2286,7 @@ dump_expr (cxx_pretty_printer *pp, tree t, int flags)
     case GE_EXPR:
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
     case EXACT_DIV_EXPR:
       dump_binary_op (pp, OVL_OP_INFO (false, TREE_CODE (t))->name, t, flags);
       break;
index 09e9c73cda59c7cb03cc6c6b1640a6168473dd9a..c9dd90fcba756446a1e61058f209a4c0a544cbe6 100644 (file)
@@ -406,6 +406,7 @@ type_has_trivial_fn (tree ctype, special_function_kind sfk)
     case sfk_virtual_destructor:
       return !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (ctype);
     case sfk_inheriting_constructor:
+    case sfk_comparison:
       return false;
     default:
       gcc_unreachable ();
@@ -877,6 +878,588 @@ do_build_copy_assign (tree fndecl)
   finish_compound_stmt (compound_stmt);
 }
 
+/* C++20 <compare> comparison category types.  */
+
+enum comp_cat_tag
+{
+  cc_weak_equality,
+  cc_strong_equality,
+  cc_partial_ordering,
+  cc_weak_ordering,
+  cc_strong_ordering,
+  cc_last
+};
+
+/* Names of the comparison categories and their value members, to be indexed by
+   comp_cat_tag enumerators.  genericize_spaceship below relies on the ordering
+   of the members.  */
+
+struct comp_cat_info_t
+{
+  const char *name;
+  const char *members[4];
+};
+static const comp_cat_info_t comp_cat_info[cc_last]
+= {
+   { "weak_equality", "equivalent", "nonequivalent" },
+   { "strong_equality", "equal", "nonequal" },
+   { "partial_ordering", "equivalent", "greater", "less", "unordered" },
+   { "weak_ordering", "equivalent", "greater", "less" },
+   { "strong_ordering", "equal", "greater", "less" }
+};
+
+/* A cache of the category types to speed repeated lookups.  */
+
+static GTY((deletable)) tree comp_cat_cache[cc_last];
+
+/* Look up one of the result variables in the comparison category type.  */
+
+static tree
+lookup_comparison_result (tree type, const char *name_str,
+                         tsubst_flags_t complain = tf_warning_or_error)
+{
+  tree name = get_identifier (name_str);
+  tree decl = lookup_qualified_name (type, name);
+  if (TREE_CODE (decl) != VAR_DECL)
+    {
+      if (complain & tf_error)
+       {
+         auto_diagnostic_group d;
+         if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST)
+           qualified_name_lookup_error (type, name, decl, input_location);
+         else
+           error ("%<%T::%D%> is not a static data member", type, decl);
+         inform (input_location, "determining value of %qs", "operator<=>");
+       }
+      return error_mark_node;
+    }
+  return decl;
+}
+
+/* Look up a <compare> comparison category type in std.  */
+
+static tree
+lookup_comparison_category (comp_cat_tag tag,
+                           tsubst_flags_t complain = tf_warning_or_error)
+{
+  if (tree cached = comp_cat_cache[tag])
+    return cached;
+
+  tree name = get_identifier (comp_cat_info[tag].name);
+  tree decl = lookup_qualified_name (std_node, name);
+  if (TREE_CODE (decl) != TYPE_DECL)
+    {
+      if (complain & tf_error)
+       {
+         auto_diagnostic_group d;
+         if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST)
+           qualified_name_lookup_error (std_node, name, decl, input_location);
+         else
+           error ("%<std::%D%> is not a type", decl);
+         inform (input_location, "forming type of %qs", "operator<=>");
+       }
+      return error_mark_node;
+    }
+  /* Also make sure we can look up the value members now, since we won't
+     really use them until genericize time.  */
+  tree type = TREE_TYPE (decl);
+  for (int i = 0; i < 4; ++i)
+    {
+      const char *p = comp_cat_info[tag].members[i];
+      if (!p) break;
+      if (lookup_comparison_result (type, p, complain)
+         == error_mark_node)
+       return error_mark_node;
+    }
+  return comp_cat_cache[tag] = type;
+}
+
+/* Wrapper that takes the tag rather than the type.  */
+
+static tree
+lookup_comparison_result (comp_cat_tag tag, const char *name_str,
+                         tsubst_flags_t complain = tf_warning_or_error)
+{
+  tree type = lookup_comparison_category (tag, complain);
+  return lookup_comparison_result (type, name_str, complain);
+}
+
+/* Wrapper that takes the index into the members array instead of the name.  */
+
+static tree
+lookup_comparison_result (comp_cat_tag tag, tree type, int idx)
+{
+  const char *name_str = comp_cat_info[tag].members[idx];
+  if (!name_str)
+    return NULL_TREE;
+  return lookup_comparison_result (type, name_str);
+}
+
+/* Does TYPE correspond to TAG?  */
+
+static bool
+is_cat (tree type, comp_cat_tag tag)
+{
+  tree name = TYPE_LINKAGE_IDENTIFIER (type);
+  return id_equal (name, comp_cat_info[tag].name);
+}
+
+/* Return the comp_cat_tag for TYPE.  */
+
+static comp_cat_tag
+cat_tag_for (tree type)
+{
+  for (int i = 0; i < cc_last; ++i)
+    {
+      comp_cat_tag tag = (comp_cat_tag)i;
+      if (is_cat (type, tag))
+       return tag;
+    }
+  return cc_last;
+}
+
+/* Return the comparison category tag of a <=> expression with non-class type
+   OPTYPE.  */
+
+static comp_cat_tag
+spaceship_comp_cat (tree optype)
+{
+  if (INTEGRAL_OR_ENUMERATION_TYPE_P (optype) || TYPE_PTROBV_P (optype))
+    return cc_strong_ordering;
+  else if (TREE_CODE (optype) == REAL_TYPE)
+    return cc_partial_ordering;
+  else if (TYPE_PTRFN_P (optype) || TYPE_PTRMEM_P (optype)
+          || NULLPTR_TYPE_P (optype))
+    return cc_strong_equality;
+  else if (TREE_CODE (optype) == COMPLEX_TYPE)
+    {
+      tree intype = optype;
+      while (TREE_CODE (intype) == COMPLEX_TYPE)
+       intype = TREE_TYPE (intype);
+      if (TREE_CODE (intype) == REAL_TYPE)
+       return cc_weak_equality;
+      else
+       return cc_strong_equality;
+    }
+
+  /* FIXME should vector <=> produce a vector of one of the above?  */
+  gcc_unreachable ();
+}
+
+/* Return the comparison category type of a <=> expression with non-class type
+   OPTYPE.  */
+
+tree
+spaceship_type (tree optype, tsubst_flags_t complain)
+{
+  comp_cat_tag tag = spaceship_comp_cat (optype);
+  return lookup_comparison_category (tag, complain);
+}
+
+/* Turn <=> with type TYPE and operands OP0 and OP1 into GENERIC.  */
+
+tree
+genericize_spaceship (tree type, tree op0, tree op1)
+{
+  /* ??? maybe optimize based on knowledge of representation? */
+  comp_cat_tag tag = cat_tag_for (type);
+  gcc_checking_assert (tag < cc_last);
+
+  tree eq = lookup_comparison_result (tag, type, 0);
+  tree negt = lookup_comparison_result (tag, type, 1);
+
+  if (tag == cc_strong_equality || tag == cc_weak_equality)
+    {
+      tree comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1);
+      return fold_build3 (COND_EXPR, type, comp, eq, negt);
+    }
+
+  tree r;
+  op0 = save_expr (op0);
+  op1 = save_expr (op1);
+
+  if (tag == cc_partial_ordering)
+    {
+      /* op0 == op1 ? equivalent : op0 < op1 ? less :
+        op0 > op1 ? greater : unordered */
+      tree uo = lookup_comparison_result (tag, type, 3);
+      tree comp = fold_build2 (GT_EXPR, boolean_type_node, op0, op1);
+      r = fold_build3 (COND_EXPR, type, comp, negt, uo);
+    }
+  else
+    /* op0 == op1 ? equal : op0 < op1 ? less : greater */
+    r = negt;
+
+  tree lt = lookup_comparison_result (tag, type, 2);
+  tree comp = fold_build2 (LT_EXPR, boolean_type_node, op0, op1);
+  r = fold_build3 (COND_EXPR, type, comp, lt, r);
+
+  comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1);
+  r = fold_build3 (COND_EXPR, type, comp, eq, r);
+
+  return r;
+}
+
+/* Check that the signature of a defaulted comparison operator is
+   well-formed.  */
+
+static bool
+early_check_defaulted_comparison (tree fn)
+{
+  location_t loc = DECL_SOURCE_LOCATION (fn);
+  tree ctx;
+  if (DECL_CLASS_SCOPE_P (fn))
+    ctx = DECL_CONTEXT (fn);
+  else
+    ctx = DECL_FRIEND_CONTEXT (fn);
+  bool ok = true;
+
+  if (!DECL_OVERLOADED_OPERATOR_IS (fn, SPACESHIP_EXPR)
+      && !same_type_p (TREE_TYPE (TREE_TYPE (fn)), boolean_type_node))
+    {
+      error_at (loc, "defaulted %qD must return %<bool%>", fn);
+      ok = false;
+    }
+
+  int i = DECL_NONSTATIC_MEMBER_FUNCTION_P (fn);
+  if (i && type_memfn_quals (TREE_TYPE (fn)) != TYPE_QUAL_CONST)
+    {
+      error_at (loc, "defaulted %qD must be %<const%>", fn);
+      ok = false;
+    }
+  tree parmnode = FUNCTION_FIRST_USER_PARMTYPE (fn);
+  for (; parmnode != void_list_node; parmnode = TREE_CHAIN (parmnode))
+    {
+      ++i;
+      tree parmtype = TREE_VALUE (parmnode);
+      diagnostic_t kind = DK_UNSPECIFIED;
+      int opt = 0;
+      if (same_type_p (parmtype, ctx))
+       /* The draft specifies const reference, but let's also allow by-value
+          unless -Wpedantic, hopefully it will be added soon. */
+       kind = DK_PEDWARN,
+         opt = OPT_Wpedantic;
+      else if (TREE_CODE (parmtype) != REFERENCE_TYPE
+              || TYPE_QUALS (TREE_TYPE (parmtype)) != TYPE_QUAL_CONST
+              || !(same_type_ignoring_top_level_qualifiers_p
+                   (TREE_TYPE (parmtype), ctx)))
+       kind = DK_ERROR;
+      if (kind)
+       emit_diagnostic (kind, loc, opt, "defaulted %qD must have "
+                        "parameter type %<const %T&%>", fn, ctx);
+      if (kind == DK_ERROR)
+       ok = false;
+    }
+
+  /* We still need to deduce deleted/constexpr/noexcept and maybe return. */
+  DECL_MAYBE_DELETED (fn) = true;
+
+  return ok;
+}
+
+/* Subroutine of build_comparison_op.  Given the vec of memberwise
+   comparisons COMPS, calculate the overall comparison category for
+   operator<=>.  */
+
+static tree
+common_comparison_type (vec<tree> &comps)
+{
+  tree seen[cc_last] = {};
+
+  for (unsigned i = 0; i < comps.length(); ++i)
+    {
+      tree comp = comps[i];
+      tree ctype = TREE_TYPE (comp);
+      comp_cat_tag tag = cat_tag_for (ctype);
+      if (tag < cc_last)
+       seen[tag] = ctype;
+      else
+       /* If any Ti is not a comparison category type, U is void.  */
+       return void_type_node;
+    }
+
+  /* Otherwise, if at least one T i is std::weak_equality, or at least one T i
+     is std::strong_equality and at least one T j is std::partial_ordering or
+     std::weak_ordering, U is std::weak_equality.  */
+  if (tree t = seen[cc_weak_equality]) return t;
+  if (seen[cc_strong_equality]
+      && (seen[cc_partial_ordering] || seen[cc_weak_ordering]))
+    return lookup_comparison_category (cc_weak_equality);
+
+  /* Otherwise, if at least one T i is std::strong_equality, U is
+     std::strong_equality.  */
+  if (tree t = seen[cc_strong_equality]) return t;
+
+  /* Otherwise, if at least one T i is std::partial_ordering, U is
+     std::partial_ordering.  */
+  if (tree t = seen[cc_partial_ordering]) return t;
+
+  /* Otherwise, if at least one T i is std::weak_ordering, U is
+     std::weak_ordering.  */
+  if (tree t = seen[cc_weak_ordering]) return t;
+
+  /* Otherwise, U is std::strong_ordering.  */
+  if (tree t = seen[cc_strong_ordering]) return t;
+  return lookup_comparison_category (cc_strong_ordering);
+}
+
+/* Data structure for build_comparison_op.  */
+
+struct comp_info
+{
+  tree fndecl;
+  location_t loc;
+  bool defining;
+  bool first_time;
+  bool constexp;
+  bool was_constexp;
+  bool noex;
+
+  comp_info (tree fndecl, tsubst_flags_t &complain)
+    : fndecl (fndecl)
+  {
+    loc = DECL_SOURCE_LOCATION (fndecl);
+
+    /* We only have tf_error set when we're called from
+       explain_invalid_constexpr_fn or maybe_explain_implicit_delete.  */
+    defining = !(complain & tf_error);
+
+    first_time = DECL_MAYBE_DELETED (fndecl);
+    DECL_MAYBE_DELETED (fndecl) = false;
+
+    /* Do we want to try to set constexpr?  */
+    was_constexp = DECL_DECLARED_CONSTEXPR_P (fndecl);
+    constexp = first_time;
+    if (constexp)
+      /* Set this for var_in_constexpr_fn.  */
+      DECL_DECLARED_CONSTEXPR_P (fndecl) = true;
+
+    /* Do we want to try to set noexcept?  */
+    noex = first_time;
+    if (noex)
+      {
+       tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl));
+       if (raises && !UNEVALUATED_NOEXCEPT_SPEC_P (raises))
+         /* There was an explicit exception-specification.  */
+         noex = false;
+      }
+  }
+
+  /* EXPR is an expression built as part of the function body.
+     Adjust the properties appropriately.  */
+  void check (tree expr)
+  {
+    if (expr == error_mark_node)
+      DECL_DELETED_FN (fndecl) = true;
+    if ((constexp || was_constexp)
+       && !potential_rvalue_constant_expression (expr))
+      {
+       if (was_constexp)
+         require_potential_rvalue_constant_expression (expr);
+       else
+         constexp = false;
+      }
+    if (noex && !expr_noexcept_p (expr, tf_none))
+      noex = false;
+  }
+};
+
+/* Build up the definition of a defaulted comparison operator.  Unlike other
+   defaulted functions that use synthesized_method_walk to determine whether
+   the function is e.g. deleted, for comparisons we use the same code.  We try
+   to use synthesize_method at the earliest opportunity and bail out if the
+   function ends up being deleted.  */
+
+static void
+build_comparison_op (tree fndecl, tsubst_flags_t complain)
+{
+  comp_info info (fndecl, complain);
+
+  if (!info.defining && !(complain & tf_error) && !DECL_MAYBE_DELETED (fndecl))
+    return;
+
+  int flags = LOOKUP_NORMAL | LOOKUP_NONVIRTUAL | LOOKUP_DEFAULTED;
+  const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (DECL_NAME (fndecl));
+  tree_code code = op->tree_code;
+
+  tree lhs = DECL_ARGUMENTS (fndecl);
+  tree rhs = DECL_CHAIN (lhs);
+  if (is_this_parameter (lhs))
+    lhs = cp_build_fold_indirect_ref (lhs);
+  else
+    lhs = convert_from_reference (lhs);
+  rhs = convert_from_reference (rhs);
+  tree ctype = TYPE_MAIN_VARIANT (TREE_TYPE (lhs));
+
+  iloc_sentinel ils (info.loc);
+
+  /* A defaulted comparison operator function for class C is defined as
+     deleted if ... C is a union-like class.  */
+  if (TREE_CODE (ctype) == UNION_TYPE)
+    {
+      if (complain & tf_error)
+       inform (info.loc, "cannot default compare union %qT", ctype);
+      DECL_DELETED_FN (fndecl) = true;
+    }
+
+  tree compound_stmt = NULL_TREE;
+  if (info.defining)
+    compound_stmt = begin_compound_stmt (0);
+  else
+    ++cp_unevaluated_operand;
+
+  tree rettype = TREE_TYPE (TREE_TYPE (fndecl));
+  if (code != SPACESHIP_EXPR && is_auto (rettype))
+    {
+      rettype = boolean_type_node;
+      apply_deduced_return_type (fndecl, rettype);
+    }
+
+  if (code == EQ_EXPR || code == SPACESHIP_EXPR)
+    {
+      auto_vec<tree> comps;
+
+      /* Compare each of the subobjects.  Note that we get bases from
+        next_initializable_field because we're past C++17.  */
+      for (tree field = next_initializable_field (TYPE_FIELDS (ctype));
+          field;
+          field = next_initializable_field (DECL_CHAIN (field)))
+       {
+         tree expr_type = TREE_TYPE (field);
+
+         /* A defaulted comparison operator function for class C is defined as
+            deleted if any non-static data member of C is of reference type or
+            C is a union-like class.  */
+         if (TREE_CODE (expr_type) == REFERENCE_TYPE)
+           {
+             if (complain & tf_error)
+               inform (DECL_SOURCE_LOCATION (field), "cannot default compare "
+                       "reference member %qD", field);
+             DECL_DELETED_FN (fndecl) = true;
+             continue;
+           }
+         else if (ANON_UNION_TYPE_P (expr_type))
+           {
+             if (complain & tf_error)
+               inform (DECL_SOURCE_LOCATION (field), "cannot default compare "
+                       "anonymous union member");
+             DECL_DELETED_FN (fndecl) = true;
+             continue;
+           }
+
+         tree lhs_mem = build3 (COMPONENT_REF, expr_type, lhs, field,
+                                NULL_TREE);
+         tree rhs_mem = build3 (COMPONENT_REF, expr_type, rhs, field,
+                                NULL_TREE);
+         tree comp = build_new_op (info.loc, code, flags, lhs_mem, rhs_mem,
+                                   NULL_TREE, NULL, complain);
+         comps.safe_push (comp);
+       }
+      if (code == SPACESHIP_EXPR && is_auto (rettype))
+       {
+         rettype = common_comparison_type (comps);
+         apply_deduced_return_type (fndecl, rettype);
+       }
+      for (unsigned i = 0; i < comps.length(); ++i)
+       {
+         tree comp = comps[i];
+         tree eq, retval = NULL_TREE, if_ = NULL_TREE;
+         if (info.defining)
+           if_ = begin_if_stmt ();
+         /* Spaceship is specified to use !=, but for the comparison category
+            types, != is equivalent to !(==), so let's use == directly.  */
+         if (code == EQ_EXPR)
+           {
+             /* if (x==y); else return false; */
+             eq = comp;
+             retval = boolean_false_node;
+           }
+         else
+           {
+             /* if (auto v = x<=>y, v == 0); else return v; */
+             if (TREE_CODE (comp) == SPACESHIP_EXPR)
+               TREE_TYPE (comp) = rettype;
+             else
+               comp = build_static_cast (rettype, comp, complain);
+             info.check (comp);
+             if (info.defining)
+               {
+                 tree var = create_temporary_var (rettype);
+                 pushdecl (var);
+                 cp_finish_decl (var, comp, false, NULL_TREE, flags);
+                 comp = retval = var;
+               }
+             eq = build_new_op (info.loc, EQ_EXPR, flags, comp,
+                                integer_zero_node, NULL_TREE, NULL,
+                                complain);
+           }
+         tree ceq = contextual_conv_bool (eq, complain);
+         info.check (ceq);
+         if (info.defining)
+           {
+             finish_if_stmt_cond (ceq, if_);
+             finish_then_clause (if_);
+             begin_else_clause (if_);
+             finish_return_stmt (retval);
+             finish_else_clause (if_);
+             finish_if_stmt (if_);
+           }
+       }
+      if (info.defining)
+       {
+         tree val;
+         if (code == EQ_EXPR)
+           val = boolean_true_node;
+         else
+           {
+             tree seql = lookup_comparison_result (cc_strong_ordering,
+                                                   "equal", complain);
+             val = build_static_cast (rettype, seql, complain);
+           }
+         finish_return_stmt (val);
+       }
+    }
+  else if (code == NE_EXPR)
+    {
+      tree comp = build_new_op (info.loc, EQ_EXPR, flags, lhs, rhs,
+                               NULL_TREE, NULL, complain);
+      comp = contextual_conv_bool (comp, complain);
+      info.check (comp);
+      if (info.defining)
+       {
+         tree neg = build1 (TRUTH_NOT_EXPR, boolean_type_node, comp);
+         finish_return_stmt (neg);
+       }
+    }
+  else
+    {
+      tree comp = build_new_op (info.loc, SPACESHIP_EXPR, flags, lhs, rhs,
+                               NULL_TREE, NULL, complain);
+      tree comp2 = build_new_op (info.loc, code, flags, comp, integer_zero_node,
+                                NULL_TREE, NULL, complain);
+      info.check (comp2);
+      if (info.defining)
+       finish_return_stmt (comp2);
+    }
+
+  if (info.defining)
+    finish_compound_stmt (compound_stmt);
+  else
+    --cp_unevaluated_operand;
+
+  if (info.first_time)
+    {
+      DECL_DECLARED_CONSTEXPR_P (fndecl) = info.constexp || info.was_constexp;
+      tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl));
+      if (!raises || UNEVALUATED_NOEXCEPT_SPEC_P (raises))
+       {
+         raises = info.noex ? noexcept_true_spec : noexcept_false_spec;
+         TREE_TYPE (fndecl) = build_exception_variant (TREE_TYPE (fndecl),
+                                                       raises);
+       }
+    }
+}
+
 /* Synthesize FNDECL, a non-static member function.   */
 
 void
@@ -889,6 +1472,7 @@ synthesize_method (tree fndecl)
   location_t save_input_location = input_location;
   int error_count = errorcount;
   int warning_count = warningcount + werrorcount;
+  special_function_kind sfk = special_function_p (fndecl);
 
   /* Reset the source location, we might have been previously
      deferred, and thus have saved where we were first needed.  */
@@ -930,6 +1514,12 @@ synthesize_method (tree fndecl)
       else
        finish_mem_initializers (NULL_TREE);
     }
+  else if (sfk == sfk_comparison)
+    {
+      /* Pass tf_none so the function is just deleted if there's a problem.  */
+      build_comparison_op (fndecl, tf_none);
+      need_body = false;
+    }
 
   /* If we haven't yet generated the body of the function, just
      generate an empty compound statement.  */
@@ -941,7 +1531,10 @@ synthesize_method (tree fndecl)
     }
 
   finish_function_body (stmt);
-  expand_or_defer_fn (finish_function (/*inline_p=*/false));
+  finish_function (/*inline_p=*/false);
+
+  if (!DECL_DELETED_FN (fndecl))
+    expand_or_defer_fn (fndecl);
 
   input_location = save_input_location;
 
@@ -1753,6 +2346,13 @@ get_defaulted_eh_spec (tree decl, tsubst_flags_t complain)
   if (DECL_CLONED_FUNCTION_P (decl))
     decl = DECL_CLONED_FUNCTION (decl);
   special_function_kind sfk = special_function_p (decl);
+  if (sfk == sfk_comparison)
+    {
+      /* We're in synthesize_method. Start with NULL_TREE, build_comparison_op
+        will adjust as needed.  */
+      gcc_assert (decl == current_function_decl);
+      return NULL_TREE;
+    }
   tree ctype = DECL_CONTEXT (decl);
   tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl);
   tree parm_type = TREE_VALUE (parms);
@@ -1836,7 +2436,14 @@ maybe_explain_implicit_delete (tree decl)
              informed = true;
            }
        }
-      if (!informed)
+      if (!informed && sfk == sfk_comparison)
+       {
+         inform (DECL_SOURCE_LOCATION (decl),
+                 "%q#D is implicitly deleted because the default "
+                 "definition would be ill-formed:", decl);
+         build_comparison_op (decl, tf_warning_or_error);
+       }
+      else if (!informed)
        {
          tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl);
          bool const_p = false;
@@ -1891,10 +2498,18 @@ explain_implicit_non_constexpr (tree decl)
   bool const_p = CP_TYPE_CONST_P (non_reference (TREE_VALUE (parms)));
   tree inh = DECL_INHERITED_CTOR (decl);
   bool dummy;
-  synthesized_method_walk (DECL_CLASS_CONTEXT (decl),
-                          special_function_p (decl), const_p,
-                          NULL, NULL, NULL, &dummy, true,
-                          &inh, parms);
+  special_function_kind sfk = special_function_p (decl);
+  if (sfk == sfk_comparison)
+    {
+      DECL_DECLARED_CONSTEXPR_P (decl) = true;
+      build_comparison_op (decl, tf_warning_or_error);
+      DECL_DECLARED_CONSTEXPR_P (decl) = false;
+    }
+  else
+    synthesized_method_walk (DECL_CLASS_CONTEXT (decl),
+                            sfk, const_p,
+                            NULL, NULL, NULL, &dummy, true,
+                            &inh, parms);
 }
 
 /* DECL is an instantiation of an inheriting constructor template.  Deduce
@@ -1933,12 +2548,12 @@ deduce_inheriting_ctor (tree decl)
 /* Implicitly declare the special function indicated by KIND, as a
    member of TYPE.  For copy constructors and assignment operators,
    CONST_P indicates whether these functions should take a const
-   reference argument or a non-const reference.  Returns the
-   FUNCTION_DECL for the implicitly declared function.  */
+   reference argument or a non-const reference.
+   Returns the FUNCTION_DECL for the implicitly declared function.  */
 
 tree
 implicitly_declare_fn (special_function_kind kind, tree type,
-                      bool const_p, tree inherited_ctor,
+                      bool const_p, tree pattern_fn,
                       tree inherited_parms)
 {
   tree fn;
@@ -1950,8 +2565,11 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   tree this_parm;
   tree name;
   HOST_WIDE_INT saved_processing_template_decl;
-  bool deleted_p;
-  bool constexpr_p;
+  bool deleted_p = false;
+  bool constexpr_p = false;
+  bool friend_p = (kind == sfk_comparison && DECL_FRIEND_P (pattern_fn));
+  tree inherited_ctor = (kind == sfk_inheriting_constructor
+                        ? pattern_fn : NULL_TREE);
 
   /* Because we create declarations for implicitly declared functions
      lazily, we may be creating the declaration for a member of TYPE
@@ -1978,6 +2596,7 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   else
     return_type = void_type_node;
 
+  int this_quals = TYPE_UNQUALIFIED;
   switch (kind)
     {
     case sfk_destructor:
@@ -2021,6 +2640,36 @@ implicitly_declare_fn (special_function_kind kind, tree type,
        }
       break;
     }
+
+    case sfk_comparison:
+      /* If the class definition does not explicitly declare an == operator
+        function, but declares a defaulted three-way comparison operator
+        function, an == operator function is declared implicitly with the same
+        access as the three-way comparison operator function.
+
+        The implicitly-declared == operator for a class X is an inline member
+        and is defined as defaulted in the definition of X.
+
+        If the three-way comparison operator function is declared as a
+        non-static const member, the implicitly-declared == operator function
+        is a member of the form
+
+          bool X::operator==(const X&) const;
+
+        Otherwise, the implicitly-declared == operator function is of the form
+
+          friend bool operator==(const X&, const X&); */
+      /* No other comparison operator is implicitly declared.  */
+      name = ovl_op_identifier (false, EQ_EXPR);
+      return_type = boolean_type_node;
+      rhs_parm_type = cp_build_qualified_type (type, TYPE_QUAL_CONST);
+      rhs_parm_type = cp_build_reference_type (rhs_parm_type, false);
+      parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types);
+      if (friend_p)
+       parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types);
+      this_quals = TYPE_QUAL_CONST;
+      break;
+
     default:
       gcc_unreachable ();
     }
@@ -2038,9 +2687,10 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   else if (cxx_dialect >= cxx11)
     {
       raises = noexcept_deferred_spec;
-      synthesized_method_walk (type, kind, const_p, NULL, &trivial_p,
-                              &deleted_p, &constexpr_p, false,
-                              &inherited_ctor, inherited_parms);
+      if (kind != sfk_comparison)
+       synthesized_method_walk (type, kind, const_p, NULL, &trivial_p,
+                                &deleted_p, &constexpr_p, false,
+                                &inherited_ctor, inherited_parms);
     }
   else
     synthesized_method_walk (type, kind, const_p, &raises, &trivial_p,
@@ -2062,7 +2712,9 @@ implicitly_declare_fn (special_function_kind kind, tree type,
     type_set_nontrivial_flag (type, kind);
 
   /* Create the function.  */
-  fn_type = build_method_type_directly (type, return_type, parameter_types);
+  tree this_type = cp_build_qualified_type (type, this_quals);
+  fn_type = build_method_type_directly (this_type, return_type,
+                                       parameter_types);
   if (raises)
     {
       if (raises != error_mark_node)
@@ -2073,16 +2725,25 @@ implicitly_declare_fn (special_function_kind kind, tree type,
        gcc_assert (seen_error ());
     }
   fn = build_lang_decl (FUNCTION_DECL, name, fn_type);
-  if (kind != sfk_inheriting_constructor)
+  if (kind == sfk_comparison)
+    {
+      DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (pattern_fn);
+      DECL_MAYBE_DELETED (fn) = true;
+    }
+  else if (kind != sfk_inheriting_constructor)
     DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (TYPE_NAME (type));
 
-  if (!IDENTIFIER_CDTOR_P (name))
-    /* Assignment operator.  */
-    DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = OVL_OP_NOP_EXPR;
+  if (IDENTIFIER_OVL_OP_P (name))
+    {
+      const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (name);
+      DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = op->ovl_op_code;
+    }
   else if (IDENTIFIER_CTOR_P (name))
     DECL_CXX_CONSTRUCTOR_P (fn) = true;
-  else
+  else if (IDENTIFIER_DTOR_P (name))
     DECL_CXX_DESTRUCTOR_P (fn) = true;
+  else
+    gcc_unreachable ();
 
   SET_DECL_ALIGN (fn, MINIMUM_METHOD_BOUNDARY);
 
@@ -2097,6 +2758,13 @@ implicitly_declare_fn (special_function_kind kind, tree type,
       retrofit_lang_decl (decl);
       DECL_PARM_INDEX (decl) = DECL_PARM_LEVEL (decl) = 1;
       DECL_ARGUMENTS (fn) = decl;
+      if (friend_p)
+       {
+         /* The second parm of friend op==.  */
+         tree decl2 = copy_decl (decl);
+         DECL_CHAIN (decl) = decl2;
+         DECL_PARM_INDEX (decl2) = 2;
+       }
     }
   else if (kind == sfk_inheriting_constructor)
     {
@@ -2122,7 +2790,7 @@ implicitly_declare_fn (special_function_kind kind, tree type,
       constexpr_p = DECL_DECLARED_CONSTEXPR_P (inherited_ctor);
     }
   /* Add the "this" parameter.  */
-  this_parm = build_this_parm (fn, fn_type, TYPE_UNQUALIFIED);
+  this_parm = build_this_parm (fn, fn_type, this_quals);
   DECL_CHAIN (this_parm) = DECL_ARGUMENTS (fn);
   DECL_ARGUMENTS (fn) = this_parm;
 
@@ -2141,6 +2809,12 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   set_linkage_according_to_type (type, fn);
   if (TREE_PUBLIC (fn))
     DECL_COMDAT (fn) = 1;
+  if (kind == sfk_comparison && !friend_p)
+    {
+      /* The implicit op== has the same access as the op<=>.  */
+      TREE_PRIVATE (fn) = TREE_PRIVATE (pattern_fn);
+      TREE_PROTECTED (fn) = TREE_PROTECTED (pattern_fn);
+    }
   rest_of_decl_compilation (fn, namespace_bindings_p (), at_eof);
   gcc_assert (!TREE_USED (fn));
 
@@ -2182,6 +2856,16 @@ defaulted_late_check (tree fn)
   /* Complain about invalid signature for defaulted fn.  */
   tree ctx = DECL_CONTEXT (fn);
   special_function_kind kind = special_function_p (fn);
+
+  if (kind == sfk_comparison)
+    {
+      /* If the function was declared constexpr, check that the definition
+        qualifies.  Otherwise we can define the function lazily.  */
+      if (DECL_DECLARED_CONSTEXPR_P (fn))
+       synthesize_method (fn);
+      return;
+    }
+
   bool fn_const_p = (copy_fn_p (fn) == 2);
   tree implicit_fn = implicitly_declare_fn (kind, ctx, fn_const_p,
                                            NULL, NULL);
@@ -2272,6 +2956,13 @@ defaultable_fn_check (tree fn)
       else if (move_fn_p (fn))
        kind = sfk_move_assignment;
     }
+  else if (DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) >= OVL_OP_EQ_EXPR
+          && DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) <= OVL_OP_SPACESHIP_EXPR)
+    {
+      kind = sfk_comparison;
+      if (!early_check_defaulted_comparison (fn))
+       return false;
+    }
 
   if (kind == sfk_none)
     {
@@ -2293,7 +2984,7 @@ defaultable_fn_check (tree fn)
        if (DECL_NAME (p))
          TREE_NO_WARNING (p) = 1;
 
-      if (TYPE_BEING_DEFINED (DECL_CONTEXT (fn)))
+      if (current_class_type && TYPE_BEING_DEFINED (current_class_type))
        /* Defer checking.  */;
       else if (!processing_template_decl)
        defaulted_late_check (fn);
index cbb61697d7c0a0b5c8f82962025ebaefbbb775c4..cd0d9551aa34fcbb12415e5eb374f9f5eb5a9459 100644 (file)
@@ -5592,6 +5592,12 @@ get_std_name_hint (const char *name)
     {"atomic_ref", "<atomic>", cxx2a},
     /* <bitset>.  */
     {"bitset", "<bitset>", cxx11},
+    /* <compare> */
+    {"weak_equality", "<compare>", cxx2a},
+    {"strong_equality", "<compare>", cxx2a},
+    {"partial_ordering", "<compare>", cxx2a},
+    {"weak_ordering", "<compare>", cxx2a},
+    {"strong_ordering", "<compare>", cxx2a},
     /* <complex>.  */
     {"complex", "<complex>", cxx98},
     {"complex_literals", "<complex>", cxx14},
index cc52e9bd96723f5352949e46d06e8c8ef8c80de7..ee0a4c17592baff0f2e9585babdfa99b98ff229f 100644 (file)
@@ -104,12 +104,16 @@ DEF_OPERATOR ("|", BIT_IOR_EXPR, "or", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("^", BIT_XOR_EXPR, "eo", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("<<", LSHIFT_EXPR, "ls", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (">>", RSHIFT_EXPR, "rs", OVL_OP_FLAG_BINARY)
+
+/* defaultable_fn_check relies on the ordering of the comparison operators.  */
 DEF_OPERATOR ("==", EQ_EXPR, "eq", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("!=", NE_EXPR, "ne", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("<", LT_EXPR, "lt", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (">", GT_EXPR, "gt", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("<=", LE_EXPR, "le", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (">=", GE_EXPR, "ge", OVL_OP_FLAG_BINARY)
+DEF_OPERATOR ("<=>", SPACESHIP_EXPR, "ss", OVL_OP_FLAG_BINARY)
+
 DEF_OPERATOR ("&&", TRUTH_ANDIF_EXPR, "aa", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR ("||", TRUTH_ORIF_EXPR, "oo", OVL_OP_FLAG_BINARY)
 DEF_OPERATOR (",", COMPOUND_EXPR, "cm", OVL_OP_FLAG_BINARY)
index 11468c0af428484d5f349303a910480e999a5032..cbbf946d32c12cd442c69ddd0c902a247a3ce933 100644 (file)
@@ -1850,6 +1850,7 @@ enum cp_parser_prec
   PREC_AND_EXPRESSION,
   PREC_EQUALITY_EXPRESSION,
   PREC_RELATIONAL_EXPRESSION,
+  PREC_SPACESHIP_EXPRESSION,
   PREC_SHIFT_EXPRESSION,
   PREC_ADDITIVE_EXPRESSION,
   PREC_MULTIPLICATIVE_EXPRESSION,
@@ -1921,6 +1922,8 @@ static const cp_parser_binary_operations_map_node binops[] = {
   { CPP_LSHIFT, LSHIFT_EXPR, PREC_SHIFT_EXPRESSION },
   { CPP_RSHIFT, RSHIFT_EXPR, PREC_SHIFT_EXPRESSION },
 
+  { CPP_SPACESHIP, SPACESHIP_EXPR, PREC_SPACESHIP_EXPRESSION },
+
   { CPP_LESS, LT_EXPR, PREC_RELATIONAL_EXPRESSION },
   { CPP_GREATER, GT_EXPR, PREC_RELATIONAL_EXPRESSION },
   { CPP_LESS_EQ, LE_EXPR, PREC_RELATIONAL_EXPRESSION },
@@ -15507,6 +15510,10 @@ cp_parser_operator (cp_parser* parser, location_t start_loc)
       op = GE_EXPR;
       break;
 
+    case CPP_SPACESHIP:
+      op = SPACESHIP_EXPR;
+      break;
+
     case CPP_AND_AND:
       op = TRUTH_ANDIF_EXPR;
       break;
index b8f8f6dbb5980e156b6be5bae30f8ff884016d9d..313b8073a3c4aff8b4f740eecd7c9e0499b157e7 100644 (file)
@@ -19003,6 +19003,7 @@ tsubst_copy_and_build (tree t,
     case GE_EXPR:
     case LT_EXPR:
     case GT_EXPR:
+    case SPACESHIP_EXPR:
     case MEMBER_REF:
     case DOTSTAR_EXPR:
       {
index a4b8b00c92295a3f112eb64d40c34a070db6fb9b..5cdeb6a07fef5226dc3e37b639a3f9abf2b9f8af 100644 (file)
@@ -5042,6 +5042,9 @@ special_function_p (const_tree decl)
     return sfk_conversion;
   if (deduction_guide_p (decl))
     return sfk_deduction_guide;
+  if (DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) >= OVL_OP_EQ_EXPR
+      && DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) <= OVL_OP_SPACESHIP_EXPR)
+    return sfk_comparison;
 
   return sfk_none;
 }
index 50240537938aef00086bd3832727503ffdfe9a72..38a15d14620077c950d34a1e3eb8ea6050f7d643 100644 (file)
@@ -4889,6 +4889,7 @@ cp_build_binary_op (const op_location_t &location,
 
     case EQ_EXPR:
     case NE_EXPR:
+    case SPACESHIP_EXPR:
       if (code0 == VECTOR_TYPE && code1 == VECTOR_TYPE)
        goto vector_compare;
       if ((complain & tf_warning)
@@ -4965,7 +4966,9 @@ cp_build_binary_op (const op_location_t &location,
          warn_for_null_address (location, op1, complain);
        }
       else if ((code0 == POINTER_TYPE && code1 == POINTER_TYPE)
-              || (TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1)))
+              || (code == SPACESHIP_EXPR
+                  ? TYPE_PTRMEM_P (type0) && TYPE_PTRMEM_P (type1)
+                  : TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1)))
        result_type = composite_pointer_type (location,
                                              type0, type1, op0, op1,
                                              CPO_COMPARISON, complain);
@@ -5358,6 +5361,55 @@ cp_build_binary_op (const op_location_t &location,
                                  location);
     }
 
+  if (code == SPACESHIP_EXPR)
+    {
+      iloc_sentinel s (location);
+
+      tree orig_type0 = TREE_TYPE (orig_op0);
+      tree_code orig_code0 = TREE_CODE (orig_type0);
+      tree orig_type1 = TREE_TYPE (orig_op1);
+      tree_code orig_code1 = TREE_CODE (orig_type1);
+      if ((orig_code0 == BOOLEAN_TYPE) != (orig_code1 == BOOLEAN_TYPE))
+       /* "If one of the operands is of type bool and the other is not, the
+          program is ill-formed."  */
+       result_type = NULL_TREE;
+      else if (code0 == POINTER_TYPE && orig_code0 != POINTER_TYPE
+              && code1 == POINTER_TYPE && orig_code1 != POINTER_TYPE)
+       /* We only do array/function-to-pointer conversion if "at least one of
+          the operands is of pointer type".  */
+       result_type = NULL_TREE;
+      else if (orig_code0 == ENUMERAL_TYPE && orig_code1 == ENUMERAL_TYPE
+              && !(same_type_ignoring_top_level_qualifiers_p
+                   (orig_type0, orig_type1)))
+       /* "If both operands have arithmetic types, or one operand has integral
+          type and the other operand has unscoped enumeration type, the usual
+          arithmetic conversions are applied to the operands."  So we don't do
+          arithmetic conversions if the operands both have enumeral type.  */
+       result_type = NULL_TREE;
+
+      if (result_type)
+       build_type = spaceship_type (result_type, complain);
+
+      if (result_type && arithmetic_types_p)
+       {
+         /* If a narrowing conversion is required, other than from an integral
+            type to a floating point type, the program is ill-formed.  */
+         bool ok = true;
+         if (TREE_CODE (result_type) == REAL_TYPE
+             && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op0)))
+           /* OK */;
+         else if (!check_narrowing (result_type, orig_op0, complain))
+           ok = false;
+         if (TREE_CODE (result_type) == REAL_TYPE
+             && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op1)))
+           /* OK */;
+         else if (!check_narrowing (result_type, orig_op1, complain))
+           ok = false;
+         if (!ok && !(complain & tf_error))
+           return error_mark_node;
+       }
+    }
+
   if (!result_type)
     {
       if (complain & tf_error)
diff --git a/gcc/testsuite/c-c++-common/cpp/spaceship-1.c b/gcc/testsuite/c-c++-common/cpp/spaceship-1.c
new file mode 100644 (file)
index 0000000..a3dc38d
--- /dev/null
@@ -0,0 +1,6 @@
+/* { dg-do preprocess } */
+/* { dg-options "-std=c11" { target c } } */
+
+#define A(x, y) x##y
+A(<=, >)       /* { dg-error "does not give a valid preprocessing token" "" { target { ! c++2a } } } */
+A(<=>, >)      /* { dg-error "does not give a valid preprocessing token" "" { target c++2a } } */
diff --git a/gcc/testsuite/g++.dg/cpp/spaceship-1.C b/gcc/testsuite/g++.dg/cpp/spaceship-1.C
new file mode 100644 (file)
index 0000000..241b277
--- /dev/null
@@ -0,0 +1,8 @@
+// { dg-do compile { target c++17_down } }
+// { dg-options "-Wno-pointer-arith" }
+
+struct X {};
+bool operator<= (X, X);
+template<bool (X, X)> struct Y {};
+Y<&operator<=> y;
+bool foo (bool (*fn) (X, X), int n) { return n+&operator<=> fn; }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C
new file mode 100644 (file)
index 0000000..9d008f1
--- /dev/null
@@ -0,0 +1,15 @@
+// { dg-do compile { target c++2a } }
+
+struct A
+{
+  int i;
+  bool operator==(A a) const { return i == a.i; }
+};
+
+struct B
+{
+  A a;
+  bool operator==(const B&) const = default; // { dg-error "A::operator==" }
+};
+
+constexpr bool x = B() == B(); // { dg-error "non-.constexpr" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C
new file mode 100644 (file)
index 0000000..19a03fb
--- /dev/null
@@ -0,0 +1,17 @@
+// { dg-do run { target c++2a } }
+
+struct D
+{
+  int i;
+  bool operator==(const D& x) const = default; // OK, returns x.i == y.i
+  bool operator!=(const D& z) const = default;  // OK, returns !(*this == z)
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  assert (d == d);
+  assert (!(d != d));
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C
new file mode 100644 (file)
index 0000000..7e98c47
--- /dev/null
@@ -0,0 +1,24 @@
+// { dg-do run { target c++2a } }
+
+template <class T>
+struct D
+{
+  T i;
+  bool operator==(const D& x) const = default; // OK, returns x.i == y.i
+  bool operator!=(const D& z) const = default;  // OK, returns !(*this == z)
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+template <class T>
+void f()
+{
+  D<T> d{42};
+  assert (d == d);
+  assert (!(d != d));
+}
+
+int main()
+{
+  f<int>();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C
new file mode 100644 (file)
index 0000000..06b988f
--- /dev/null
@@ -0,0 +1,12 @@
+// { dg-do compile { target c++2a } }
+
+struct D
+{
+  int i;
+  bool operator==(const D& x) const = default; // OK, returns x.i == y.i
+  bool operator!=(const D& z) const = default;  // OK, returns !(*this == z)
+};
+
+constexpr D d{42};
+static_assert (d == d);
+static_assert (!(d != d));
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C
new file mode 100644 (file)
index 0000000..490726d
--- /dev/null
@@ -0,0 +1,16 @@
+// { dg-do compile { target c++2a } }
+
+struct A {
+  bool operator==(const A&) const;
+};
+
+struct D
+{
+  A i;
+  bool operator==(const D& x) const = default; // { dg-error "A::operator==" }
+  bool operator!=(const D& z) const = default; // { dg-error "D::operator==" }
+};
+
+constexpr D d{A()};
+static_assert (d == d);                // { dg-error "non-constant|constexpr" }
+static_assert (!(d != d));     // { dg-error "non-constant|constexpr" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C
new file mode 100644 (file)
index 0000000..d89fc88
--- /dev/null
@@ -0,0 +1,8 @@
+// { dg-do compile { target c++2a } }
+
+struct A {
+  int operator==(const A&) const = default; // { dg-error "return .bool" }
+  bool operator==(const A&, const A&) const = default; // { dg-error "exactly one" }
+  bool operator==(int) const = default; // { dg-error "parameter type" }
+  bool operator==(const A&) = default; // { dg-error "const" }
+};
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C
new file mode 100644 (file)
index 0000000..ac24f36
--- /dev/null
@@ -0,0 +1,10 @@
+// { dg-do compile { target c++2a } }
+
+struct A {
+  int &r;                      // { dg-message "reference" }
+  bool operator==(const A&) const = default; // { dg-message "deleted" }
+};
+
+int i;
+A a { i };
+bool b = a == a;               // { dg-error "deleted" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C
new file mode 100644 (file)
index 0000000..f804e13
--- /dev/null
@@ -0,0 +1,10 @@
+// { dg-do compile { target c++2a } }
+
+struct A
+{
+  union { int i; }                          // { dg-message "union" }
+  bool operator==(const A&) const = default; // { dg-message "deleted" }
+};
+
+A a { 42 };
+bool b = a == a;               // { dg-error "deleted" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C
new file mode 100644 (file)
index 0000000..8112eaa
--- /dev/null
@@ -0,0 +1,10 @@
+// { dg-do compile { target c++2a } }
+
+union A
+{
+  int i;
+  bool operator==(const A&) const = default; // { dg-message "union" }
+};
+
+A a { 42 };
+bool b = a == a;               // { dg-error "deleted" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C
new file mode 100644 (file)
index 0000000..ce7b56c
--- /dev/null
@@ -0,0 +1,5 @@
+// Test that we suggest adding #include <compare>.
+// { dg-do compile { target c++2a } }
+
+auto x = 1<=>2;                        // { dg-error "" }
+// { dg-message "<compare>" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C
new file mode 100644 (file)
index 0000000..6461c6a
--- /dev/null
@@ -0,0 +1,7 @@
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+template <class T, T x = (T() <=> T())> // { dg-error "31:0 <=> 0" }
+void f(T);
+//constexpr int f(...) { return 42; }
+constexpr int i = f(24);       //  { dg-error "no match" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C
new file mode 100644 (file)
index 0000000..bb60302
--- /dev/null
@@ -0,0 +1,15 @@
+// This should continue to work.
+// { dg-do compile { target c++2a } }
+
+template<class T>
+struct A {
+  template<class U>
+  bool operator==(const A<U>&);
+};
+
+int main()
+{
+  A<int> a1;
+  A<void> a2;
+  return a1 == a2;
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C
new file mode 100644 (file)
index 0000000..2ca86b7
--- /dev/null
@@ -0,0 +1,93 @@
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while(0)
+
+void f(){}
+void g(){}
+
+int main()
+{
+  {
+    constexpr auto v = 1 <=> 2;
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (!is_gteq (v));
+  }
+
+  {
+    enum E { a = 0 };
+    constexpr auto v = E::a <=> 1;
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (!is_gteq (v));
+  }
+
+  {
+    enum class E { a, b };
+    constexpr auto v = E::a <=> E::b;
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (!is_gteq (v));
+  }
+
+  {
+    int ar[2];
+    constexpr auto v = &ar[1] <=> &ar[0];
+    static_assert (__is_same_as (decltype (v), const std::strong_ordering));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+    static_assert (!is_lt (v));
+    static_assert (!is_lteq (v));
+    static_assert (is_gt (v));
+    static_assert (is_gteq (v));
+  }
+
+  {
+    constexpr auto v = 3.14 <=> 3.14;
+    static_assert (__is_same_as (decltype (v), const std::partial_ordering));
+    static_assert (is_eq (v));
+    static_assert (!is_neq (v));
+    static_assert (!is_lt (v));
+    static_assert (is_lteq (v));
+    static_assert (!is_gt (v));
+    static_assert (is_gteq (v));
+  }
+
+  {
+    // GCC doesn't consider &f == &g to be a constant expression (PR 69681)
+    const auto v = &f <=> &g;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    assert (!is_eq (v));
+    assert (is_neq (v));
+  }
+
+  {
+    struct A { int i; int j; };
+    constexpr auto v = &A::i <=> &A::j;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+  }
+
+  {
+    struct A { void f(); };
+    constexpr auto v = &A::f <=> &A::f;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    static_assert (is_eq (v));
+    static_assert (!is_neq (v));
+  }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C
new file mode 100644 (file)
index 0000000..1dc9549
--- /dev/null
@@ -0,0 +1,41 @@
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while(0)
+
+void f(){}
+void g(){}
+
+template <class T, class U, class R>
+constexpr bool check(T a, U b, R expected)
+{
+  auto r = a <=> b;
+  static_assert (__is_same_as (decltype (r), R));
+  return r == expected;
+}
+
+int main()
+{
+  static_assert (check (1, 2, std::strong_ordering::less));
+
+  enum E1 { a = 0 };
+  static_assert (check (a, 1, std::strong_ordering::less));
+
+  enum class E2 { a, b };
+  static_assert (check (E2::a, E2::b, std::strong_ordering::less));
+
+  int ar[2];
+  static_assert (check (&ar[1], &ar[0], std::strong_ordering::greater));
+
+  static_assert (check (3.14, 3.14, std::partial_ordering::equivalent));
+
+  // GCC doesn't consider &f == &g to be a constant expression (PR 69681)
+  assert (check (&f, &g, std::strong_equality::nonequal));
+
+  struct A { int i; int j; };
+  static_assert (check (&A::i, &A::j, std::strong_equality::nonequal));
+
+  struct A2 { void f(); };
+  static_assert (check (&A2::f, &A2::f, std::strong_equality::equal));
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C
new file mode 100644 (file)
index 0000000..d3cb0a6
--- /dev/null
@@ -0,0 +1,11 @@
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+
+int main()
+{
+  { true <=> 1; }              // { dg-error "bool" }
+  { int a[2]; a <=> a; }       // { dg-error "2" }
+  { -1 <=> 1U; }               // { dg-error "narrowing" }
+  { enum A { a }; enum B { b }; a <=> b; } // { dg-error "A" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C
new file mode 100644 (file)
index 0000000..20bc8e6
--- /dev/null
@@ -0,0 +1,21 @@
+// { dg-do run { target c++2a } }
+// { dg-options "-fext-numeric-literals" }
+
+#include <compare>
+
+int main()
+{
+  // GCC complex literal extension  
+  {
+    constexpr auto v = 1 <=> 1i;
+    static_assert (__is_same_as (decltype (v), const std::strong_equality));
+    static_assert (!is_eq (v));
+    static_assert (is_neq (v));
+  }
+  {
+    constexpr auto v = 1i <=> 1.0i;
+    static_assert (__is_same_as (decltype (v), const std::weak_equality));
+    static_assert (is_eq (v));
+    static_assert (!is_neq (v));
+  }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C
new file mode 100644 (file)
index 0000000..6a03f54
--- /dev/null
@@ -0,0 +1,7 @@
+// { dg-do compile { target c++2a } }
+
+// missing #include <compare>
+template <class T, T x = (T() <=> T()) == 0>
+void f(T);
+constexpr int f(...) { return 42; }
+constexpr int i = f(24);
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C
new file mode 100644 (file)
index 0000000..2a35de9
--- /dev/null
@@ -0,0 +1,43 @@
+// Test with all operators explicitly defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(const D& x) const = default;
+  bool operator==(const D& x) const = default;
+  bool operator!=(const D& x) const = default;
+  bool operator<(const D& x) const = default;
+  bool operator<=(const D& x) const = default;
+  bool operator>(const D& x) const = default;
+  bool operator>=(const D& x) const = default;
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  D d2{24};
+
+  assert (is_eq (d <=> d));
+  assert (is_lteq (d <=> d));
+  assert (is_gteq (d <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (!(d2 != d2));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C
new file mode 100644 (file)
index 0000000..3231457
--- /dev/null
@@ -0,0 +1,113 @@
+// Test with all operators explicitly defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+template <class T>
+struct D
+{
+  T i;
+  auto operator<=>(const D& x) const = default;
+  bool operator==(const D& x) const = default;
+  bool operator!=(const D& x) const = default;
+  bool operator<(const D& x) const = default;
+  bool operator<=(const D& x) const = default;
+  bool operator>(const D& x) const = default;
+  bool operator>=(const D& x) const = default;
+};
+
+template <class T>
+struct E
+{
+  T i;
+  auto operator<=>(const E& x) const = default;
+  // auto operator==(const E& x) const = default;
+  // auto operator!=(const E& x) const = default;
+  // auto operator<(const E& x) const = default;
+  // auto operator<=(const E& x) const = default;
+  // auto operator>(const E& x) const = default;
+  // auto operator>=(const E& x) const = default;
+};
+
+template <class T>
+struct F
+{
+  T i;
+  constexpr auto operator<=>(T x) const { return i<=>x; }
+  constexpr bool operator== (T x) const { return i==x;  }
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+template <class T, class U>
+constexpr bool check_eq (T d, U d2)
+{
+  return is_eq (d <=> d2)
+    && is_eq (d2 <=> d)
+    && is_lteq (d <=> d2)
+    && is_lteq (d2 <=> d)
+    && !is_lt (d <=> d2)
+    && !is_lt (d2 <=> d)
+    && is_gteq (d <=> d2)
+    && is_gteq (d2 <=> d)
+    && !is_gt (d <=> d2)
+    && !is_gt (d2 <=> d)
+    && d == d2
+    && d2 == d
+    && !(d != d2)
+    && !(d2 != d)
+    && d >= d2
+    && d <= d2
+    && d2 >= d
+    && d2 <= d
+    && !(d < d2)
+    && !(d2 < d)
+    && !(d > d2)
+    && !(d2 > d);
+}
+
+template <class T, class U>
+constexpr bool check_less (T d, U d2)
+{
+  return !is_eq (d <=> d2)
+    && !is_eq (d2 <=> d)
+    && is_lteq (d <=> d2)
+    && !is_lteq (d2 <=> d)
+    && is_lt (d <=> d2)
+    && !is_lt (d2 <=> d)
+    && !is_gteq (d <=> d2)
+    && is_gteq (d2 <=> d)
+    && !is_gt (d <=> d2)
+    && is_gt (d2 <=> d)
+    && !(d == d2)
+    && !(d2 == d)
+    && (d != d2)
+    && (d2 != d)
+    && !(d >= d2)
+    && (d <= d2)
+    && (d2 >= d)
+    && !(d2 <= d)
+    && (d < d2)
+    && !(d2 < d)
+    && !(d > d2)
+    && (d2 > d);
+}
+
+int main()
+{
+  constexpr D<int> d{42};
+  constexpr D<int> d2{24};
+
+  static_assert (check_eq (d, d));
+  static_assert (check_less (d2, d));
+
+  constexpr E<float> e { 3.14 };
+  constexpr E<float> ee { 2.72 };
+  static_assert (check_eq (e, e));
+  static_assert (check_less (ee, e));
+
+  constexpr F<char> f { 'b' };
+  static_assert (check_eq (f, 'b'));
+  static_assert (check_less (f, 'c'));
+  static_assert (check_less ('a', f));
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C
new file mode 100644 (file)
index 0000000..cf23c97
--- /dev/null
@@ -0,0 +1,43 @@
+// Test with only spaceship defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(const D& x) const = default;
+  // auto operator==(const D& x) const = default;
+  // auto operator!=(const D& x) const = default;
+  // auto operator<(const D& x) const = default;
+  // auto operator<=(const D& x) const = default;
+  // auto operator>(const D& x) const = default;
+  // auto operator>=(const D& x) const = default;
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  D d2{24};
+
+  assert (is_eq (d <=> d));
+  assert (is_lteq (d <=> d));
+  assert (is_gteq (d <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (!(d2 != d2));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C
new file mode 100644 (file)
index 0000000..0fc5aa2
--- /dev/null
@@ -0,0 +1,48 @@
+// Test for reversed candidates.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(int x) const { return i<=>x; }
+  bool operator== (int x) const { return i==x;  }
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+int main()
+{
+  D d{42};
+  int d1 = 42;
+  int d2 = 24;
+
+  assert (is_eq (d <=> d1));
+  assert (is_eq (d1 <=> d));
+  assert (is_lteq (d <=> d1));
+  assert (is_lteq (d1 <=> d));
+  assert (is_gteq (d <=> d1));
+  assert (is_gteq (d1 <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d1);
+  assert (d1 == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (d2 != d);
+  assert (!(d != d1));
+  assert (!(d1 != d));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d1 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+  assert (d >= d1);
+  assert (d <= d1);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C
new file mode 100644 (file)
index 0000000..89f8489
--- /dev/null
@@ -0,0 +1,54 @@
+// Test for reversed candidates.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+struct D
+{
+  int i;
+  auto operator<=>(int x) const { return i<=>x; }
+  bool operator== (int x) const { return i==x;  }
+};
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
+
+template <class T>
+void f()
+{
+  D d{42};
+  int d1 = 42;
+  int d2 = 24;
+
+  assert (is_eq (d <=> d1));
+  assert (is_eq (d1 <=> d));
+  assert (is_lteq (d <=> d1));
+  assert (is_lteq (d1 <=> d));
+  assert (is_gteq (d <=> d1));
+  assert (is_gteq (d1 <=> d));
+  assert (is_lt (d2 <=> d));
+  assert (is_lteq (d2 <=> d));
+  assert (is_gt (d <=> d2));
+  assert (is_gteq (d <=> d2));
+
+  assert (d == d1);
+  assert (d1 == d);
+  assert (!(d2 == d));
+  assert (!(d == d2));
+  assert (d != d2);
+  assert (d2 != d);
+  assert (!(d != d1));
+  assert (!(d1 != d));
+
+  assert (d2 < d);
+  assert (d2 <= d);
+  assert (d1 <= d);
+  assert (d > d2);
+  assert (d >= d2);
+  assert (d >= d1);
+  assert (d <= d1);
+}
+
+int main()
+{
+  f<int>();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C
new file mode 100644 (file)
index 0000000..1ff3954
--- /dev/null
@@ -0,0 +1,15 @@
+// Test explicit weak_ordering.
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+struct A
+{
+  int i;
+  std::weak_ordering operator<=> (const A&) const = default;
+};
+
+constexpr A a = { 42 };
+constexpr auto c = a <=> a;
+static_assert (std::same_as <decltype (c), const std::weak_ordering>);
+static_assert (std::is_eq (c));
+
index 139f7b49c25ab981b4de9bff6a8ceffd6799b09a..18b2219166a46482e5f92a9b5067e61d9385b79d 100644 (file)
@@ -60,6 +60,7 @@ struct Y : virtual X
   template <typename T>
   int operator&(T x) { return m + x + 1; }
   friend int operator==(Y o, int x) { return o.m + x + 1; }
+  int operator!=(int x) { return m + x + 1; }
 };
 
 /* The folloiwng "FooN" functions each contain a different way to call and to
@@ -81,7 +82,6 @@ Foo1 (T)
   { int t = x | I; assert (t == 7); }
   { int t = x && I; assert (t == 7); }
   { int t = x || I; assert (t == 7); }
-  { int t = x != I; assert (t == 7); }
   { int t = x < I; assert (t == 7); }
   { int t = x <= I; assert (t == 7); }
   { int t = x > I; assert (t == 7); }
@@ -104,6 +104,7 @@ Foo1 (T)
   { int t = x & I; assert (t == 8); }
   { int t = &x; assert (t == 8); }
   { int t = x == I; assert (t == 8); }
+  { int t = x != I; assert (t == 8); }
 }
 
 template <typename T>
@@ -204,7 +205,6 @@ Foo4 (T)
   { int t = x.operator| (I); assert (t == 7); }
   { int t = x.operator&& (I); assert (t == 7); }
   { int t = x.operator|| (I); assert (t == 7); }
-  { int t = x.operator!= (I); assert (t == 7); }
   { int t = x.operator< (I); assert (t == 7); }
   { int t = x.operator<= (I); assert (t == 7); }
   { int t = x.operator> (I); assert (t == 7); }
@@ -227,6 +227,7 @@ Foo4 (T)
   { int t = x.operator& (); assert (t == 8); }
   { int t = x.operator& (I); assert (t == 8); }
   { int t = operator== (x, I); assert (t == 8); }
+  { int t = x.operator!= (I); assert (t == 8); }
 }
 
 
index 62dd18d0ccb843610489e70fa4eb7e295c802682..c0921bb43de93c135267546c43677b63465fe56d 100644 (file)
@@ -11,18 +11,17 @@ public:
         operator int() const {return 2;}
 };
 
-bool operator==(const MyInt& a, const int& b)   // { dg-message "operator==" } candidate
+bool operator==(const MyInt& a, const int& b) // { dg-message "operator==" "" { target c++17_down } }
 {
         return (int)a == b;
 }
 
-bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" } candidate
+bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" "" { target c++17_down } }
 {
         return (int)a == (int)b;
 }
 
 bool f()
 {
-  return 3 == MyInt();                          // { dg-error "ambiguous" "err" } 
-  // { dg-message "operator==" "match candidate text" { target *-*-* } .-1 }
+  return 3 == MyInt(); // { dg-error "ambiguous" "err" { target c++17_down } }
 }
index a13f75085990ea237e9af3a270db23dda5a6b60d..8be84386d6cb814a78bdaffe58889eefd3516816 100644 (file)
@@ -1,3 +1,8 @@
+2019-11-05  Tim van Deurzen  <tim@kompiler.org>
+
+       * cpplib.h: Add spaceship operator for C++.
+       * lex.c: Implement conditional lexing of spaceship operator for C++20.
+
 2019-10-31  Jakub Jelinek  <jakub@redhat.com>
 
        PR preprocessor/92296
index c655d3ffc903f4270edc6a613a00b3f5af7e2b47..ed108f17bfa122fa951199f90fd2d665ec056cc4 100644 (file)
@@ -78,6 +78,7 @@ struct _cpp_file;
   OP(NOT_EQ,           "!=")                                           \
   OP(GREATER_EQ,       ">=")                                           \
   OP(LESS_EQ,          "<=")                                           \
+  OP(SPACESHIP,                "<=>")                                          \
                                                                        \
   /* These two are unary + / - in preprocessor expressions.  */                \
   OP(PLUS_EQ,          "+=")   /* math */                              \
index 3e7d1c37ff5edb83da573b2f47e7b65e39667905..e95eda3f44e84e1d203ce7ea4a3c90279b42f71d 100644 (file)
@@ -2980,7 +2980,13 @@ _cpp_lex_direct (cpp_reader *pfile)
 
       result->type = CPP_LESS;
       if (*buffer->cur == '=')
-       buffer->cur++, result->type = CPP_LESS_EQ;
+       {
+         buffer->cur++, result->type = CPP_LESS_EQ;
+         if (*buffer->cur == '>'
+             && CPP_OPTION (pfile, cplusplus)
+             && CPP_OPTION (pfile, lang) >= CLK_GNUCXX2A)
+           buffer->cur++, result->type = CPP_SPACESHIP;
+       }
       else if (*buffer->cur == '<')
        {
          buffer->cur++;
@@ -3491,6 +3497,7 @@ cpp_avoid_paste (cpp_reader *pfile, const cpp_token *token1,
                                || (CPP_OPTION (pfile, objc)
                                    && token1->val.str.text[0] == '@'
                                    && (b == CPP_NAME || b == CPP_STRING)));
+    case CPP_LESS_EQ:  return c == '>';
     case CPP_STRING:
     case CPP_WSTRING:
     case CPP_UTF8STRING:
index 05f6a2453350514a171a4d921176b964d13788fa..4f05f774b8b2cea23626492e7d8dda309f6d76e3 100644 (file)
@@ -1,5 +1,10 @@
 2019-11-05  Jonathan Wakely  <jwakely@redhat.com>
 
+       * libsupc++/compare: New header.
+       * libsupc++/Makefile.am (std_HEADERS): Add compare.
+       * include/std/version: Define __cpp_lib_three_way_comparison.
+       * include/std/functional: #include <compare>.
+
        * include/std/version [!_GLIBCXX_HOSTED]: Do not define feature test
        macros for features that are only present in hosted builds.
 
index 3e526dc14b74121959995c15f988c130dac772b2..49fd41360bca942275ff927beba34ccee30107f4 100644 (file)
@@ -1385,8 +1385,9 @@ endif
 # <new>, <typeinfo>, <exception>, <initializer_list>, <cstdalign>, <cstdarg>,
 # <concepts>, <cstdbool>, <type_traits>, <bit>, <atomic>,
 # and any files which they include (and which we provide).
-# <new>, <typeinfo>, <exception>, and <initializer_list> are installed by
-# libsupc++, so only the others and the sub-includes are copied here.
+# <new>, <typeinfo>, <exception>, <initializer_list> and <compare>
+# are installed by libsupc++, so only the others and the sub-includes
+# are copied here.
 install-freestanding-headers:
        $(mkinstalldirs) $(DESTDIR)${gxx_include_dir}/bits
        for file in c++0x_warning.h atomic_base.h concept_check.h move.h; do \
index dad7781378b23123b3c88352df1574ad86659247..b6a57ac9b54ef16eaea8bddfdb29793fc2be8c16 100644 (file)
@@ -66,6 +66,7 @@
 #endif
 #if __cplusplus > 201703L
 # include <bits/range_cmp.h>
+# include <compare>
 #endif
 
 namespace std _GLIBCXX_VISIBILITY(default)
index 2df1369666823b46b5c000e377dbbe39415567d9..fa6d27467f798a443285f81192984179726cd61d 100644 (file)
 #define __cpp_lib_list_remove_return_type 201806L
 #define __cpp_lib_math_constants 201907L
 #define __cpp_lib_span 201902L
+#if __cpp_impl_three_way_comparison >= 201907L
+# define __cpp_lib_three_way_comparison 201711L
+#endif
 #define __cpp_lib_to_array 201907L
 #endif
 #endif // C++2a
index eec7b9535142fd91eab92dea13b082af6fa3945e..8303f88ef41f5fdf4ea31bb661a856e0751c814c 100644 (file)
@@ -31,7 +31,7 @@ toolexeclib_LTLIBRARIES = libsupc++.la
 noinst_LTLIBRARIES = libsupc++convenience.la
 
 std_HEADERS = \
-       cxxabi.h exception initializer_list new typeinfo
+       compare cxxabi.h exception initializer_list new typeinfo
 
 bits_HEADERS = \
        atomic_lockfree_defines.h cxxabi_forced.h \
diff --git a/libstdc++-v3/libsupc++/compare b/libstdc++-v3/libsupc++/compare
new file mode 100644 (file)
index 0000000..379b2d4
--- /dev/null
@@ -0,0 +1,644 @@
+// -*- C++ -*- operator<=> three-way comparison support.
+
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of GCC.
+//
+// GCC is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3, or (at your option)
+// any later version.
+//
+// GCC is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+/** @file compare
+ *  This is a Standard C++ Library header.
+ */
+
+#ifndef _COMPARE
+#define _COMPARE
+
+#pragma GCC system_header
+
+#if __cplusplus > 201703L && __cpp_impl_three_way_comparison >= 201907L
+
+#pragma GCC visibility push(default)
+
+#include <concepts>
+
+namespace std
+{
+#define __cpp_lib_three_way_comparison 201711L
+
+  // [cmp.categories], comparison category types
+
+  namespace __cmp_cat
+  {
+    enum class _Eq
+    { equal = 0, equivalent = equal, nonequal = 1, nonequivalent = nonequal };
+
+    enum class _Ord { _Less = -1, _Greater = 1 };
+
+    enum class _Ncmp { _Unordered = -127 };
+
+    struct __unspec
+    {
+      constexpr __unspec(__unspec*) { }
+    };
+  }
+
+  class weak_equality
+  {
+    int _M_value;
+
+    constexpr explicit
+    weak_equality(__cmp_cat::_Eq __val) noexcept
+    : _M_value(int(__val))
+    { }
+
+  public:
+    // valid values
+
+    static const weak_equality equivalent;
+    static const weak_equality nonequivalent;
+
+    // comparisons
+
+    friend constexpr bool
+    operator==(weak_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(weak_equality, weak_equality) noexcept = default;
+
+    friend constexpr weak_equality
+    operator<=>(weak_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr weak_equality
+    operator<=>(__cmp_cat::__unspec, weak_equality __v) noexcept
+    { return __v; }
+  };
+
+  // valid values' definitions
+  inline constexpr weak_equality
+  weak_equality::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr weak_equality
+  weak_equality::nonequivalent(__cmp_cat::_Eq::nonequivalent);
+
+  class strong_equality
+  {
+    int _M_value;
+
+    constexpr explicit
+    strong_equality(__cmp_cat::_Eq __val) noexcept
+    : _M_value(int(__val))
+    { }
+
+  public:
+    // valid values
+
+    static const strong_equality equal;
+    static const strong_equality nonequal;
+    static const strong_equality equivalent;
+    static const strong_equality nonequivalent;
+
+    // conversion
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+       return weak_equality::equivalent;
+      else
+       return weak_equality::nonequivalent;
+    }
+
+    // comparisons
+
+    friend constexpr bool
+    operator==(strong_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(strong_equality, strong_equality) noexcept = default;
+
+    friend constexpr strong_equality
+    operator<=>(strong_equality __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr strong_equality
+    operator<=>(__cmp_cat::__unspec, strong_equality __v) noexcept
+    { return __v; }
+  };
+
+  // valid values' definitions
+  inline constexpr strong_equality
+  strong_equality::equal(__cmp_cat::_Eq::equal);
+
+  inline constexpr strong_equality
+  strong_equality::nonequal(__cmp_cat::_Eq::nonequal);
+
+  inline constexpr strong_equality
+  strong_equality::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr strong_equality
+  strong_equality::nonequivalent(__cmp_cat::_Eq::nonequivalent);
+
+  class partial_ordering
+  {
+    int _M_value;
+    bool _M_is_ordered;
+
+    constexpr explicit
+    partial_ordering(__cmp_cat::_Eq __v) noexcept
+    : _M_value(int(__v)), _M_is_ordered(true)
+    { }
+
+    constexpr explicit
+    partial_ordering(__cmp_cat::_Ord __v) noexcept
+    : _M_value(int(__v)), _M_is_ordered(true)
+    { }
+
+    constexpr explicit
+    partial_ordering(__cmp_cat::_Ncmp __v) noexcept
+    : _M_value(int(__v)), _M_is_ordered(false)
+    { }
+
+  public:
+    // valid values
+    static const partial_ordering less;
+    static const partial_ordering equivalent;
+    static const partial_ordering greater;
+    static const partial_ordering unordered;
+
+    // conversion
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+       return weak_equality::equivalent;
+      else
+       return weak_equality::nonequivalent;
+    }
+
+    // comparisons
+    friend constexpr bool
+    operator==(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(partial_ordering, partial_ordering) noexcept = default;
+
+    friend constexpr bool
+    operator< (partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value < 0; }
+
+    friend constexpr bool
+    operator> (partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value > 0; }
+
+    friend constexpr bool
+    operator<=(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value <= 0; }
+
+    friend constexpr bool
+    operator>=(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_is_ordered && __v._M_value >= 0; }
+
+    friend constexpr bool
+    operator< (__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 < __v._M_value; }
+
+    friend constexpr bool
+    operator> (__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 > __v._M_value; }
+
+    friend constexpr bool
+    operator<=(__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 <= __v._M_value; }
+
+    friend constexpr bool
+    operator>=(__cmp_cat::__unspec, partial_ordering __v) noexcept
+    { return __v._M_is_ordered && 0 >= __v._M_value; }
+
+    friend constexpr partial_ordering
+    operator<=>(partial_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr partial_ordering
+    operator<=>(__cmp_cat::__unspec, partial_ordering __v) noexcept
+    {
+      if (__v < 0)
+       return  partial_ordering::greater;
+      else if (__v > 0)
+       return partial_ordering::less;
+      else
+       return __v;
+    }
+  };
+
+  // valid values' definitions
+  inline constexpr partial_ordering
+  partial_ordering::less(__cmp_cat::_Ord::_Less);
+
+  inline constexpr partial_ordering
+  partial_ordering::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr partial_ordering
+  partial_ordering::greater(__cmp_cat::_Ord::_Greater);
+
+  inline constexpr partial_ordering
+  partial_ordering::unordered(__cmp_cat::_Ncmp::_Unordered);
+
+  class weak_ordering
+  {
+    int _M_value;
+
+    constexpr explicit
+    weak_ordering(__cmp_cat::_Eq __v) noexcept : _M_value(int(__v))
+    { }
+
+    constexpr explicit
+    weak_ordering(__cmp_cat::_Ord __v) noexcept : _M_value(int(__v))
+    { }
+
+  public:
+    // valid values
+    static const weak_ordering less;
+    static const weak_ordering equivalent;
+    static const weak_ordering greater;
+
+    // conversions
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+       return weak_equality::equivalent;
+      else
+       return weak_equality::nonequivalent;
+    }
+
+    constexpr operator partial_ordering() const noexcept
+    {
+      if (_M_value == 0)
+       return partial_ordering::equivalent;
+      else if (_M_value < 0)
+       return partial_ordering::less;
+      else
+       return partial_ordering::greater;
+    }
+
+    // comparisons
+    friend constexpr bool
+    operator==(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(weak_ordering, weak_ordering) noexcept = default;
+
+    friend constexpr bool
+    operator< (weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value < 0; }
+
+    friend constexpr bool
+    operator> (weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value > 0; }
+
+    friend constexpr bool
+    operator<=(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value <= 0; }
+
+    friend constexpr bool
+    operator>=(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value >= 0; }
+
+    friend constexpr bool
+    operator< (__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 < __v._M_value; }
+
+    friend constexpr bool
+    operator> (__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 > __v._M_value; }
+
+    friend constexpr bool
+    operator<=(__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 <= __v._M_value; }
+
+    friend constexpr bool
+    operator>=(__cmp_cat::__unspec, weak_ordering __v) noexcept
+    { return 0 >= __v._M_value; }
+
+    friend constexpr weak_ordering
+    operator<=>(weak_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr weak_ordering
+    operator<=>(__cmp_cat::__unspec, weak_ordering __v) noexcept
+    {
+      if (__v < 0)
+       return  weak_ordering::greater;
+      else if (__v > 0)
+       return weak_ordering::less;
+      else
+       return __v;
+    }
+  };
+
+  // valid values' definitions
+  inline constexpr weak_ordering
+  weak_ordering::less(__cmp_cat::_Ord::_Less);
+
+  inline constexpr weak_ordering
+  weak_ordering::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr weak_ordering
+  weak_ordering::greater(__cmp_cat::_Ord::_Greater);
+
+  class strong_ordering
+  {
+    int _M_value;
+
+    constexpr explicit
+    strong_ordering(__cmp_cat::_Eq __v) noexcept
+    : _M_value(int(__v))
+    { }
+
+    constexpr explicit
+    strong_ordering(__cmp_cat::_Ord __v) noexcept
+    : _M_value(int(__v))
+    { }
+
+  public:
+    // valid values
+    static const strong_ordering less;
+    static const strong_ordering equal;
+    static const strong_ordering equivalent;
+    static const strong_ordering greater;
+
+    // conversions
+    constexpr operator weak_equality() const noexcept
+    {
+      if (_M_value == 0)
+       return weak_equality::equivalent;
+      else
+       return weak_equality::nonequivalent;
+    }
+
+    constexpr operator strong_equality() const noexcept
+    {
+      if (_M_value == 0)
+       return strong_equality::equal;
+      else
+       return strong_equality::nonequal;
+    }
+
+    constexpr operator partial_ordering() const noexcept
+    {
+      if (_M_value == 0)
+       return partial_ordering::equivalent;
+      else if (_M_value < 0)
+       return partial_ordering::less;
+      else
+       return partial_ordering::greater;
+    }
+
+    constexpr operator weak_ordering() const noexcept
+    {
+      if (_M_value == 0)
+       return weak_ordering::equivalent;
+      else if (_M_value < 0)
+       return weak_ordering::less;
+      else
+       return weak_ordering::greater;
+    }
+
+    // comparisons
+    friend constexpr bool
+    operator==(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value == 0; }
+
+    friend constexpr bool
+    operator==(strong_ordering, strong_ordering) noexcept = default;
+
+    friend constexpr bool
+    operator< (strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value < 0; }
+
+    friend constexpr bool
+    operator> (strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value > 0; }
+
+    friend constexpr bool
+    operator<=(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value <= 0; }
+
+    friend constexpr bool
+    operator>=(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v._M_value >= 0; }
+
+    friend constexpr bool
+    operator< (__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 < __v._M_value; }
+
+    friend constexpr bool
+    operator> (__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 > __v._M_value; }
+
+    friend constexpr bool
+    operator<=(__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 <= __v._M_value; }
+
+    friend constexpr bool
+    operator>=(__cmp_cat::__unspec, strong_ordering __v) noexcept
+    { return 0 >= __v._M_value; }
+
+    friend constexpr strong_ordering
+    operator<=>(strong_ordering __v, __cmp_cat::__unspec) noexcept
+    { return __v; }
+
+    friend constexpr strong_ordering
+    operator<=>(__cmp_cat::__unspec, strong_ordering __v) noexcept
+    {
+      if (__v < 0)
+       return  strong_ordering::greater;
+      else if (__v > 0)
+       return strong_ordering::less;
+      else
+       return __v;
+    }
+  };
+
+  // valid values' definitions
+  inline constexpr strong_ordering
+  strong_ordering::less(__cmp_cat::_Ord::_Less);
+
+  inline constexpr strong_ordering
+  strong_ordering::equal(__cmp_cat::_Eq::equal);
+
+  inline constexpr strong_ordering
+  strong_ordering::equivalent(__cmp_cat::_Eq::equivalent);
+
+  inline constexpr strong_ordering
+  strong_ordering::greater(__cmp_cat::_Ord::_Greater);
+
+
+  // named comparison functions
+  constexpr bool
+  is_eq(weak_equality __cmp) noexcept
+  { return __cmp == 0; }
+
+  constexpr bool
+  is_neq(weak_equality __cmp) noexcept
+  { return __cmp != 0; }
+
+  constexpr bool
+  is_lt  (partial_ordering __cmp) noexcept
+  { return __cmp < 0; }
+
+  constexpr bool
+  is_lteq(partial_ordering __cmp) noexcept
+  { return __cmp <= 0; }
+
+  constexpr bool
+  is_gt  (partial_ordering __cmp) noexcept
+  { return __cmp > 0; }
+
+  constexpr bool
+  is_gteq(partial_ordering __cmp) noexcept
+  { return __cmp >= 0; }
+
+  // [cmp.common], common comparison category type
+  template<typename... _Ts>
+    struct common_comparison_category {
+      // using type = TODO
+    };
+
+  template<typename... _Ts>
+    using common_comparison_category_t
+      = typename common_comparison_category<_Ts...>::type;
+
+#if __cpp_concepts
+  namespace __detail
+  {
+    template<typename _Tp, typename _Cat>
+      concept __compares_as
+       = same_as<common_comparison_category_t<_Tp, _Cat>, _Cat>;
+
+    template<typename _Tp, typename _Up>
+      concept __partially_ordered_with
+       = requires(const remove_reference_t<_Tp>& __t,
+                  const remove_reference_t<_Up>& __u) {
+         { __t <  __u } -> boolean;
+         { __t >  __u } -> boolean;
+         { __t <= __u } -> boolean;
+         { __t >= __u } -> boolean;
+         { __u <  __t } -> boolean;
+         { __u >  __t } -> boolean;
+         { __u <= __t } -> boolean;
+         { __u >= __t } -> boolean;
+       };
+  } // namespace __detail
+
+  // [cmp.concept], concept three_way_comparable
+  template<typename _Tp, typename _Cat = partial_ordering>
+    concept three_way_comparable
+      = __detail::__weakly_eq_cmp_with<_Tp, _Tp>
+      && (!convertible_to<_Cat, partial_ordering>
+         || __detail::__partially_ordered_with<_Tp, _Tp>)
+      && requires(const remove_reference_t<_Tp>& __a,
+                 const remove_reference_t<_Tp>& __b) {
+       { __a <=> __b } -> __detail::__compares_as<_Cat>;
+      };
+
+  template<typename _Tp, typename _Up, typename _Cat = partial_ordering>
+    concept three_way_comparable_with
+      = __detail::__weakly_eq_cmp_with<_Tp, _Up>
+      && (!convertible_to<_Cat, partial_ordering>
+         || __detail::__partially_ordered_with<_Tp, _Up>)
+      && three_way_comparable<_Tp, _Cat>
+      && three_way_comparable<_Up, _Cat>
+      && common_reference_with<const remove_reference_t<_Tp>&,
+                              const remove_reference_t<_Up>&>
+      && three_way_comparable<
+         common_reference_t<const remove_reference_t<_Tp>&,
+                            const remove_reference_t<_Up>&>, _Cat>
+      && requires(const remove_reference_t<_Tp>& __t,
+                 const remove_reference_t<_Up>& __u) {
+       { __t <=> __u } -> __detail::__compares_as<_Cat>;
+       { __u <=> __t } -> __detail::__compares_as<_Cat>;
+      };
+#endif
+
+  template<typename _Tp, typename _Up>
+    using __cmp2way_res_t
+      = decltype(std::declval<_Tp&>() <=> std::declval<_Up&>());
+
+  template<typename _Tp, typename _Up = _Tp, typename = void>
+    struct __cmp3way_helper
+    { };
+
+  template<typename _Tp, typename _Up>
+    struct __cmp3way_helper<_Tp, _Up, void_t<__cmp2way_res_t<_Tp, _Up>>>
+    {
+      using type = __cmp2way_res_t<_Tp, _Up>;
+      using __type = type;
+    };
+
+  /// [cmp.result], result of three-way comparison
+  template<typename _Tp, typename _Up = _Tp>
+    struct compare_three_way_result
+    : __cmp3way_helper<_Tp, _Up>
+    { };
+
+  template<typename _Tp, typename _Up = _Tp>
+    using compare_three_way_result_t
+      = typename compare_three_way_result<_Tp, _Up>::__type;
+
+  // [cmp.object], typename compare_three_way
+  struct compare_three_way
+  {
+    // TODO
+#if 0
+    template<typename _Tp, typename _Up>
+      requires (three_way_comparable_with<_Tp, _Up>
+         || BUILTIN-PTR-THREE-WAY(_Tp, _Up))
+    constexpr auto
+    operator()(_Tp&& __t, _Up&& __u) const noexcept
+    {
+      // TODO
+    }
+#endif
+
+    using is_transparent = void;
+  };
+
+  // [cmp.alg], comparison algorithms
+  inline namespace __cmp_alg
+  {
+    // TODO
+#if 0
+    inline constexpr unspecified strong_order = unspecified;
+    inline constexpr unspecified weak_order = unspecified;
+    inline constexpr unspecified partial_order = unspecified;
+    inline constexpr unspecified compare_strong_order_fallback = unspecified;
+    inline constexpr unspecified compare_weak_order_fallback = unspecified;
+    inline constexpr unspecified compare_partial_order_fallback = unspecified;
+#endif
+  }
+}
+
+#pragma GCC visibility pop
+
+#endif // C++20
+
+#endif // _COMPARE
index 338cf1ba32c87a7bda7b4fc03beee0cd2e50b4cf..938b75e39d98f3083b5b3eabc2b52f629411c8e4 100644 (file)
@@ -52,7 +52,7 @@ static_assert( ! std::regular<HasReference> );
 
 struct HasEq { };
 bool operator==(HasEq, HasEq) { return true; }
-#ifdef __cpp_lib_three_way_comparison
+#ifdef __cpp_impl_three_way_comparison
 static_assert( std::regular<HasEq> );
 #else
 static_assert( ! std::regular<HasEq> );