On 7/1/26 11:55 AM, Jakub Jelinek wrote:
Hi!

The following patch attempts to implement the C++29 P2953R5
Adding restrictions to defaulted assignment operator functions
paper.
The paper seems to be misnamed to me because it changes the validity
of defaulted constructors as well in some cases.

What the patch does is that it calls maybe_delete_defaulted_fn
also for the FUNCTION_RVALUE_QUALIFIED case for C++29, and then in
maybe_delete_defaulted_fn for C++29 errors rather than making the
function deleted in most cases, with the exception of
[dcl.fct.def.default]/(2.5) case which ought to be still deleted
rather than ill-formed (but only if that is the sole change that
is not on the whitelist of possible differences).

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


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.

if it wasn't explicitly declared, and is not in the C++26
https://eel.is/c++draft/dcl.fct.def.default#2 (2.1) to (2.4)
exception list, so I think https://eel.is/c++draft/dcl.fct.def.default#2.7
should apply and make it ill-formed.  But we don't actually
pass imp_const that would indicate that it should be non-const,
we pass unknown state when it is defaulted outside of class.
And for C++29, I think it should be also ill-formed.

So far tested on make check-g++ with 98,11,14,17,20,23,26,29 modes,
ok for trunk if it passes full bootstrap/regtest?

2026-07-01  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.
        (defaulted_late_check): For C++29 also call
        maybe_delete_defaulted_fn if FUNCTION_RVALUE_QUALIFIED.

        * 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-01 11:54:35.403958883 +0200
+++ gcc/cp/method.cc    2026-07-01 13:12:57.375384759 +0200
@@ -3768,6 +3768,22 @@ maybe_delete_defaulted_fn (tree fn, tree
      = 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));
+  auto bad_xobj_parm = [](tree fn, tree implicit_fn) {

Please move the { to the next line
(https://gcc.gnu.org/codingconventions.html#Lambda_Form).

+    tree fn_parms = TYPE_ARG_TYPES (TREE_TYPE (fn));

This variable could move below the early exit to go with the other variable.

+    if (!DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+      return false;
+
+    tree fn_obj_ref_type = TREE_VALUE (fn_parms);
+    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 true;
+    return false;
+  };
+
    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"  */
@@ -3778,7 +3794,16 @@ maybe_delete_defaulted_fn (tree fn, tree
           || !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))
+             || bad_xobj_parm (fn, implicit_fn)
+             || 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);
@@ -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.

      maybe_delete_defaulted_fn (fn, implicit_fn);
Jason

Reply via email to