On Thu, Jul 02, 2026 at 05:14:25PM -0400, Jason Merrill wrote:
> > I've tried to include all the tests I found in the paper (referenced or
> > directly in it) with the exception of the https://gcc.gnu.org/PR86646
> > case, added some further ones and tweaked anything in the existing
> > test that behaves differently for -std=c=+29 with the patch.
> 
> c=+29 typo

Changed in my copy.

> > There is one case in cpp29/defaulted5.C test that I believe is wrong,
> > struct F {
> >    F (F &);
> > };
> > 
> > struct K {
> >    F a;
> >    K &operator= (const K &);
> > };
> > 
> > K &K::operator= (const K &) = default;
> > This is accepted by both g++ and clang++ before C++29 and by
> > g++ even in C++29 with this patch, but I think the implicitly
> > defined copy ctor in that case would be K &operator= (K &);
> 
> It seems like you're conflating copy constructor (in F) and copy assignment
> (in K) here?  If I change the F copy ctor to an assignment operator, I get
> the expected
> 
> > wa.C:10:4: note: ‘K& K::operator=(const K&)’ is implicitly deleted because 
> > the default definition would be ill-formed:
> >    10 | K &K::operator= (const K &) = default;
> >       |    ^
> > wa.C:10:4: error: binding reference of type ‘F&’ to ‘const F’ discards 
> > qualifiers
> > wa.C:2:17: note: initializing argument 1 of ‘F& F::operator=(F&)’
> >     2 |   F& operator= (F &);
> >       |                 ^~~
> 
> so I think the current behavior on that testcase is correct.

You're right.  Added further tests with such an assignment operator.

