]> gcc.gnu.org Git - gcc.git/commitdiff
c++: implement P2468R2, the equality operator you are looking for
authorJason Merrill <jason@redhat.com>
Fri, 4 Nov 2022 19:22:45 +0000 (15:22 -0400)
committerJason Merrill <jason@redhat.com>
Tue, 8 Nov 2022 00:54:04 +0000 (14:54 -1000)
This paper is resolving the problem of well-formed C++17 code becoming
ambiguous in C++20 due to asymmetrical operator== being compared with itself
in reverse.  I had previously implemented a tiebreaker such that if the two
candidates were functions with the same parameter types, we would prefer the
non-reversed candidate.  But the committee went with a different approach:
if there's an operator!= with the same parameter types as the operator==,
don't consider the reversed form of the ==.

So this patch implements that, and changes my old tiebreaker to give a
pedwarn if it is used.  I also noticed that we were giving duplicate errors
for some testcases, and fixed the tourney logic to avoid that.

As a result, a lot of tests of the form

  struct A { bool operator==(const A&); };

need to be fixed to add a const function-cv-qualifier, e.g.

  struct A { bool operator==(const A&) const; };

The committee thought such code ought to be fixed, so breaking it was fine.

18_support/comparisons/algorithms/fallback.cc also breaks with this patch,
because of the similarly asymmetrical

  bool operator==(const S&, S&) { return true; }

As a result, some of the asserts need to be reversed.

The H test in spaceship-eq15.C is specified in the standard to be
well-formed because the op!= in the inline namespace is not found by the
search, but that seems wrong to me.  I've implemented that behavior, but
disabled it for now; if we decide that is the way we want to go, we can just
remove the "0 &&" in add_candidates to enable it.

Co-authored-by: Jakub Jelinek <jakub@redhat.com>
gcc/cp/ChangeLog:

* cp-tree.h (fns_correspond): Declare.
* decl.cc (fns_correspond): New.
* call.cc (add_candidates): Look for op!= matching op==.
(joust): Complain about non-standard reversed tiebreaker.
(tourney): Fix champ_compared_to_predecessor logic.
(build_new_op): Don't complain about error_mark_node not having
'bool' type.
* pt.cc (tsubst_copy_and_build): Don't try to be permissive
when seen_error().

gcc/testsuite/ChangeLog:

* g++.dg/cpp2a/spaceship-eq15.C: New test.
* g++.dg/cpp0x/defaulted3.C: Add const.
* g++.dg/cpp2a/bit-cast7.C: Add const.
* g++.dg/cpp2a/spaceship-rewrite1.C: Expect error.
* g++.dg/cpp2a/spaceship-rewrite5.C: Expect error.
* g++.old-deja/g++.jason/byval2.C: Expect error.
* g++.old-deja/g++.other/overload13.C: Add const.

libstdc++-v3/ChangeLog:

* testsuite/18_support/comparisons/algorithms/fallback.cc: Adjust
asserts.

12 files changed:
gcc/cp/call.cc
gcc/cp/cp-tree.h
gcc/cp/decl.cc
gcc/cp/pt.cc
gcc/testsuite/g++.dg/cpp0x/defaulted3.C
gcc/testsuite/g++.dg/cpp2a/bit-cast7.C
gcc/testsuite/g++.dg/cpp2a/spaceship-eq15.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C
gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite5.C
gcc/testsuite/g++.old-deja/g++.jason/byval2.C
gcc/testsuite/g++.old-deja/g++.other/overload13.C
libstdc++-v3/testsuite/18_support/comparisons/algorithms/fallback.cc

index 2c0fa37f53a969742e839b0ff1111df9722a48e7..492db9b59ad5d4283464fc1e3a2fce854dca2ec4 100644 (file)
@@ -6232,6 +6232,7 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
   bool check_list_ctor = false;
   bool check_converting = false;
   unification_kind_t strict;
+  tree ne_fns = NULL_TREE;
 
   if (!fns)
     return;
@@ -6269,6 +6270,32 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
       ctype = conversion_path ? BINFO_TYPE (conversion_path) : NULL_TREE;
     }
 
