]> gcc.gnu.org Git - gcc.git/commitdiff
c++: defaulted <=> with bitfields [PR102490]
authorJakub Jelinek <jakub@redhat.com>
Fri, 1 Oct 2021 15:07:17 +0000 (17:07 +0200)
committerJason Merrill <jason@redhat.com>
Wed, 6 Oct 2021 02:41:00 +0000 (22:41 -0400)
The testcases in the patch are either miscompiled or ICE with checking,
because the defaulted operator== is synthesized too early (but only if
constexpr), when the corresponding class type is still incomplete type.  The
problem is that at that point the bitfield FIELD_DECLs still have as
TREE_TYPE their underlying type rather than integral type with their
precision and when layout_class_type is called for the class soon after
that, it changes those types but the COMPONENT_REFs type stay the way that
they were during the operator== synthesize_method type and the middle-end is
then upset by the mismatch of types.  As what exact type will be given isn't
just a one liner but quite long code especially for over-sized bitfields, I
think it is best to just not synthesize the comparison operators so early
and call defaulted_late_check for them once again as soon as the class is
complete.

This is also a problem for virtual operator<=>, where we need to compare the
noexcept-specifier to validate the override before the class is complete.
Rather than try to defer that comparison, maybe_instantiate_noexcept now
calls maybe_synthesize_method, which calls build_comparison_op in
non-defining mode if the class isn't complete yet.  In that situation we
also might not have base fields yet, so we look in the binfo for the bases.

Co-authored-by: Jason Merrill <jason@redhat.com>
2021-10-01  Jakub Jelinek  <jakub@redhat.com>

PR c++/98712
PR c++/102490
* cp-tree.h (maybe_synthesize_method): Declare.
* method.c (genericize_spaceship): Use
LOOKUP_NORMAL | LOOKUP_NONVIRTUAL | LOOKUP_DEFAULTED instead of
LOOKUP_NORMAL for flags.
(comp_info): Remove defining member.  Add complain, code, retcat.
(comp_info::comp_info): Adjust.
(do_one_comp): Split out from build_comparison_op.   Use
LOOKUP_NORMAL | LOOKUP_NONVIRTUAL | LOOKUP_DEFAULTED instead of
LOOKUP_NORMAL for flags.
(build_comparison_op): Add defining argument. Adjust comp_info
construction.  Use defining instead of info.defining.  Assert that
if defining, ctype is a complete type.  Walk base binfos.
(synthesize_method, maybe_explain_implicit_delete,
explain_implicit_non_constexpr): Adjust build_comparison_op callers.
(maybe_synthesize_method): New function.
* class.c (check_bases_and_members): Don't call defaulted_late_check
for sfk_comparison.
(finish_struct_1): Call it here instead after class has been
completed.
* pt.c (maybe_instantiate_noexcept): Call maybe_synthesize_method
instead of synthesize_method.

* g++.dg/cpp2a/spaceship-synth8.C (std::strong_ordering): Provide
more complete definition.
(std::strong_ordering::less, std::strong_ordering::equal,
std::strong_ordering::greater): Define.
* g++.dg/cpp2a/spaceship-synth12.C: New test.
* g++.dg/cpp2a/spaceship-synth13.C: New test.
* g++.dg/cpp2a/spaceship-synth14.C: New test.
* g++.dg/cpp2a/spaceship-eq11.C: New test.
* g++.dg/cpp2a/spaceship-eq12.C: New test.
* g++.dg/cpp2a/spaceship-eq13.C: New test.

gcc/cp/class.c
gcc/cp/cp-tree.h
gcc/cp/method.c
gcc/cp/pt.c
gcc/testsuite/g++.dg/cpp2a/spaceship-eq11.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq12.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-eq13.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth12.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth13.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth14.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-synth8.C