> > +  tree implicit_parmtype
> > +    = TREE_VALUE (FUNCTION_FIRST_USER_PARMTYPE (implicit_fn));
> > +  auto bad_xobj_parm = [](tree fn, tree implicit_fn) {
> 
> Please move the { to the next line
> (https://gcc.gnu.org/codingconventions.html#Lambda_Form).

Ok (but this is all gone now anyway).
> 
> > +    tree fn_parms = TYPE_ARG_TYPES (TREE_TYPE (fn));
> 
> This variable could move below the early exit to go with the other variable.

> > @@ -3892,7 +3917,8 @@ defaulted_late_check (tree fn, tristate
> >     if (!same_type_p (TREE_TYPE (TREE_TYPE (fn)),
> >                 TREE_TYPE (TREE_TYPE (implicit_fn)))
> > -      || !compare_fn_params (fn, implicit_fn))
> > +      || !compare_fn_params (fn, implicit_fn)
> > +      || (cxx_dialect >= cxx29 && FUNCTION_RVALUE_QUALIFIED (TREE_TYPE 
> > (fn))))
> 
> Not necessary, but I wonder about moving all the checks into
> maybe_delete_defaulted_fn instead of having some here and some there.

Here is a patch which does that.  Alternatively, it could just pass down
an argument which kind of mismatch it is, but because there are several
ones, I guess it is cleaner when all the checks are in
maybe_delete_defaulted_fn.

2026-07-03  Jakub Jelinek  <[email protected]>

        PR c++/125826
        * method.cc: Implement C++29 P2953R5 - Adding restrictions to
        defaulted assignment operator functions.
        (maybe_delete_defaulted_fn): For C++29, error instead of
        deleting always, with the exception of F1 having parmtype
        const C & and F2 having implicit_parmtype C & and no other
        non-permitted changes.  Move checks whether defaulted fn
        should be deleted or ill-formed at all from defaulted_late_check
        to this function.  Also error for C++29 if
        FUNCTION_RVALUE_QUALIFIED.
        (defaulted_late_check): Call maybe_delete_defaulted_fn
        unconditionally.

        * g++.dg/cpp0x/defaulted51.C: Adjust expected diagnostics
        for C++29.
        * g++.dg/cpp0x/defaulted55.C: Likewise.
        * g++.dg/cpp0x/defaulted56.C: Likewise.
        * g++.dg/cpp0x/defaulted57.C: Likewise.
        * g++.dg/cpp0x/defaulted63.C: Likewise.
        * g++.dg/cpp0x/defaulted64.C: Likewise.
        * g++.dg/cpp0x/defaulted65.C: Likewise.
        * g++.dg/cpp0x/defaulted66.C: Likewise.
        * g++.dg/cpp0x/defaulted67.C: Likewise.
        * g++.dg/cpp0x/defaulted68.C: Likewise.
        * g++.dg/cpp1y/defaulted2.C: Likewise.
        * g++.dg/cpp29/defaulted1.C: New test.
        * g++.dg/cpp29/defaulted2.C: New test.
        * g++.dg/cpp29/defaulted3.C: New test.
        * g++.dg/cpp29/defaulted4.C: New test.
        * g++.dg/cpp29/defaulted5.C: New test.
        * g++.dg/cpp29/defaulted6.C: New test.

--- gcc/cp/method.cc.jj 2026-07-02 09:55:05.912561353 +0200
+++ gcc/cp/method.cc    2026-07-03 09:33:00.297535236 +0200
@@ -3753,8 +3753,7 @@ implicitly_declare_fn (special_function_
 /* Maybe mark an explicitly defaulted function FN as =deleted and warn,
    or emit an error, as per [dcl.fct.def.default].
    IMPLICIT_FN is the corresponding special member function that
-   would have been implicitly declared.  We've already compared FN and
-   IMPLICIT_FN and they are not the same.  */
+   would have been implicitly declared.  */
 
 static void
 maybe_delete_defaulted_fn (tree fn, tree implicit_fn)
@@ -3762,23 +3761,68 @@ maybe_delete_defaulted_fn (tree fn, tree
   if (DECL_ARTIFICIAL (fn))
     return;
 
+  /* Includes special handling for a default xobj operator.
+     Returns 2 for xobj parameter mismatch, 1 if parameters are
+     different and 0 if they are the same.  */
+  auto compare_fn_params = [] (tree fn, tree implicit_fn)
+  {
+    tree fn_parms = TYPE_ARG_TYPES (TREE_TYPE (fn));
+    tree implicit_fn_parms = TYPE_ARG_TYPES (TREE_TYPE (implicit_fn));
+
+    if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+      {
+       tree fn_obj_ref_type = TREE_VALUE (fn_parms);
+       /* We can't default xobj operators with an xobj parameter that is not
+          an lvalue reference, even if it would correspond.  */
+       if (!TYPE_REF_P (fn_obj_ref_type)
+           || TYPE_REF_IS_RVALUE (fn_obj_ref_type)
+           || !object_parms_correspond (fn, implicit_fn,
+                                        DECL_CONTEXT (implicit_fn)))
+         return 2;
+       /* We just compared the object parameters, skip over them before
+          passing to compparms.  */
+       fn_parms = TREE_CHAIN (fn_parms);
+       implicit_fn_parms = TREE_CHAIN (implicit_fn_parms);
+      }
+    return compparms (fn_parms, implicit_fn_parms) ? 0 : 1;
+  };
+
+  bool same_ret_type = same_type_p (TREE_TYPE (TREE_TYPE (fn)),
+                                   TREE_TYPE (TREE_TYPE (implicit_fn)));
+  int cmp_params = compare_fn_params (fn, implicit_fn);
+  if (same_ret_type
+      && cmp_params == 0
+      && (cxx_dialect < cxx29 || !FUNCTION_RVALUE_QUALIFIED (TREE_TYPE (fn))))
+    return;
+
   auto_diagnostic_group d;
   const special_function_kind kind = special_function_p (fn);
   tree parmtype
     = TREE_VALUE (DECL_XOBJ_MEMBER_FUNCTION_P (fn)
                  ? TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (fn)))
                  : FUNCTION_FIRST_USER_PARMTYPE (fn));
+  tree implicit_parmtype
+    = TREE_VALUE (FUNCTION_FIRST_USER_PARMTYPE (implicit_fn));
+
   if (/* [dcl.fct.def.default] "if F1 is an assignment operator"...  */
       (SFK_ASSIGN_P (kind)
        /* "and the return type of F1 differs from the return type of F2"  */
-       && (!same_type_p (TREE_TYPE (TREE_TYPE (fn)),
-                        TREE_TYPE (TREE_TYPE (implicit_fn)))
+       && (!same_ret_type
           /* "or F1's non-object parameter type is not a reference,
              the program is ill-formed"  */
           || !TYPE_REF_P (parmtype)))
       /* If F1 is *not* explicitly defaulted on its first declaration, the
         program is ill-formed.  */
-      || !DECL_DEFAULTED_IN_CLASS_P (fn))
+      || !DECL_DEFAULTED_IN_CLASS_P (fn)
+      || (cxx_dialect >= cxx29
+         /* For C++29, the only case which is deleted rather than
+            ill-formed is when F1 has const C & argument and F2 C &
+            and no other non-allowed differences.  */
+         && (FUNCTION_RVALUE_QUALIFIED (TREE_TYPE (fn))
+             || cmp_params == 2
+             || TYPE_REF_IS_RVALUE (parmtype)
+             || TYPE_QUALS (TREE_TYPE (parmtype)) != TYPE_QUAL_CONST
+             || TYPE_QUALS (TREE_TYPE (implicit_parmtype)))))
     {
       error ("defaulted declaration %q+D does not match the expected "
             "signature", fn);
@@ -3867,33 +3911,7 @@ defaulted_late_check (tree fn, tristate
                                            /*inherited_parms=*/NULL_TREE);
   tree eh_spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (implicit_fn));
 
-  /* Includes special handling for a default xobj operator.  */
-  auto compare_fn_params = [](tree fn, tree implicit_fn){
-    tree fn_parms = TYPE_ARG_TYPES (TREE_TYPE (fn));
-    tree implicit_fn_parms = TYPE_ARG_TYPES (TREE_TYPE (implicit_fn));
-
-    if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
-      {
-       tree fn_obj_ref_type = TREE_VALUE (fn_parms);
-       /* We can't default xobj operators with an xobj parameter that is not
-          an lvalue reference, even if it would correspond.  */
-       if (!TYPE_REF_P (fn_obj_ref_type)
-           || TYPE_REF_IS_RVALUE (fn_obj_ref_type)
-           || !object_parms_correspond (fn, implicit_fn,
-                                        DECL_CONTEXT (implicit_fn)))
-         return false;
-       /* We just compared the object parameters, skip over them before
-          passing to compparms.  */
-       fn_parms = TREE_CHAIN (fn_parms);
-       implicit_fn_parms = TREE_CHAIN (implicit_fn_parms);
-      }
-    return compparms (fn_parms, implicit_fn_parms);
-  };
-
-  if (!same_type_p (TREE_TYPE (TREE_TYPE (fn)),
-                   TREE_TYPE (TREE_TYPE (implicit_fn)))
-      || !compare_fn_params (fn, implicit_fn))
-    maybe_delete_defaulted_fn (fn, implicit_fn);
+  maybe_delete_defaulted_fn (fn, implicit_fn);
 
   if (DECL_DELETED_FN (implicit_fn))
     {
--- gcc/testsuite/g++.dg/cpp0x/defaulted51.C.jj 2026-07-02 09:55:05.921561236 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted51.C    2026-07-03 09:16:39.020429028 
+0200
@@ -4,7 +4,7 @@
 template<int> struct A
 {
   A();
-  A(volatile A&) = default;  // { dg-error "defaulted" "" { target c++17_down 
} }
+  A(volatile A&) = default;  // { dg-error "defaulted" "" { target { 
c++17_down || c++29 } } }
 };
 
 struct B
