https://gcc.gnu.org/g:c1a7d0a2140563198fef1a704029c5a9546e77a0
commit r17-2127-gc1a7d0a2140563198fef1a704029c5a9546e77a0 Author: Jakub Jelinek <[email protected]> Date: Fri Jul 3 20:43:22 2026 +0200 c++: Implement C++29 P2953R5 - Adding restrictions to defaulted assignment operator functions [PR125826] 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. 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. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/method.cc | 82 +++++++++++++--------- gcc/testsuite/g++.dg/cpp0x/defaulted51.C | 2 +- gcc/testsuite/g++.dg/cpp0x/defaulted55.C | 2 +- gcc/testsuite/g++.dg/cpp0x/defaulted56.C | 8 +-- gcc/testsuite/g++.dg/cpp0x/defaulted57.C | 8 +-- gcc/testsuite/g++.dg/cpp0x/defaulted63.C | 20 +++--- gcc/testsuite/g++.dg/cpp0x/defaulted64.C | 4 +- gcc/testsuite/g++.dg/cpp0x/defaulted65.C | 12 ++-- gcc/testsuite/g++.dg/cpp0x/defaulted66.C | 8 ++- gcc/testsuite/g++.dg/cpp0x/defaulted67.C | 6 +- gcc/testsuite/g++.dg/cpp0x/defaulted68.C | 10 +-- gcc/testsuite/g++.dg/cpp1y/defaulted2.C | 5 +- gcc/testsuite/g++.dg/cpp29/defaulted1.C | 113 +++++++++++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp29/defaulted2.C | 15 ++++ gcc/testsuite/g++.dg/cpp29/defaulted3.C | 20 ++++++ gcc/testsuite/g++.dg/cpp29/defaulted4.C | 40 +++++++++++ gcc/testsuite/g++.dg/cpp29/defaulted5.C | 102 ++++++++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp29/defaulted6.C | 33 +++++++++ 18 files changed, 417 insertions(+), 73 deletions(-) diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc index 4c432efb56ae..cec98bdc89b6 100644 --- a/gcc/cp/method.cc +++ b/gcc/cp/method.cc @@ -3753,8 +3753,7 @@ implicitly_declare_fn (special_function_kind kind, tree type, /* 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 implicit_fn) 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 imp_const/*=tristate::unknown()*/) /*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)) { diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted51.C b/gcc/testsuite/g++.dg/cpp0x/defaulted51.C index d81baa0059da..69a91fa2ffc9 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted51.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted51.C @@ -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 diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted55.C b/gcc/testsuite/g++.dg/cpp0x/defaulted55.C index 04cfc1720006..444ac8c89c77 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted55.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted55.C @@ -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; }; diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted56.C b/gcc/testsuite/g++.dg/cpp0x/defaulted56.C index 0e36fd293f61..2cba02a499eb 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted56.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted56.C @@ -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 diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted57.C b/gcc/testsuite/g++.dg/cpp0x/defaulted57.C index feca9662b4a5..7994cba2019b 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted57.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted57.C @@ -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 diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted63.C b/gcc/testsuite/g++.dg/cpp0x/defaulted63.C index 99f92ff69c52..9e9d1052bf3f 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted63.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted63.C @@ -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 } }; diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted64.C b/gcc/testsuite/g++.dg/cpp0x/defaulted64.C index f20030192c37..b2d8325688f3 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted64.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted64.C @@ -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; }; diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted65.C b/gcc/testsuite/g++.dg/cpp0x/defaulted65.C index 88ca1d96084b..aadd33c6d6a9 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted65.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted65.C @@ -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 } }; diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted66.C b/gcc/testsuite/g++.dg/cpp0x/defaulted66.C index 00d3e43e89fe..289bcc18e787 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted66.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted66.C @@ -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 { diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted67.C b/gcc/testsuite/g++.dg/cpp0x/defaulted67.C index a47761c3a265..cbe6e740d5a2 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted67.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted67.C @@ -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 } } }; diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted68.C b/gcc/testsuite/g++.dg/cpp0x/defaulted68.C index c8c1261de425..8ba881e93135 100644 --- a/gcc/testsuite/g++.dg/cpp0x/defaulted68.C +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted68.C @@ -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 } } }; diff --git a/gcc/testsuite/g++.dg/cpp1y/defaulted2.C b/gcc/testsuite/g++.dg/cpp1y/defaulted2.C index 9e5e6a69eead..eb6bed3f220f 100644 --- a/gcc/testsuite/g++.dg/cpp1y/defaulted2.C +++ b/gcc/testsuite/g++.dg/cpp1y/defaulted2.C @@ -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; } diff --git a/gcc/testsuite/g++.dg/cpp29/defaulted1.C b/gcc/testsuite/g++.dg/cpp29/defaulted1.C new file mode 100644 index 000000000000..0be0c0d924b5 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp29/defaulted1.C @@ -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 } diff --git a/gcc/testsuite/g++.dg/cpp29/defaulted2.C b/gcc/testsuite/g++.dg/cpp29/defaulted2.C new file mode 100644 index 000000000000..82d030c46039 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp29/defaulted2.C @@ -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 &>, ""); diff --git a/gcc/testsuite/g++.dg/cpp29/defaulted3.C b/gcc/testsuite/g++.dg/cpp29/defaulted3.C new file mode 100644 index 000000000000..d205acc5e3b2 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp29/defaulted3.C @@ -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; diff --git a/gcc/testsuite/g++.dg/cpp29/defaulted4.C b/gcc/testsuite/g++.dg/cpp29/defaulted4.C new file mode 100644 index 000000000000..ad9a7818ee12 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp29/defaulted4.C @@ -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 } diff --git a/gcc/testsuite/g++.dg/cpp29/defaulted5.C b/gcc/testsuite/g++.dg/cpp29/defaulted5.C new file mode 100644 index 000000000000..d3b4b7d16791 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp29/defaulted5.C @@ -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 } diff --git a/gcc/testsuite/g++.dg/cpp29/defaulted6.C b/gcc/testsuite/g++.dg/cpp29/defaulted6.C new file mode 100644 index 000000000000..99116655e870 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp29/defaulted6.C @@ -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" } +};