+  /* P2468: Check if operator== is a rewrite target with first operand
+     (*args)[0]; for now just do the lookups.  */
+  if ((flags & (LOOKUP_REWRITTEN | LOOKUP_REVERSED))
+      && DECL_OVERLOADED_OPERATOR_IS (fn, EQ_EXPR))
+    {
+      tree ne_name = ovl_op_identifier (false, NE_EXPR);
+      if (DECL_CLASS_SCOPE_P (fn))
+       {
+         ne_fns = lookup_fnfields (TREE_TYPE ((*args)[0]), ne_name,
+                                   1, tf_none);
+         if (ne_fns == error_mark_node || ne_fns == NULL_TREE)
+           ne_fns = NULL_TREE;
+         else
+           ne_fns = BASELINK_FUNCTIONS (ne_fns);
+       }
+      else
+       {
+         tree context = decl_namespace_context (fn);
+         ne_fns = lookup_qualified_name (context, ne_name, LOOK_want::NORMAL,
+                                         /*complain*/false);
+         if (ne_fns == error_mark_node
+             || !is_overloaded_fn (ne_fns))
+           ne_fns = NULL_TREE;
+       }
+    }
+
   if (first_arg)
     non_static_args = args;
   else
@@ -6345,6 +6372,27 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
            continue;
        }
 