--- gcc/testsuite/g++.dg/cpp0x/defaulted55.C.jj 2026-07-02 09:55:05.921561236 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted55.C    2026-07-03 09:16:39.020641894 
+0200
@@ -12,7 +12,7 @@ template<typename T> struct W
   W();
   W(W&) = default;
   // T1 and T2 may have differing ref-qualifiers (copy assign op).
-  constexpr W& operator=(const W&) && = default; // { dg-error "defaulted" "" 
{ target c++11_down } }
+  constexpr W& operator=(const W&) && = default; // { dg-error "defaulted" "" 
{ target { c++11_down || c++29 } } }
   T t;
 };
 
--- gcc/testsuite/g++.dg/cpp0x/defaulted56.C.jj 2026-07-02 09:55:05.921561236 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted56.C    2026-07-03 09:16:39.020808859 
+0200
@@ -11,14 +11,14 @@ struct S
 
 struct T
 {
-  constexpr T(volatile T &) = default; // { dg-error "defaulted" "" { target 
c++17_down } }
-                                      // { dg-warning "implicitly deleted" "" 
{ target c++20 } .-1 }
+  constexpr T(volatile T &) = default; // { dg-error "defaulted" "" { target { 
c++17_down || c++29 } } }
+                                      // { dg-warning "implicitly deleted" "" 
{ target { c++20 && c++26_down } } .-1 }
 };
 
 struct U
 {
-  constexpr U(const volatile U &) = default; // { dg-error "defaulted" "" { 
target c++17_down } }
-                                            // { dg-warning "implicitly 
deleted" "" { target c++20 } .-1 }
+  constexpr U(const volatile U &) = default; // { dg-error "defaulted" "" { 
target { c++17_down || c++29 } } }
+                                            // { dg-warning "implicitly 
deleted" "" { target { c++20 && c++26_down } } .-1 }
 };
 
 struct V
--- gcc/testsuite/g++.dg/cpp0x/defaulted57.C.jj 2026-07-02 09:55:05.921561236 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted57.C    2026-07-03 09:16:39.020943183 
+0200
@@ -11,14 +11,14 @@ struct S
 
 struct T
 {
-  T& operator=(volatile T &) = default; // { dg-error "defaulted" "" { target 
c++17_down } }
-                                       // { dg-warning "implicitly deleted" "" 
{ target c++20 } .-1 }
+  T& operator=(volatile T &) = default; // { dg-error "defaulted" "" { target 
{ c++17_down || c++29 } } }
+                                       // { dg-warning "implicitly deleted" "" 
{ target { c++20 && c++26_down } } .-1 }
 };
 
 struct U
 {
-  U& operator=(const volatile U &) = default; // { dg-error "defaulted" "" { 
target c++17_down } }
-                                             // { dg-warning "implicitly 
deleted" "" { target c++20 } .-1 }
+  U& operator=(const volatile U &) = default; // { dg-error "defaulted" "" { 
target { c++17_down || c++29 } } }
+                                             // { dg-warning "implicitly 
deleted" "" { target { c++20 && c++26_down } } .-1 }
 };
 
 struct V
--- gcc/testsuite/g++.dg/cpp0x/defaulted63.C.jj 2026-07-02 09:55:05.921561236 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted63.C    2026-07-03 09:16:39.021068012 
+0200
@@ -6,8 +6,8 @@ struct C0 {
 };
 
 struct C1 {
-  C1(volatile C1&) = default; // { dg-warning "implicitly deleted" "" { target 
c++20 } }
-                             // { dg-error "does not match" "" { target 
c++17_down } .-1 }
+  C1(volatile C1&) = default; // { dg-warning "implicitly deleted" "" { target 
{ c++20 && c++26_down } } }
+                             // { dg-error "does not match" "" { target { 
c++17_down || c++29 } } .-1 }
 };
 
 struct C2 {
@@ -15,8 +15,8 @@ struct C2 {
 };
 
 struct C3 {
-  C3(const volatile C3&) = default;  // { dg-warning "implicitly deleted" "" { 
target c++20 } }
-                                     // { dg-error "does not match" "" { 
target c++17_down } .-1 }
+  C3(const volatile C3&) = default;  // { dg-warning "implicitly deleted" "" { 
target { c++20 && c++26_down } } }
+                                     // { dg-error "does not match" "" { 
target { c++17_down || c++29 } } .-1 }
 };
 
 struct M0 {
@@ -24,16 +24,16 @@ struct M0 {
 };
 
 struct M1 {
-  M1(const M1&&) = default; // { dg-warning "implicitly deleted" "" { target 
c++20 } }
-                           // { dg-error "does not match" "" { target 
c++17_down } .-1 }
+  M1(const M1&&) = default; // { dg-warning "implicitly deleted" "" { target { 
c++20 && c++26_down } } }
+                           // { dg-error "does not match" "" { target { 
c++17_down || c++29 } } .-1 }
 };
 
 struct M2 {
-  M2(volatile M2&&) = default; // { dg-warning "implicitly deleted" "" { 
target c++20 } }
-                               // { dg-error "does not match" "" { target 
c++17_down } .-1 }
+  M2(volatile M2&&) = default; // { dg-warning "implicitly deleted" "" { 
target { c++20 && c++26_down } } }
+                               // { dg-error "does not match" "" { target { 
c++17_down || c++29 } } .-1 }
 };
 
 struct M3 {
-  M3(const volatile M3&&) = default;  // { dg-warning "implicitly deleted" "" 
{ target c++20 } }
-                                     // { dg-error "does not match" "" { 
target c++17_down } .-1 }
+  M3(const volatile M3&&) = default;  // { dg-warning "implicitly deleted" "" 
{ target { c++20 && c++26_down } } }
+                                     // { dg-error "does not match" "" { 
target { c++17_down || c++29 } } .-1 }
 };