index fe225c61a6290c400e15700767934a68402cc80f..59611627d187d2834d680f772958cf42bbb78c8a 100644 (file)
@@ -6119,6 +6119,10 @@ check_bases_and_members (tree t)
        && !DECL_ARTIFICIAL (fn)
        && DECL_DEFAULTED_IN_CLASS_P (fn))
       {
+       /* ...except handle comparisons later, in finish_struct_1.  */
+       if (special_function_p (fn) == sfk_comparison)
+         continue;
+
        int copy = copy_fn_p (fn);
        if (copy > 0)
          {
@@ -7467,7 +7471,14 @@ finish_struct_1 (tree t)
      for any static member objects of the type we're working on.  */
   for (x = TYPE_FIELDS (t); x; x = DECL_CHAIN (x))
     if (DECL_DECLARES_FUNCTION_P (x))
-      DECL_IN_AGGR_P (x) = false;
+      {
+       /* Synthesize constexpr defaulted comparisons.  */
+       if (!DECL_ARTIFICIAL (x)
+           && DECL_DEFAULTED_IN_CLASS_P (x)
+           && special_function_p (x) == sfk_comparison)
+         defaulted_late_check (x);
+       DECL_IN_AGGR_P (x) = false;
+      }
     else if (VAR_P (x) && TREE_STATIC (x)
             && TREE_TYPE (x) != error_mark_node
             && same_type_p (TYPE_MAIN_VARIANT (TREE_TYPE (x)), t))
index 1fcd50c64fd9befc016e4affabbbf56acef057ae..5248ecd8fe73654254589d5fe133cacab1e7c89b 100644 (file)
@@ -7013,6 +7013,7 @@ extern void explain_implicit_non_constexpr        (tree);
 extern bool deduce_inheriting_ctor             (tree);
 extern bool decl_remember_implicit_trigger_p   (tree);
 extern void synthesize_method                  (tree);
+extern void maybe_synthesize_method            (tree);
 extern tree lazily_declare_fn                  (special_function_kind,
                                                 tree);
 extern tree skip_artificial_parms_for          (const_tree, tree);
index c38912a7ce96def49beec778f20448f31b7b2062..1023aefc575b3124989c188867bda75cac010839 100644 (file)
@@ -1288,21 +1288,19 @@ struct comp_info
 {
   tree fndecl;
   location_t loc;
-  bool defining;
+  tsubst_flags_t complain;
+  tree_code code;
+  comp_cat_tag retcat;
   bool first_time;
   bool constexp;
   bool was_constexp;
   bool noex;
 
-  comp_info (tree fndecl, tsubst_flags_t &complain)
-    : fndecl (fndecl)
+  comp_info (tree fndecl, tsubst_flags_t complain)
+    : fndecl (fndecl), complain (complain)
   {
     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;
 
@@ -1358,23 +1356,99 @@ struct comp_info
   }
 };
 
+/* Subroutine of build_comparison_op, to compare a single subobject.  */
+
+static tree
+do_one_comp (location_t loc, const comp_info &info, tree sub, tree lhs, tree rhs)
+{
+  const tree_code code = info.code;
+  const tree fndecl = info.fndecl;
+  const comp_cat_tag retcat = info.retcat;
+  const tsubst_flags_t complain = info.complain;
+
+  tree overload = NULL_TREE;
+  int flags = LOOKUP_NORMAL | LOOKUP_NONVIRTUAL | LOOKUP_DEFAULTED;
+  /* If we have an explicit comparison category return type we can fall back
+     to </=, so don't give an error yet if <=> lookup fails.  */
+  bool tentative = retcat != cc_last;
+  tree comp = build_new_op (loc, code, flags, lhs, rhs,
+                           NULL_TREE, &overload,
+                           tentative ? tf_none : complain);
+
+  if (code != SPACESHIP_EXPR)
+    return comp;
+
+  tree rettype = TREE_TYPE (TREE_TYPE (fndecl));
+
+  if (comp == error_mark_node)
+    {
+      if (overload == NULL_TREE && (tentative || complain))
+       {
+         /* No viable <=>, try using op< and op==.  */
+         tree lteq = genericize_spaceship (loc, rettype, lhs, rhs);
+         if (lteq != error_mark_node)
+           {
+             /* We found usable < and ==.  */
+             if (retcat != cc_last)
+               /* Return type is a comparison category, use them.  */
+               comp = lteq;
+             else if (complain & tf_error)
+               /* Return type is auto, suggest changing it.  */
+               inform (info.loc, "changing the return type from %qs "
+                       "to a comparison category type will allow the "
+                       "comparison to use %qs and %qs", "auto",
+                       "operator<", "operator==");
+           }
+         else if (tentative && complain)
+           /* No usable < and ==, give an error for op<=>.  */
+           build_new_op (loc, code, flags, lhs, rhs, complain);
+       }
+      if (comp == error_mark_node)
+       return error_mark_node;
+    }
+
+  if (FNDECL_USED_AUTO (fndecl)
+      && cat_tag_for (TREE_TYPE (comp)) == cc_last)
+    {
+      /* The operator function is defined as deleted if ... Ri is not a
+        comparison category type.  */
+      if (complain & tf_error)
+       inform (loc,
+               "three-way comparison of %qD has type %qT, not a "
+               "comparison category type", sub, TREE_TYPE (comp));
+      return error_mark_node;
+    }
+  else if (!FNDECL_USED_AUTO (fndecl)
+          && !can_convert (rettype, TREE_TYPE (comp), complain))
+    {
+      if (complain & tf_error)
+       error_at (loc,
+                 "three-way comparison of %qD has type %qT, which "
+                 "does not convert to %qT",
+                 sub, TREE_TYPE (comp), rettype);
+      return error_mark_node;
+    }
+
+  return comp;
+}
+
 /* 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)
+void
+build_comparison_op (tree fndecl, bool defining, tsubst_flags_t complain)
 {
   comp_info info (fndecl, complain);
 
-  if (!info.defining && !(complain & tf_error) && !DECL_MAYBE_DELETED (fndecl))
+  if (!defining && !(complain & tf_error) && !DECL_MAYBE_DELETED (fndecl))
     return;
 
   int flags = LOOKUP_NORMAL;
   const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (DECL_NAME (fndecl));
-  tree_code code = op->tree_code;
+  tree_code code = info.code = op->tree_code;
 
   tree lhs = DECL_ARGUMENTS (fndecl);
   tree rhs = DECL_CHAIN (lhs);
@@ -1384,6 +1458,7 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
     lhs = convert_from_reference (lhs);
   rhs = convert_from_reference (rhs);
   tree ctype = TYPE_MAIN_VARIANT (TREE_TYPE (lhs));
+  gcc_assert (!defining || COMPLETE_TYPE_P (ctype));
 
   iloc_sentinel ils (info.loc);
 
@@ -1399,7 +1474,7 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
     }
 
   tree compound_stmt = NULL_TREE;
-  if (info.defining)
+  if (defining)
     compound_stmt = begin_compound_stmt (0);
   else
     ++cp_unevaluated_operand;
@@ -1413,21 +1488,42 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
 
   if (code == EQ_EXPR || code == SPACESHIP_EXPR)
     {
-      comp_cat_tag retcat = cc_last;
+      comp_cat_tag &retcat = (info.retcat = cc_last);
       if (code == SPACESHIP_EXPR && !FNDECL_USED_AUTO (fndecl))
        retcat = cat_tag_for (rettype);
 
       bool bad = false;
       auto_vec<tree> comps;
 
-      /* Compare each of the subobjects.  Note that we get bases from
-        next_initializable_field because we're past C++17.  */
+      /* Compare the base subobjects.  We handle them this way, rather than in
+        the field loop below, because maybe_instantiate_noexcept might bring
+        us here before we've built the base fields.  */
+      for (tree base_binfo : BINFO_BASE_BINFOS (TYPE_BINFO (ctype)))
+       {
+         tree lhs_base
+           = build_base_path (PLUS_EXPR, lhs, base_binfo, 0, complain);
+         tree rhs_base
+           = build_base_path (PLUS_EXPR, rhs, base_binfo, 0, complain);
+
+         location_t loc = DECL_SOURCE_LOCATION (TYPE_MAIN_DECL (ctype));
+         tree comp = do_one_comp (loc, info, BINFO_TYPE (base_binfo),
+                                  lhs_base, rhs_base);
+         if (comp == error_mark_node)
+           {
+             bad = true;
+             continue;
+           }
+
+         comps.safe_push (comp);
+       }
+
+      /* Now compare the field subobjects.  */
       for (tree field = next_initializable_field (TYPE_FIELDS (ctype));
           field;
           field = next_initializable_field (DECL_CHAIN (field)))
        {
-         if (DECL_VIRTUAL_P (field))
-           /* Don't compare vptr fields.  */
+         if (DECL_VIRTUAL_P (field) || DECL_FIELD_IS_BASE (field))
+           /* We ignore the vptr, and we already handled bases.  */
            continue;
 
          tree expr_type = TREE_TYPE (field);
@@ -1478,8 +1574,8 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
                break;
              tree idx;
              /* [1] array, no loop needed, just add [0] ARRAY_REF.
-                Similarly if !info.defining.  */
-             if (integer_zerop (maxval) || !info.defining)
+                Similarly if !defining.  */
+             if (integer_zerop (maxval) || !defining)
                idx = size_zero_node;
              /* Some other array, will need runtime loop.  */
              else
@@ -1496,69 +1592,13 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
          if (TREE_CODE (expr_type) == ARRAY_TYPE)
            continue;
 
-         tree overload = NULL_TREE;
-         tree comp = build_new_op (field_loc, code, flags, lhs_mem, rhs_mem,
-                                   NULL_TREE, &overload,
-                                   retcat != cc_last ? tf_none : complain);
+         tree comp = do_one_comp (field_loc, info, field, lhs_mem, rhs_mem);
          if (comp == error_mark_node)
            {
-             if (overload == NULL_TREE && code == SPACESHIP_EXPR
-                 && (retcat != cc_last || complain))
-               {
-                 tree comptype = (retcat != cc_last ? rettype
-                                  : DECL_SAVED_AUTO_RETURN_TYPE (fndecl));
-                 /* No viable <=>, try using op< and op==.  */
-                 tree lteq = genericize_spaceship (field_loc, comptype,
-                                                   lhs_mem, rhs_mem);
-                 if (lteq != error_mark_node)
-                   {
-                     /* We found usable < and ==.  */
-                     if (retcat != cc_last)
-                       /* Return type is a comparison category, use them.  */
-                       comp = lteq;
-                     else if (complain & tf_error)
-                       /* Return type is auto, suggest changing it.  */
-                       inform (info.loc, "changing the return type from %qs "
-                               "to a comparison category type will allow the "
-                               "comparison to use %qs and %qs", "auto",
-                               "operator<", "operator==");
-                   }
-                 else if (retcat != cc_last && complain != tf_none)
-                   /* No usable < and ==, give an error for op<=>.  */
-                   build_new_op (field_loc, code, flags, lhs_mem, rhs_mem,
-                                 complain);
-               }
-             if (comp == error_mark_node)
-               {
-                 bad = true;
-                 continue;
-               }
-           }
-         if (code != SPACESHIP_EXPR)
-           ;
-         else if (FNDECL_USED_AUTO (fndecl)
-                  && cat_tag_for (TREE_TYPE (comp)) == cc_last)
-           {
-             /* The operator function is defined as deleted if ... Ri is not a
-                comparison category type.  */
-             if (complain & tf_error)
-               inform (field_loc,
-                       "three-way comparison of %qD has type %qT, not a "
-                       "comparison category type", field, TREE_TYPE (comp));
-             bad = true;
-             continue;
-           }
-         else if (!FNDECL_USED_AUTO (fndecl)
-                  && !can_convert (rettype, TREE_TYPE (comp), complain))
-           {
-             if (complain & tf_error)
-               error_at (field_loc,
-                         "three-way comparison of %qD has type %qT, which "
-                         "does not convert to %qT",
-                         field, TREE_TYPE (comp), rettype);
              bad = true;
              continue;
            }
+
          /* Most of the time, comp is the expression that should be evaluated
             to compare the two members.  If the expression needs to be
             evaluated more than once in a loop, it will be a TREE_LIST
@@ -1588,7 +1628,7 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
          tree comp = comps[i];
          tree eq, retval = NULL_TREE, if_ = NULL_TREE;
          tree loop_indexes = NULL_TREE;
-         if (info.defining)
+         if (defining)
            {
              if (TREE_CODE (comp) == TREE_LIST)
                {
@@ -1636,7 +1676,7 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
                comp = build_static_cast (input_location, rettype, comp,
                                          complain);
              info.check (comp);
-             if (info.defining)
+             if (defining)
                {
                  tree var = create_temporary_var (rettype);
                  pushdecl (var);
@@ -1649,7 +1689,7 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
            }
          tree ceq = contextual_conv_bool (eq, complain);
          info.check (ceq);
-         if (info.defining)
+         if (defining)
            {
              finish_if_stmt_cond (ceq, if_);
              finish_then_clause (if_);
@@ -1662,7 +1702,7 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
                finish_for_stmt (TREE_VALUE (loop_index));
            }
        }
-      if (info.defining)
+      if (defining)
        {
          tree val;
          if (code == EQ_EXPR)
@@ -1683,7 +1723,7 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
                                NULL_TREE, NULL, complain);
       comp = contextual_conv_bool (comp, complain);
       info.check (comp);
-      if (info.defining)
+      if (defining)
        {
          tree neg = build1 (TRUTH_NOT_EXPR, boolean_type_node, comp);
          finish_return_stmt (neg);
@@ -1696,12 +1736,12 @@ build_comparison_op (tree fndecl, tsubst_flags_t complain)
       tree comp2 = build_new_op (info.loc, code, flags, comp, integer_zero_node,
                                 NULL_TREE, NULL, complain);
       info.check (comp2);
-      if (info.defining)
+      if (defining)
        finish_return_stmt (comp2);
     }
 
  out:
-  if (info.defining)
+  if (defining)
     finish_compound_stmt (compound_stmt);
   else
     --cp_unevaluated_operand;
@@ -1780,7 +1820,7 @@ synthesize_method (tree fndecl)
   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);
+      build_comparison_op (fndecl, true, tf_none);
       need_body = false;
     }
 
@@ -1814,6 +1854,32 @@ synthesize_method (tree fndecl)
              fndecl);
 }
 
+/* Like synthesize_method, but don't actually synthesize defaulted comparison
+   methods if their class is still incomplete.  Just deduce the return
+   type in that case.  */
+
+void
+maybe_synthesize_method (tree fndecl)
+{
+  if (special_function_p (fndecl) == sfk_comparison)
+    {
+      tree lhs = DECL_ARGUMENTS (fndecl);
+      if (is_this_parameter (lhs))
+       lhs = cp_build_fold_indirect_ref (lhs);
+      else
+       lhs = convert_from_reference (lhs);
+      tree ctype = TYPE_MAIN_VARIANT (TREE_TYPE (lhs));
+      if (!COMPLETE_TYPE_P (ctype))
+       {
+         push_deferring_access_checks (dk_no_deferred);
+         build_comparison_op (fndecl, false, tf_none);
+         pop_deferring_access_checks ();
+         return;
+       }
+    }
+  return synthesize_method (fndecl);
+}
+
 /* Build a reference to type TYPE with cv-quals QUALS, which is an
    rvalue if RVALUE is true.  */
 
@@ -2753,7 +2819,7 @@ maybe_explain_implicit_delete (tree decl)
          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);
+         build_comparison_op (decl, false, tf_warning_or_error);
        }
       else if (!informed)
        {
@@ -2814,7 +2880,7 @@ explain_implicit_non_constexpr (tree decl)
   if (sfk == sfk_comparison)
     {
       DECL_DECLARED_CONSTEXPR_P (decl) = true;
-      build_comparison_op (decl, tf_warning_or_error);
+      build_comparison_op (decl, false, tf_warning_or_error);
       DECL_DECLARED_CONSTEXPR_P (decl) = false;
     }
   else
index 844b7c14fe42b0a42f404e63d697c26e3d342988..19e03369ffa654ec1a5fe4cabe84844075c9ed1a 100644 (file)
@@ -25773,7 +25773,7 @@ maybe_instantiate_noexcept (tree fn, tsubst_flags_t complain)
        return true;
 
       ++function_depth;
-      synthesize_method (fn);
+      maybe_synthesize_method (fn);
       --function_depth;
       return !DECL_MAYBE_DELETED (fn);
     }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq11.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq11.C
new file mode 100644 (file)
index 0000000..b71ed4f
--- /dev/null
@@ -0,0 +1,43 @@
+// PR c++/102490
+// { dg-do run { target c++20 } }
+
+struct A
+{
+  unsigned char a : 1;
+  unsigned char b : 1;
+  constexpr bool operator== (const A &) const = default;
+};
+
+struct B
+{
+  unsigned char a : 8;
+  int : 0;
+  unsigned char b : 7;
+  constexpr bool operator== (const B &) const = default;
+};
+
+struct C
+{
+  unsigned char a : 3;
+  unsigned char b : 1;
+  constexpr bool operator== (const C &) const = default;
+};
+
+void
+foo (C &x, int y)
+{
+  x.b = y;
+}
+
+int
+main ()
+{
+  A a{}, b{};
+  B c{}, d{};
+  C e{}, f{};
+  a.b = 1;
+  d.b = 1;
+  foo (e, 0);
+  foo (f, 1);
+  return a == b || c == d || e == f;
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq12.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq12.C
new file mode 100644 (file)
index 0000000..8346e7e
--- /dev/null
@@ -0,0 +1,5 @@
+// PR c++/102490
+// { dg-do run { target c++20 } }
+// { dg-options "-O2" }
+
+#include "spaceship-eq11.C"
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq13.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq13.C
new file mode 100644 (file)
index 0000000..cb521ed
--- /dev/null
@@ -0,0 +1,22 @@
+// PR c++/98712
+// { dg-do run { target c++20 } }
+
+struct S
+{
+  int s = 0;
+  S(int s) : s(s) {}
+  bool operator==(const S&) const = default;
+};
+
+struct T : S
+{
+  T(int s) : S(s) {}
+  constexpr bool operator==(const T&) const = default;
+};
+
+int
+main()
+{
+  if (T(0) == T(1))
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth12.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth12.C
new file mode 100644 (file)
index 0000000..85b4784
--- /dev/null
@@ -0,0 +1,24 @@
+// PR c++/98712
+// { dg-do run { target c++20 } }
+
+#include <compare>
+
+struct S
+{
+  int s = 0;
+  S(int s) : s(s) {}
+  auto operator<=>(const S&) const = default;
+};
+
+struct T : S
+{
+  T(int s) : S(s) {}
+  constexpr auto operator<=>(const T&) const = default;
+};
+
+int
+main()
+{
+  if (T(0) >= T(1))
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth13.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth13.C
new file mode 100644 (file)
index 0000000..ab479d7
--- /dev/null
@@ -0,0 +1,29 @@
+// { dg-do compile { target c++20 } }
+
+#include <compare>
+#include <type_traits>
+
+struct E;
+struct D {
+  auto operator<=>(const D&) const = default;
+  float f;
+};
+struct E : D {
+  auto operator<=>(const E&) const = default;
+  int i;
+};
+extern E e1, e2;
+auto a = e1 <=> e2;
+static_assert (std::is_same_v <decltype (a), std::partial_ordering>);
+struct G;
+struct H {
+  constexpr auto operator<=>(const H&) const = default;
+  float f;
+};
+struct G : H {
+  constexpr auto operator<=>(const G&) const = default;
+  int i;
+};
+extern G g1, g2;
+auto c = g1 <=> g2;
+static_assert (std::is_same_v <decltype (c), std::partial_ordering>);
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth14.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth14.C
new file mode 100644 (file)
index 0000000..369d3a3
--- /dev/null
@@ -0,0 +1,26 @@
+// Test virtual <=>.
+// { dg-do run { target c++20 } }
+
+#include <compare>
+
+struct E;
+struct D {
+  std::partial_ordering operator<=>(const D&) const = default;
+  virtual std::partial_ordering operator<=>(const E&) const = 0;
+  float f;
+  D(float f): f(f) {}
+};
+struct E : D {
+  std::partial_ordering operator<=>(const E&) const override = default;
+  int i;
+  E(float f, int i): D(f), i(i) {}
+};
+
+int main()
+{
+  E e1{0.0,42};
+  E e2{1.0,24};
+  auto a = e1 <=> e2;
+  if (!is_lt (e1 <=> e2))
+    __builtin_abort();
+}
index bd1c4d2af2f0b559f485dec7756c3edb5390eb1c..886176522f5cf7aae28b4bc2f38bdf5ed7a1b320 100644 (file)
@@ -1,7 +1,18 @@
 // PR c++/94907
 // { dg-do compile { target c++20 } }
 
-namespace std { struct strong_ordering { }; }
+namespace std { struct strong_ordering {
+  int _v;
+  constexpr strong_ordering (int v) :_v(v) {}
+  constexpr operator int (void) const { return _v; }
+  static const strong_ordering less;
+  static const strong_ordering equal;
+  static const strong_ordering greater;
+};
+constexpr strong_ordering strong_ordering::less = -1;
+constexpr strong_ordering strong_ordering::equal = 0;
+constexpr strong_ordering strong_ordering::greater = 1;
+}
 
 struct E;
 struct D {
This page took 0.108266 seconds and 5 git commands to generate.