+      /* When considering reversed operator==, if there's a corresponding
+        operator!= in the same scope, it's not a rewrite target.  */
+      if (ne_fns)
+       {
+         bool found = false;
+         for (lkp_iterator ne (ne_fns); !found && ne; ++ne)
+           if (0 && !ne.using_p ()
+               && DECL_NAMESPACE_SCOPE_P (fn)
+               && DECL_CONTEXT (*ne) != DECL_CONTEXT (fn))
+             /* ??? This kludge excludes inline namespace members for the H
+                test in spaceship-eq15.C, but I don't see why we would want
+                that behavior.  Asked Core 2022-11-04.  Disabling for now.  */;
+           else if (fns_correspond (fn, *ne))
+             {
+               found = true;
+               break;
+             }
+         if (found)
+           continue;
+       }
+
       if (TREE_CODE (fn) == TEMPLATE_DECL)
        {
          if (!add_template_candidate (candidates,
@@ -6917,10 +6965,12 @@ build_new_op (const op_location_t &loc, enum tree_code code, int flags,
                  gcc_checking_assert (cand->reversed ());
                  gcc_fallthrough ();
                case NE_EXPR:
+                 if (result == error_mark_node)
+                   ;
                  /* 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)
+                 else if (TREE_CODE (TREE_TYPE (result)) != BOOLEAN_TYPE)
                    {
                      if (complain & tf_error)
                        {
@@ -12488,10 +12538,53 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
          if (winner && comp != winner)
            {
              /* Ambiguity between normal and reversed comparison operators
-                with the same parameter types; prefer the normal one.  */
-             if ((cand1->reversed () != cand2->reversed ())
+                with the same parameter types.  P2468 decided not to go with
+                this approach to resolving the ambiguity, so pedwarn.  */
+             if ((complain & tf_warning_or_error)
+                 && (cand1->reversed () != cand2->reversed ())
                  && cand_parms_match (cand1, cand2))
-               return cand1->reversed () ? -1 : 1;
+               {
+                 struct z_candidate *w, *l;
+                 if (cand2->reversed ())
+                   winner = 1, w = cand1, l = cand2;
+                 else
+                   winner = -1, w = cand2, l = cand1;
+                 if (warn)
+                   {
+                     auto_diagnostic_group d;
+                     if (pedwarn (input_location, 0,
+                                  "C++20 says that these are ambiguous, "
+                                  "even though the second is reversed:"))
+                       {
+                         print_z_candidate (input_location,
+                                            N_("candidate 1:"), w);
+                         print_z_candidate (input_location,
+                                            N_("candidate 2:"), l);
+                         if (w->fn == l->fn
+                             && DECL_NONSTATIC_MEMBER_FUNCTION_P (w->fn)
+                             && (type_memfn_quals (TREE_TYPE (w->fn))
+                                 & TYPE_QUAL_CONST) == 0)
+                           {
+                             /* Suggest adding const to
+                                struct A { bool operator==(const A&); }; */
+                             tree parmtype
+                               = FUNCTION_FIRST_USER_PARMTYPE (w->fn);
+                             parmtype = TREE_VALUE (parmtype);
+                             if (TYPE_REF_P (parmtype)
+                                 && TYPE_READONLY (TREE_TYPE (parmtype))
+                                 && (same_type_ignoring_top_level_qualifiers_p
+                                     (TREE_TYPE (parmtype),
+                                      DECL_CONTEXT (w->fn))))
+                               inform (DECL_SOURCE_LOCATION (w->fn),
+                                       "try making the operator a %<const%> "
+                                       "member function");
+                           }
+                       }
+                   }
+                 else
+                   add_warning (w, l);
+                 return winner;
+               }
 
              winner = 0;
              goto tweak;
@@ -12880,7 +12973,7 @@ tourney (struct z_candidate *candidates, tsubst_flags_t complain)
 {
   struct z_candidate *champ = candidates, *challenger;
   int fate;
-  int champ_compared_to_predecessor = 0;
+  struct z_candidate *champ_compared_to_predecessor = nullptr;
 
   /* Walk through the list once, comparing each current champ to the next
      candidate, knocking out a candidate or two with each comparison.  */
@@ -12897,12 +12990,12 @@ tourney (struct z_candidate *candidates, tsubst_flags_t complain)
              champ = challenger->next;
              if (champ == 0)
                return NULL;
-             champ_compared_to_predecessor = 0;
+             champ_compared_to_predecessor = nullptr;
            }
          else
            {
+             champ_compared_to_predecessor = champ;
              champ = challenger;
-             champ_compared_to_predecessor = 1;
            }
 
          challenger = champ->next;
@@ -12914,7 +13007,7 @@ tourney (struct z_candidate *candidates, tsubst_flags_t complain)
 
   for (challenger = candidates;
        challenger != champ
-        && !(champ_compared_to_predecessor && challenger->next == champ);
+        && challenger != champ_compared_to_predecessor;
        challenger = challenger->next)
     {
       fate = joust (champ, challenger, 0, complain);
index d13bb3d4c0eb3d9a6918763946af4f774c3112d2..bbc8be21900438369049807e8f86643e562e5845 100644 (file)
@@ -6820,6 +6820,7 @@ extern void note_break_stmt                       (void);
 extern bool note_iteration_stmt_body_start     (void);
 extern void note_iteration_stmt_body_end       (bool);
 extern void determine_local_discriminator      (tree);
+extern bool fns_correspond                     (tree, tree);
 extern int decls_match                         (tree, tree, bool = true);
 extern bool maybe_version_functions            (tree, tree, bool);
 extern bool merge_default_template_args                (tree, tree, bool);
index 6e98ea35a394b7a96c65f6dedefa08dda1d8187d..890cfcabd35f7339d8fbc0d891873a83e158bdcc 100644 (file)
@@ -980,6 +980,72 @@ function_requirements_equivalent_p (tree newfn, tree oldfn)
   return cp_tree_equal (reqs1, reqs2);
 }
 
+/* Two functions of the same name correspond [basic.scope.scope] if
+
+   + both declare functions with the same non-object-parameter-type-list,
+   equivalent ([temp.over.link]) trailing requires-clauses (if any, except as
+   specified in [temp.friend]), and, if both are non-static members, they have
+   corresponding object parameters, or
+
+   + both declare function templates with equivalent
+   non-object-parameter-type-lists, return types (if any), template-heads, and
+   trailing requires-clauses (if any), and, if both are non-static members,
+   they have corresponding object parameters.
+
+   This is a subset of decls_match: it identifies declarations that cannot be
+   overloaded with one another.  This function does not consider DECL_NAME.  */
+
+bool
+fns_correspond (tree newdecl, tree olddecl)
+{
+  if (TREE_CODE (newdecl) != TREE_CODE (olddecl))
+    return false;
+
+  if (TREE_CODE (newdecl) == TEMPLATE_DECL)
+    {
+      if (!template_heads_equivalent_p (newdecl, olddecl))
+       return 0;
+      newdecl = DECL_TEMPLATE_RESULT (newdecl);
+      olddecl = DECL_TEMPLATE_RESULT (olddecl);
+    }
+
+  tree f1 = TREE_TYPE (newdecl);
+  tree f2 = TREE_TYPE (olddecl);
+
+  int rq1 = type_memfn_rqual (f1);
+  int rq2 = type_memfn_rqual (f2);
+
+  /* If only one is a non-static member function, ignore ref-quals.  */
+  if (TREE_CODE (f1) != TREE_CODE (f2))
+    rq1 = rq2;
+  /* Two non-static member functions have corresponding object parameters if:
+     + exactly one is an implicit object member function with no ref-qualifier
+     and the types of their object parameters ([dcl.fct]), after removing
+     top-level references, are the same, or
+     + their object parameters have the same type.  */
+  /* ??? We treat member functions of different classes as corresponding even
+     though that means the object parameters have different types.  */
+  else if ((rq1 == REF_QUAL_NONE) != (rq2 == REF_QUAL_NONE))
+    rq1 = rq2;
+
+  bool types_match = rq1 == rq2;
+
+  if (types_match)
+    {
+      tree p1 = FUNCTION_FIRST_USER_PARMTYPE (newdecl);
+      tree p2 = FUNCTION_FIRST_USER_PARMTYPE (olddecl);
+      types_match = compparms (p1, p2);
+    }
+
+  /* Two function declarations match if either has a requires-clause
+     then both have a requires-clause and their constraints-expressions
+     are equivalent.  */
+  if (types_match && flag_concepts)
+    types_match = function_requirements_equivalent_p (newdecl, olddecl);
+
+  return types_match;
+}
+
 /* Subroutine of duplicate_decls: return truthvalue of whether
    or not types of these decls match.
 
index c3fc56a13ffc8bccb6965083cf243db2ad18a837..57917de321f09177c7e1d5ca89085b4e1bdadefd 100644 (file)
@@ -20937,8 +20937,9 @@ tsubst_copy_and_build (tree t,
                    /* In a lambda fn, we have to be careful to not
                       introduce new this captures.  Legacy code can't
                       be using lambdas anyway, so it's ok to be
-                      stricter.  Be strict with C++20 template-id ADL too.  */
-                   bool strict = in_lambda || template_id_p;
+                      stricter.  Be strict with C++20 template-id ADL too.
+                      And be strict if we're already failing anyway.  */
+                   bool strict = in_lambda || template_id_p || seen_error();
                    bool diag = true;
                    if (strict)
                      error_at (cp_expr_loc_or_input_loc (t),
index 75e89c8ff0ccc1bd913be7eb68987f579c5eae98..33de973a1faaead9fb724c0c58e1dc36cc4c0b93 100644 (file)
@@ -4,7 +4,7 @@
 template<class T>
 struct A {
   template<class U>
-  bool operator==(const A<U>&) = delete; // { dg-message "declared" }
+  bool operator==(const A<U>&) const = delete; // { dg-message "declared" }
   operator bool () { return true; }
 };
 
index 4a3c68200701b07687addd0fb05a2244ee6999fd..6927db3c961e0236e5c859fd6816ede5785fa983 100644 (file)
@@ -16,7 +16,7 @@ struct J
 struct K
 {
   long int a, b : 11, c;
-  constexpr bool operator == (const K &x)
+  constexpr bool operator == (const K &x) const
   {
     return a == x.a && b == x.b && c == x.c;
   }
@@ -29,7 +29,7 @@ struct L
 struct M
 {
   long long int a, b : 11, c;
-  constexpr bool operator == (const M &x)
+  constexpr bool operator == (const M &x) const
   {
     return a == x.a && b == x.b && c == x.c;
   }
diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq15.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq15.C
new file mode 100644 (file)
index 0000000..dc50956
--- /dev/null
@@ -0,0 +1,208 @@
+// P2468R2 - The Equality Operator You Are Looking For
+// { dg-do compile { target c++20 } }
+
+struct A {
+  bool operator== (const A &) { return true; }
+  bool operator!= (const A &) { return false; }
+};
+bool a = A{} != A{};
+
+template <typename T>
+struct B {
+  bool operator== (const T &) const;
+  bool operator!= (const T &) const;
+};
+struct B1 : B<B1> { };
+bool b1 = B1{} == B1{};
+bool b2 = B1{} != B1{};
+
+template <bool>
+struct C {
+  using C1 = C<true>;
+  using C2 = C<false>;
+  C () = default;
+  C (const C2 &);
+  bool operator== (C1) const;
+  bool operator!= (C1) const;
+};
+using C3 = C<false>;
+bool c = C3{} == C3{};
+
+struct D {
+  D ();
+  D (int *);
+  bool operator== (const D &) const;   // { dg-message "candidate: 'bool D::operator==\\\(const D&\\\) const' \\\(reversed\\\)" }
+  operator int * () const;
+};
+bool d = nullptr != D{};       // { dg-error "ambiguous overload for 'operator!=' in 'nullptr != D\\\(\\\)' \\\(operand types are 'std::nullptr_t' and 'D'\\\)" }
+                               // { dg-message "candidate: 'operator!=\\\(int\\\*, int\\\*\\\)' \\\(built-in\\\)" "" { target *-*-* } .-1 }
+
+using ubool = unsigned char;
+
+struct E {
+  operator bool () const;
+};
+unsigned char operator== (E, E);// { dg-message "candidate: 'unsigned char operator==\\\(E, E\\\)'" }
+                               // { dg-message "no known conversion for argument 1 from 'int' to 'E'" "" { target *-*-* } .-1 }
+unsigned char e = E{} != E{};  // { dg-error "return type of 'unsigned char operator==\\\(E, E\\\)' is not 'bool'" }
+                               // { dg-message "used as rewritten candidate for comparison of 'E' and 'E'" "" { target *-*-* } .-1 }
+
+// F-H are the testcase from [over.match.oper]
+struct F {};
+template <typename T>
+bool operator== (F, T);                // { dg-message "candidate: 'template<class T> bool operator==\\\(F, T\\\)'" }
+                               // { dg-message "template argument deduction/substitution failed:" "" { target *-*-* } .-1 }
+bool f1 = 0 == F ();           // OK, calls reversed ==
+template <typename T>
+bool operator!= (F, T);
+bool f2 = 0 == F ();           // { dg-error "no match for 'operator==' in '0 == F\\\(\\\)' \\\(operand types are 'int' and 'F'\\\)" }
+                               // { dg-message "cannot convert '0' \\\(type 'int'\\\) to type 'F'" "" { target *-*-* } .-1 }
+
+struct G {
+  bool operator== (const G &);
+};
+struct G1 : G {
+  G1 ();
+  G1 (G);
+  bool operator!= (const G &);
+};
+bool g1 = G () == G1 ();       // OK, != prevents rewrite
+bool g2 = G1 () == G ();       // { dg-error "ambiguous, even though the second is reversed" }
+
+struct H {};
+template <typename T>
+bool operator== (H, T);
+inline namespace H1 {
+  template <typename T>
+  bool operator!= (H, T);
+}
+// [over.match.oper] currently says that this is OK because the inline
+// namespace isn't searched, but that seems wrong to me, so I'm going to go
+// ahead and search it for now.  Remove the "0 &&" in add_candidates to
+// change this to the currently specified behavior.
+// { dg-error "no match" "" { target *-*-* } .+1 }
+bool h = 0 == H ();            // OK, calls reversed ==
+
+template <class T>
+struct I {
+  int operator== (const double &) const;
+  friend inline int operator== (const double &, const T &) { return 1; }
+};
+struct I1 : I<I1> { };
+bool i = I1{} == 0.;           // { dg-error "return type of 'int operator==\\\(const double&, const I1&\\\)' is not 'bool'" }
+                               // { dg-message "used as rewritten candidate for comparison of 'I1' and 'double'" "" { target *-*-* } .-1 }
+
+struct J {
+  bool operator== (const J &) const;
+  bool operator!= (const J &) const;
+};
+struct J1 : J {
+  J1 (const J &);
+  bool operator== (const J1 &x) const {
+    return static_cast<const J &> (*this) == x;        // { dg-error "ambiguous overload for 'operator==' in '\\\*\\\(const J\\\*\\\)\\\(\\\(const J1\\\*\\\)this\\\) == x' \\\(operand types are 'const J' and 'const J1'\\\)" }
+  }
+};
+
+struct K {
+  bool operator== (const K &);
+};
+bool k = K{} == K{};           // { dg-error "ambiguous, even though the second is reversed" }
+
+struct L {
+  bool operator== (const L &) const;
+};
+bool l = L{} == L{};
+
+struct M {
+  bool operator== (M);
+};
+bool m = M () == M ();
+
+struct N {
+  virtual bool operator== (const N &) const;
+};
+struct N1 : N {
+  bool operator== (const N &) const override;
+};
+bool n = N1 () == N1 ();       // { dg-error "ambiguous, even though the second is reversed" }
+
+struct O {
+  virtual signed char operator== (const O &) const;
+  signed char operator!= (const O &x) const { return !operator== (x); }
+};
+struct O1 : O {
+  signed char operator== (const O &) const override;
+};
+bool o = O1 () != O1 ();
+
+template <class T>
+bool
+foo (T x, T y)
+requires requires { x == y; }
+{
+  return x == y;
+}
+bool b3 = foo (B1 (), B1 ());
+
+struct P {};
+template <typename T, class U = int>
+bool operator== (P, T);
+template <class T>
+bool operator!= (P, T);
+bool p = 0 == P ();
+
+struct Q {};
+template <typename T>
+bool operator== (Q, T);
+template <typename U>
+bool operator!= (Q, U);
+bool q = 0 == Q ();            // { dg-error "" }
+
+struct R {
+  template <typename T>
+  bool operator== (const T &);
+};
+bool r = R () == R ();         // { dg-error "ambiguous, even though the second is reversed" }
+
+struct S {
+  template <typename T>
+  bool operator== (const T &) const;
+  bool operator!= (const S &);
+};
+bool s = S () == S ();
+
+struct T {};
+template <class U = int>
+bool operator== (T, int);
+bool operator!= (T, int);
+bool t = 0 == T ();
+
+struct U {};
+bool operator== (U, int);
+bool u1 = 0 == U ();
+namespace U1 { bool operator!= (U, int); }
+bool u2 = 0 == U ();
+using U1::operator!=;
+bool u3 = 0 == U ();           // { dg-error "" }
+
+struct V {};
+template <typename T>
+bool operator== (V, T);
+bool v1 = 0 == V ();
+namespace V1 { template <typename T> bool operator!= (V, T); }
+bool v2 = 0 == V ();
+using V1::operator!=;
+bool v3 = 0 == V ();           // { dg-error "" }
+
+template <int N>
+struct W {
+bool operator== (int) requires (N == 1);
+bool operator!= (int) requires (N == 2);
+};
+int w = 0 == W<1> ();
+
+struct X {
+  bool operator== (X const &);
+  static bool operator!= (X const &, X const &);       // { dg-error "'static bool X::operator!=\\\(const X&, const X&\\\)' must be either a non-static member function or a non-member function" }
+};
+bool x = X () == X ();         // { dg-error "ambiguous, even though the second is reversed" }
index c4030cd2f4d684693c56e2d8ba5889441b2d8b1b..ebe81e4b5e92b71af3724294c18bd7e557b28663 100644 (file)
@@ -11,5 +11,5 @@ int main()
 {
   A<int> a1;
   A<void> a2;
-  return a1 == a2;
+  return a1 == a2; // { dg-error "ambiguous, even though the second is reversed" }
 }
index d0424377e8e3ac560de7ac7f7cccd2230c6cdf7b..460f6332938fe26c2d630fe1b2e05e2798b7b601 100644 (file)
@@ -12,4 +12,4 @@ struct A {
 
 A<const int> a;
 A<int> b;
-auto c = (a == b);
+auto c = (a == b); // { dg-error "ambiguous, even though the second is reversed" "" { target c++20 } }
index 40bf2a365284f07f32ee5ebbe5524d4abc2d39af..0575109ed1ae9b922b489a07acb803c10535754c 100644 (file)
@@ -18,6 +18,6 @@ inline  char  operator == (const Char    a, const char b) { return 0; }
 
 char mystrcmp(Char s[31], Char t[31])
 {
-  for (; *s == *t; ++s, ++t) if (*s == '\0') return 0;
+  for (; *s == *t; ++s, ++t) if (*s == '\0') return 0; // { dg-error "reversed" "" { target c++20 } }
   return char(*s - *t);
 }
index 54ab404af11f1ba3a1e1953b5636de29e46722d1..f59bd4a49c3078d86acee7959ff354846c3ee9f1 100644 (file)
@@ -2,7 +2,7 @@
 // Origin: Nathan Sidwell <nathan@codesourcery.com>
 
 struct A {
-  bool operator== (A const &);
+  bool operator== (A const &) const;
   operator bool () const;
   operator int * () const;
 };
index 05e1bf7775e6634838a370b99705cc96fbe6870a..8bf78faf232039bff3c1feef4b965439bc074818 100644 (file)
@@ -31,12 +31,12 @@ template<typename T, typename U>
 
 using adl::S;
 
-static_assert( has_strong_order_fallback<S, S> );
+static_assert( has_strong_order_fallback<S, S> );
 static_assert( has_strong_order_fallback<const S, S> );
 static_assert( ! has_strong_order_fallback<const S, const S> );
-static_assert( has_weak_order_fallback<S, S> );
+static_assert( has_weak_order_fallback<S, S> );
 static_assert( has_weak_order_fallback<const S, S> );
 static_assert( ! has_weak_order_fallback<const S, const S> );
-static_assert( has_partial_order_fallback<S, S> );
+static_assert( has_partial_order_fallback<S, S> );
 static_assert( ! has_partial_order_fallback<const S, S> ); // LWG 3465
 static_assert( ! has_partial_order_fallback<const S, const S> );
This page took 0.119611 seconds and 5 git commands to generate.