--- gcc/testsuite/g++.dg/cpp0x/defaulted64.C.jj 2026-07-02 09:55:05.921561236 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted64.C    2026-07-03 09:16:39.021178000 
+0200
@@ -14,8 +14,8 @@ struct R
 
 struct S
 {
-  S& operator=(const S&&) = default; // { dg-warning "implicitly deleted" "" { 
target c++20 } }
-                                    // { dg-error "does not match" "" { target 
c++17_down } .-1 }
+  S& operator=(const S&&) = default; // { dg-warning "implicitly deleted" "" { 
target { c++20 && c++26_down } } }
+                                    // { dg-error "does not match" "" { target 
{ c++17_down || c++29 } } .-1 }
 
   M m;
 };
--- gcc/testsuite/g++.dg/cpp0x/defaulted65.C.jj 2026-07-02 09:55:05.922561222 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted65.C    2026-07-03 09:16:39.021353776 
+0200
@@ -8,18 +8,18 @@ struct S
 
 struct T
 {
-  T& operator=(volatile T &&) = default; // { dg-error "defaulted" "" { target 
c++17_down } }
-                                        // { dg-warning "implicitly deleted" 
"" { target c++20 } .-1 }
+  T& operator=(volatile T &&) = default; // { dg-error "defaulted" "" { target 
{ c++17_down || c++29 } } }
+                                        // { dg-warning "implicitly deleted" 
"" { target { c++20 && c++26_down } } .-1 }
 };
 
 struct U
 {
-  U& operator=(const volatile U &&) = default; // { dg-error "defaulted" "" { 
target c++17_down } }
-                                              // { dg-warning "implicitly 
deleted" "" { target c++20 } .-1 }
+  U& operator=(const volatile U &&) = default; // { dg-error "defaulted" "" { 
target { c++17_down || c++29 } } }
+                                              // { dg-warning "implicitly 
deleted" "" { target { c++20 && c++26_down } } .-1 }
 };
 
 struct V
 {
-  V& operator=(const V &&) = default; // { dg-error "defaulted" "" { target 
c++17_down } }
-                                     // { dg-warning "implicitly deleted" "" { 
target c++20 } .-1 }
+  V& operator=(const V &&) = default; // { dg-error "defaulted" "" { target { 
c++17_down || c++29 } } }
+                                     // { dg-warning "implicitly deleted" "" { 
target { c++20 && c++26_down } } .-1 }
 };
--- gcc/testsuite/g++.dg/cpp0x/defaulted66.C.jj 2026-07-02 09:55:05.922561222 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted66.C    2026-07-03 09:16:39.021516903 
+0200
@@ -6,12 +6,14 @@
 template<typename>
 struct C {
    C();
-   C(const C&&) = default; // { dg-error "implicitly deleted" "" { target 
c++17_down} }
+   C(const C&&) = default; // { dg-error "implicitly deleted" "" { target 
c++17_down } }
+   // { dg-error "does not match the expected signature" "" { target c++29 } 
.-1 }
 };
 
 struct D {
-  D(const D&&) = default; // { dg-error "implicitly deleted" "" { target 
c++17_down} }
-  // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
+  D(const D&&) = default; // { dg-error "implicitly deleted" "" { target 
c++17_down } }
+  // { dg-warning "implicitly deleted" "" { target { c++20 && c++26_down } } 
.-1 }
+  // { dg-error "does not match the expected signature" "" { target c++29 } 
.-2 }
 };
 
 struct M {
--- gcc/testsuite/g++.dg/cpp0x/defaulted67.C.jj 2026-07-02 09:55:05.922561222 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted67.C    2026-07-03 09:16:39.021632391 
+0200
@@ -9,15 +9,15 @@ struct S
 
 struct T
 {
-  T& operator=(volatile T &&) = default;
+  T& operator=(volatile T &&) = default;       // { dg-error "does not match 
the expected signature" "" { target c++29 } }
 };
 
 struct U
 {
-  U& operator=(const volatile U &&) = default;
+  U& operator=(const volatile U &&) = default; // { dg-error "does not match 
the expected signature" "" { target c++29 } }
 };
 
 struct V
 {
-  V& operator=(const V &&) = default;
+  V& operator=(const V &&) = default;          // { dg-error "does not match 
the expected signature" "" { target c++29 } }
 };
--- gcc/testsuite/g++.dg/cpp0x/defaulted68.C.jj 2026-07-02 09:55:05.922561222 
+0200
+++ gcc/testsuite/g++.dg/cpp0x/defaulted68.C    2026-07-03 09:16:39.021757498 
+0200
@@ -7,7 +7,7 @@ struct C0 {
 };
 
 struct C1 {
-  C1(volatile C1&) = default;
+  C1(volatile C1&) = default;          // { dg-error "does not match the 
expected signature" "" { target c++29 } }
 };
 
 struct C2 {
@@ -15,7 +15,7 @@ struct C2 {
 };
 
 struct C3 {
-  C3(const volatile C3&) = default;
+  C3(const volatile C3&) = default;    // { dg-error "does not match the 
expected signature" "" { target c++29 } }
 };
 
 struct M0 {
@@ -23,13 +23,13 @@ struct M0 {
 };
 
 struct M1 {
-  M1(const M1&&) = default;
+  M1(const M1&&) = default;            // { dg-error "does not match the 
expected signature" "" { target c++29 } }
 };
 
 struct M2 {
-  M2(volatile M2&&) = default;
+  M2(volatile M2&&) = default;         // { dg-error "does not match the 
expected signature" "" { target c++29 } }
 };
 
 struct M3 {
-  M3(const volatile M3&&) = default;
+  M3(const volatile M3&&) = default;   // { dg-error "does not match the 
expected signature" "" { target c++29 } }
 };
--- gcc/testsuite/g++.dg/cpp1y/defaulted2.C.jj  2026-07-02 09:55:05.922561222 
+0200
+++ gcc/testsuite/g++.dg/cpp1y/defaulted2.C     2026-07-03 09:16:39.021898004 
+0200
@@ -5,11 +5,12 @@ struct A {
     int i;
     constexpr A(int v) : i(v) {}
     constexpr A(const A&&) = default;  // { dg-error "implicitly deleted" "" { 
target c++17_down } }
-                                      // { dg-warning "implicitly deleted" "" 
{ target c++20 } .-1 }
+                                      // { dg-warning "implicitly deleted" "" 
{ target { c++20 && c++26_down } } .-1 }
+                                      // { dg-error "does not match the 
expected signature" "" { target c++29 } .-2 }
 };
 
 constexpr int f() {
     A a(1);
-    A b = static_cast<const A&&>( a ); // { dg-error "use of deleted function" 
}
+    A b = static_cast<const A&&>( a ); // { dg-error "use of deleted function" 
"" { target c++26_down } }
     return b.i;
 }
