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