--- gcc/testsuite/g++.dg/cpp29/defaulted1.C.jj  2026-07-03 09:16:39.022085110 
+0200
+++ gcc/testsuite/g++.dg/cpp29/defaulted1.C     2026-07-03 09:16:39.022085110 
+0200
@@ -0,0 +1,113 @@
+// P2953R5 - Adding restrictions to defaulted assignment operator functions
+// { dg-do compile { target c++11 } }
+
+// Define std::move.
+namespace std {
+  template <typename T>
+  struct remove_reference
+  { typedef T type; };
+
+  template <typename T>
+  struct remove_reference <T &>
+  { typedef T type; };
+
+  template <typename T>
+  struct remove_reference <T &&>
+  { typedef T type; };
+
+  template <typename T>
+  constexpr typename std::remove_reference <T>::type &&
+  move (T &&t) noexcept
+  { return static_cast <typename std::remove_reference <T>::type &&> (t); }
+}
+
+struct A {
+  A (A &) = default;
+  A &operator= (A &) = default;
+};
+
+void
+foo (A a)
+{
+  a = a;
+  A b = a;
+}
+
+struct B {
+  B (const B &&) = default;            // { dg-error "explicitly defaulted 
move constructor is implicitly deleted because its declared type does not match 
the type of an implicit move constructor" "" { target c++17_down } }
+                                       // { dg-warning "explicitly defaulted 
move constructor is implicitly deleted because its declared type does not match 
the type of an implicit move constructor" "" { target { c++20 && c++26_down } } 
.-1 }
+                                       // { dg-error "defaulted declaration 
'B::B\\\(const B\\\&\\\&\\\)' does not match the expected signature" "" { 
target c++29 } .-2 }
+                                       // { dg-message "note: expected 
signature: 'constexpr B::B\\\(B\\\&\\\&\\\)'" "" { target *-*-* } .-3 }
+  B &operator= (const B &&) = default; // { dg-error "explicitly defaulted 
move assignment operator is implicitly deleted because its declared type does 
not match the type of an implicit move assignment operator" "" { target 
c++17_down } }
+                                       // { dg-warning "explicitly defaulted 
move assignment operator is implicitly deleted because its declared type does 
not match the type of an implicit move assignment operator" "" { target { c++20 
&& c++26_down } } .-1 }
+};                                     // { dg-error "defaulted declaration 
'B\\\& B::operator=\\\(const B\\\&\\\&\\\)' does not match the expected 
signature" "" { target c++29 } .-2 }
+                                       // { dg-message "note: expected 
signature: 'constexpr B\\\& B::operator=\\\(B\\\&\\\&\\\)'" "" { target c++14 } 
.-3 }
+                                       // { dg-message "note: expected 
signature: 'B\\\& B::operator=\\\(B\\\&\\\&\\\)'" "" { target c++11_only } .-4 }
+
+void
+foo (B a)
+{
+  a = std::move (a);   // { dg-error "use of deleted function 'constexpr B\\\& 
B::operator=\\\(const B\\\&\\\&\\\)'" "" { target { c++14 && c++26_down } } }
+                       // { dg-error "use of deleted function 'B\\\& 
B::operator=\\\(const B\\\&\\\&\\\)'" "" { target c++11_only } .-1 }
+  B b = std::move (a); // { dg-error "use of deleted function 'constexpr 
B::B\\\(const B\\\&\\\&\\\)'" "" { target c++26_down } }
+}
+
+struct C {
+  C &operator= (const C &) const = default; // { dg-error "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++17_down } }
+};                                     // { dg-warning "explicitly defaulted 
copy assignment operator is implicitly deleted because its declared type does 
not match the type of an implicit copy assignment operator" "" { target { c++20 
&& c++26_down } } .-1 }
+                                       // { dg-error "defaulted declaration 
'C\\\& C::operator=\\\(const C\\\&\\\) const' does not match the expected 
signature" "" { target c++29 } .-2 }
+                                       // { dg-message "note: expected 
signature: 'constexpr C\\\& C::operator=\\\(const C\\\&\\\)'" "" { target c++14 
} .-3 }
+                                       // { dg-message "note: expected 
signature: 'C\\\& C::operator=\\\(const C\\\&\\\)'" "" { target c++11_only } 
.-4 }
+
+void
+foo (C &)
+{
+  C c;
+  c = c;       // { dg-error "use of deleted function 'constexpr C\\\& 
C::operator=\\\(const C\\\&\\\) const'" "" { target { c++14 && c++26_down } } }
+}              // { dg-error "use of deleted function 'C\\\& 
C::operator=\\\(const C\\\&\\\) const'" "" { target c++11_only } .-1 }
+
+struct D {
+  D &operator= (const D &) && = default; // { dg-error "defaulted declaration 
'D\\\& D::operator=\\\(const D\\\&\\\) \\\&\\\&' does not match the expected 
signature" "" { target c++29 } }
+};             // { dg-message "note: expected signature: 'constexpr D\\\& 
D::operator=\\\(const D\\\&\\\)'" "" { target c++29 } .-1 }
+
+void
+foo (D a)
+{
+  std::move (a) = a;
+}
+
+struct E {
+  E &&operator= (const E &) && = default; // { dg-error "defaulted declaration 
'E\\\&\\\& E::operator=\\\(const E\\\&\\\) \\\&\\\&' does not match the 
expected signature" }
+};                                     // { dg-message "note: expected 
signature: 'constexpr E\\\& E::operator=\\\(const E\\\&\\\)'" "" { target c++14 
} .-1 }
+                                       // { dg-message "note: expected 
signature: 'E\\\& E::operator=\\\(const E\\\&\\\)'" "" { target c++11_only } 
.-2 }
+
+void
+foo (E a)
+{
+  std::move (a) = a;
+}
+
+struct F {        
+  F &operator= (const F &) volatile;   // { dg-message "note: initializing 
argument 1 of 'F\\\& F::operator=\\\(const F\\\&\\\) volatile'" }
+};
+struct G {
+  volatile F f;
+  G &operator= (const G &) = default;  // { dg-error "binding reference of 
type 'const F\\\&' to 'const volatile F' discards qualifiers" }
+}; // { dg-message "note: 'G\\\& G::operator=\\\(const G\\\&\\\)' is 
implicitly deleted because the default definition would be ill-formed:" "" { 
target *-*-* } .-1 }
+
+void
+foo (G &)
+{
+  G a;
+  decltype(a = a) b = a;       // { dg-error "cannot convert 'G' to 'int' in 
initialization" }
+} // { dg-error "use of deleted function 'G\\\& G::operator=\\\(const 
G\\\&\\\)'" "" { target *-*-* } .-1 }
+
+struct H {
+  H &operator= (H &);          // { dg-message "note: initializing argument 1 
of 'H\\\& H::operator=\\\(H\\\&\\\)'" }
+};
+struct I {
+  H h;
+  I &operator= (const I &);
+};
+I &I::operator= (const I &) = default; // { dg-error "binding reference of 
type 'H\\\&' to 'const H' discards qualifiers" }
+// { dg-message "note: 'I\\\& I::operator=\\\(const I\\\&\\\)' is implicitly 
deleted because the default definition would be ill-formed:" "" { target *-*-* 
} .-1 }
--- gcc/testsuite/g++.dg/cpp29/defaulted2.C.jj  2026-07-03 09:16:39.022195882 
+0200
+++ gcc/testsuite/g++.dg/cpp29/defaulted2.C     2026-07-03 09:16:39.022195882 
+0200
@@ -0,0 +1,15 @@
+// P2953R5 - Adding restrictions to defaulted assignment operator functions
+// { dg-do compile { target c++17 } }
+
+#include <type_traits>
+
+struct A {
+  A &operator= (A &);
+};
+struct B {
+  A a;
+  B &operator= (const B &) = default;  // { dg-error "explicitly defaulted 
copy assignment operator is implicitly deleted because its declared type does 
not match the type of an implicit copy assignment operator" "" { target 
c++17_only } }
+};                                     // { dg-warning "explicitly defaulted 
copy assignment operator is implicitly deleted because its declared type does 
not match the type of an implicit copy assignment operator" "" { target c++20 } 
.-1 }
+                                       // { dg-message "note: expected 
signature: 'B\\\& B::operator=\\\(B\\\&\\\)'" "" { target *-*-* } .-2 }
+static_assert (!std::is_assignable_v <B &, const B &>, "");
+static_assert (!std::is_assignable_v <B &, B &>, "");
--- gcc/testsuite/g++.dg/cpp29/defaulted3.C.jj  2026-07-03 09:16:39.022282946 
+0200
+++ gcc/testsuite/g++.dg/cpp29/defaulted3.C     2026-07-03 09:16:39.022282946 
+0200
@@ -0,0 +1,20 @@
+// P2953R5 - Adding restrictions to defaulted assignment operator functions
+// { dg-do compile { target c++11 } }
+
+struct A {
+  A (A &) = default;
+  A &operator= (A &) = default;
+};
+
+template <class T>
+struct B {
+  T b;
+  explicit B ();
+  B (const B &) = default;             // { dg-error "explicitly defaulted 
copy constructor is implicitly deleted because its declared type does not match 
the type of an implicit copy constructor" "" { target c++17_down } }
+                                       // { dg-message "note: expected 
signature: 'constexpr B<A>::B\\\(B<A>\\\&\\\)'" "" { target c++17_down } .-1 }
+  B &operator= (const B &) = default;  // { dg-error "explicitly defaulted 
copy assignment operator is implicitly deleted because its declared type does 
not match the type of an implicit copy assignment operator" "" { target 
c++17_down } }
+                                       // { dg-message "note: expected 
signature: 'constexpr B<A>\\\& B<A>::operator=\\\(B<A>\\\&\\\)'" "" { target { 
c++14 && c++17_down } } .-1 }
+                                       // { dg-message "note: expected 
signature: 'B<A>\\\& B<A>::operator=\\\(B<A>\\\&\\\)'" "" { target c++11_only } 
.-2 }
+};
+
+B <A> c;
--- gcc/testsuite/g++.dg/cpp29/defaulted4.C.jj  2026-07-03 09:16:39.022376429 
+0200
+++ gcc/testsuite/g++.dg/cpp29/defaulted4.C     2026-07-03 09:16:39.022376429 
+0200
@@ -0,0 +1,40 @@
+// P2953R5 - Adding restrictions to defaulted assignment operator functions
+// { dg-do compile { target c++11 } }
+
+struct A {
+  A (A &);
+};
+
+struct B {
+  A a;
+  B (const B &) = default;     // { dg-error "explicitly defaulted copy 
constructor is implicitly deleted because its declared type does not match the 
type of an implicit copy constructor" "" { target c++17_down } }
+// { dg-warning "explicitly defaulted copy constructor is implicitly deleted 
because its declared type does not match the type of an implicit copy 
constructor" "" { target c++20 } .-1 }
+}; // { dg-message "note: expected signature: 'B::B\\\(B\\\&\\\)'" "" { target 
*-*-* } .-2 }
+
+struct C {
+  C (C &);
+};
+
+struct D {
+  C a;
+  D &&operator= (const D &) = default; // { dg-error "defaulted declaration 
'D\\\&\\\& D::operator=\\\(const D\\\&\\\)' does not match the expected 
signature" }
+}; // { dg-message "note: expected signature: 'constexpr D\\\& 
D::operator=\\\(const D\\\&\\\)'" "" { target c++14 } .-1 }
+// { dg-message "note: expected signature: 'D\\\& D::operator=\\\(const 
D\\\&\\\)'" "" { target c++11_only } .-2 }
+
+struct E {
+  E &operator= (const E &) && = default;       // { dg-error "defaulted 
declaration 'E\\\& E::operator=\\\(const E\\\&\\\) \\\&\\\&' does not match the 
expected signature" "" { target c++29 } }
+}; // { dg-message "note: expected signature: 'constexpr E\\\& 
E::operator=\\\(const E\\\&\\\)'" "" { target c++29 } .-1 }
+
+struct F {
+  F &operator= (const F &) const = default;    // { dg-error "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++17_down } }
+// { dg-warning "explicitly defaulted copy assignment operator is implicitly 
deleted because its declared type does not match the type of an implicit copy 
assignment operator" "" { target { c++20 && c++26_down } } .-1 }
+// { dg-error "defaulted declaration 'F\\\& F::operator=\\\(const F\\\&\\\) 
const' does not match the expected signature" "" { target c++29 } .-2 }
+}; // { dg-message "note: expected signature: 'constexpr F\\\& 
F::operator=\\\(const F\\\&\\\)'" "" { target c++14 } .-3 }
+// { dg-message "note: expected signature: 'F\\\& F::operator=\\\(const 
F\\\&\\\)'" "" { target c++11_only } .-4 }
+
+struct G {
+  G &operator= (const G &&) = default; // { dg-error "explicitly defaulted 
move assignment operator is implicitly deleted because its declared type does 
not match the type of an implicit move assignment operator" "" { target 
c++17_down } }
+// { dg-warning "explicitly defaulted move assignment operator is implicitly 
deleted because its declared type does not match the type of an implicit move 
assignment operator" "" { target { c++20 && c++26_down } } .-1 }
+// { dg-error "defaulted declaration 'G\\\& G::operator=\\\(const 
G\\\&\\\&\\\)' does not match the expected signature" "" { target c++29 } .-2 }
+}; // { dg-message "note: expected signature: 'constexpr G\\\& 
G::operator=\\\(G\\\&\\\&\\\)'" "" { target c++14 } .-3 }
+// { dg-message "note: expected signature: 'G\\\& 
G::operator=\\\(G\\\&\\\&\\\)'" "" { target c++11_only } .-4 }
--- gcc/testsuite/g++.dg/cpp29/defaulted5.C.jj  2026-07-03 09:16:39.022472735 
+0200
+++ gcc/testsuite/g++.dg/cpp29/defaulted5.C     2026-07-03 10:12:29.504071695 
+0200
@@ -0,0 +1,102 @@
+// P2953R5 - Adding restrictions to defaulted assignment operator functions
+// { dg-do compile { target c++11 } }
+
+struct A {
+  A &operator= (const A &) & = default;
+};
+
+struct B {
+  B &&operator= (const B &) & = default;       // { dg-error "defaulted 
declaration 'B\\\&\\\& B::operator=\\\(const B\\\&\\\) \\\&' does not match the 
expected signature" }
+};                                             // { dg-message "note: expected 
signature: 'constexpr B\\\& B::operator=\\\(const B\\\&\\\)'" "" { target c++14 
} .-1 }
+                                               // { dg-message "note: expected 
signature: 'B\\\& B::operator=\\\(const B\\\&\\\)'" "" { target c++11_only } 
.-2 }
+
+struct C {
+  C &operator= (const C &) noexcept (true) = default;
+};
+
+struct D {
+  D &operator= (const D &) noexcept (false) = default;
+};
+
+struct E {
+  E &operator= (E &) = default;
+};
+
+struct F {
+  F (F &);
+};
+
+struct G {
+  F a;
+  G &operator= (const G &) & = default;
+};
+
+struct H {
+  F a;
+  H &operator= (const H &) && = default;       // { dg-error "defaulted 
declaration 'H\\\& H::operator=\\\(const H\\\&\\\) \\\&\\\&' does not match the 
expected signature" "" { target c++29 } }
+};                                             // { dg-message "note: expected 
signature: 'constexpr H\\\& H::operator=\\\(const H\\\&\\\)'" "" { target c++29 
} .-1 }
+
+struct I {
+  F a;
+  I &operator= (const I &) const = default;    // { dg-error "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++17_down } }
+};                                             // { dg-message "note: expected 
signature: 'constexpr I\\\& I::operator=\\\(const I\\\&\\\)'" "" { target c++14 
} .-1 }
+                                               // { dg-message "note: expected 
signature: 'I\\\& I::operator=\\\(const I\\\&\\\)'" "" { target c++11_only } 
.-2 }
+                                               // { dg-warning "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target { c++20 && c++26_down } } .-3 }
+                                               // { dg-error "defaulted 
declaration 'I\\\& I::operator=\\\(const I\\\&\\\) const' does not match the 
expected signature" "" { target c++29 } .-4 }
+
+struct J {
+  F a;
+  J &operator= (const J &) volatile = default; // { dg-error "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++17_down } }
+};                                             // { dg-message "note: expected 
signature: 'constexpr J\\\& J::operator=\\\(const J\\\&\\\)'" "" { target c++14 
} .-1 }
+                                               // { dg-message "note: expected 
signature: 'J\\\& J::operator=\\\(const J\\\&\\\)'" "" { target c++11_only } 
.-2 }
+                                               // { dg-warning "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target { c++20 && c++26_down } } .-3 }
+                                               // { dg-error "defaulted 
declaration 'J\\\& J::operator=\\\(const J\\\&\\\) volatile' does not match the 
expected signature" "" { target c++29 } .-4 }
+
+struct K {
+  F a;
+  K &operator= (const K &);
+};
+
+K &K::operator= (const K &) = default;
+
+struct L {
+  L &&operator= (L &) = default;               // { dg-error "defaulted 
declaration 'L\\\&\\\& L::operator=\\\(L\\\&\\\)' does not match the expected 
signature" }
+};                                             // { dg-message "note: expected 
signature: 'constexpr L\\\& L::operator=\\\(L\\\&\\\)'" "" { target c++14 } .-1 
}
+                                               // { dg-message "note: expected 
signature: 'L\\\& L::operator=\\\(L\\\&\\\)'" "" { target c++11_only } .-2 }
+
+struct M {
+  M &operator= (M &);
+};
+
+struct N {
+  M a;
+  N &operator= (const N &) & = default;                // { dg-error 
"explicitly defaulted copy assignment operator is implicitly deleted because 
its declared type does not match the type of an implicit copy assignment 
operator" "" { target c++17_down } }
+};                                             // { dg-message "note: expected 
signature: 'N\\\& N::operator=\\\(N\\\&\\\)'" "" { target *-*-* } .-1 }
+                                               // { dg-warning "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++20 } .-2 }
+
+struct O {
+  M a;
+  O &operator= (const O &) && = default;       // { dg-error "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target { c++17_down } } }
+};                                             // { dg-message "note: expected 
signature: 'O\\\& O::operator=\\\(O\\\&\\\)'" "" { target *-*-* } .-1 }
+                                               // { dg-warning "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target { c++20 && c++26_down } } .-2 }
+                                               // { dg-error "defaulted 
declaration 'O\\\& O::operator=\\\(const O\\\&\\\) \\\&\\\&' does not match the 
expected signature" "" { target c++29 } .-3 }
+
+struct P {
+  M a;
+  P &operator= (const P &) const = default;    // { dg-error "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++17_down } }
+};                                             // { dg-message "note: expected 
signature: 'P\\\& P::operator=\\\(P\\\&\\\)'" "" { target *-*-* } .-1 }
+                                               // { dg-warning "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++20 } .-2 }
+
+struct Q {
+  M a;
+  Q &operator= (const Q &) volatile = default; // { dg-error "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++17_down } }
+};                                             // { dg-message "note: expected 
signature: 'Q\\\& Q::operator=\\\(Q\\\&\\\)'" "" { target *-*-* } .-1 }
+                                               // { dg-warning "explicitly 
defaulted copy assignment operator is implicitly deleted because its declared 
type does not match the type of an implicit copy assignment operator" "" { 
target c++20 } .-2 }
+
+struct R {
+  M a;
+  R &operator= (const R &);
+};
+
+R &R::operator= (const R &) = default;         // { dg-error "binding 
reference of type 'M\\\&' to 'const M' discards qualifiers" }
+                                               // { dg-message "note: 'R\\\& 
R::operator=\\\(const R\\\&\\\)' is implicitly deleted because the default 
definition would be ill-formed:" "" { target *-*-* } .-1 }
--- gcc/testsuite/g++.dg/cpp29/defaulted6.C.jj  2026-07-03 09:16:39.022579237 
+0200
+++ gcc/testsuite/g++.dg/cpp29/defaulted6.C     2026-07-03 09:16:39.022579237 
+0200
@@ -0,0 +1,33 @@
+// P2953R5 - Adding restrictions to defaulted assignment operator functions
+// { dg-do compile { target c++23 } }
+
+struct A {
+  A &operator= (this A &, const A &) = default;
+};
+
+struct D {
+  D &&operator= (this D &, const D &) = default;       // { dg-error 
"defaulted declaration 'D\\\&\\\& D::operator=\\\(this D\\\&, const D\\\&\\\)' 
does not match the expected signature" }
+};                                                     // { dg-message "note: 
expected signature: 'constexpr D\\\& D::operator=\\\(const D\\\&\\\)'" "" { 
target *-*-* } .-1 }
+
+struct E {
+  E &operator= (this E, const E &) = default;          // { dg-error "'E\\\& 
E::operator=\\\(this E, const E\\\&\\\)' cannot be defaulted" }
+};
+
+struct F {
+  F (F &);
+};
+
+struct G {
+  F a;
+  G &operator= (this G &, const G &) = default;
+};
+
+struct H {
+  F a;
+  H &operator= (this H &, H &) = default;
+};
+
+struct I {
+  F a;
+  I &operator= (this I, const I &) = default;          // { dg-error "'I\\\& 
I::operator=\\\(this I, const I\\\&\\\)' cannot be defaulted" }
+};


        Jakub

Reply via email to