diff mbox series

[v4] c++: deleting explicitly-defaulted functions [PR116162]

Message ID Zuhnb3K3qME7KkKj@redhat.com
State New
Headers show
Series [v4] c++: deleting explicitly-defaulted functions [PR116162] | expand

Commit Message

Marek Polacek Sept. 16, 2024, 5:14 p.m. UTC
On Fri, Sep 13, 2024 at 06:43:06PM +0200, Jason Merrill wrote:
> On 9/12/24 3:12 PM, Marek Polacek wrote:
> > On Wed, Sep 11, 2024 at 10:25:34PM -0400, Jason Merrill wrote:
> > > On 9/11/24 4:08 PM, Marek Polacek wrote:
> > > > @@ -6503,10 +6504,17 @@ check_bases_and_members (tree t)
> > > >    	    bool fn_const_p = (copy == 2);
> > > >    	    if (fn_const_p && !imp_const_p)
> > > > -	      /* If the function is defaulted outside the class, we just
> > > > -		 give the synthesis error.  Core Issue #1331 says this is
> > > > -		 no longer ill-formed, it is defined as deleted instead.  */
> > > > -	      DECL_DELETED_FN (fn) = true;
> > > > +	      {
> > > > +		tree implicit_fn
> > > > +		  = implicitly_declare_fn (kind, DECL_CONTEXT (fn),
> > > > +					   /*const_p=*/false,
> > > > +					   /*pattern_fn=*/NULL_TREE,
> > > > +					   /*inherited_parms=*/NULL_TREE);
> > > > +		/* If the function is defaulted outside the class, we just
> > > > +		   give the synthesis error.  Core Issue #1331 says this is
> > > > +		   no longer ill-formed, it is defined as deleted instead.  */
> > > > +		maybe_delete_defaulted_fn (fn, implicit_fn);
> > > > +	      }
> > > 
> > > Since we're about to call defaulted_late_check anyway, can we remove all the
> > > copy ctor handling here?  I don't want to call implicitly_declare_fn twice
> > > for the same function.
> > 
> > Yeh, I should've done that; it wasn't complicated in the end.
> > 
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > 
> > -- >8 --
> > This PR points out the we're not implementing [dcl.fct.def.default]
> > properly.  Consider e.g.
> > 
> >    struct C {
> >       C(const C&&) = default;
> >    };
> > 
> > where we wrongly emit an error, but the move ctor should be just =deleted.
> > According to [dcl.fct.def.default], if the type of the special member
> > function differs from the type of the corresponding special member function
> > that would have been implicitly declared in a way other than as allowed
> > by 2.1-4, the function is defined as deleted.  There's an exception for
> > assignment operators in which case the program is ill-formed.
> > 
> > clang++ has a warning for when we delete an explicitly-defaulted function
> > so this patch adds it too.
> 
> Does clang warn by default for template instantiations?  I'm concerned about
> getting a bunch of noise warnings, but perhaps I'm overestimating how common
> that would be.

Doesn't look like it.  I've added a DECL_TEMPLATE_INSTANTIATION check.  But
I think we should still emit errors and I suppose pedwarns too.
 
> > +void
> > +maybe_delete_defaulted_fn (tree fn, tree implicit_fn)
> > +{
> > +  if (DECL_ARTIFICIAL (fn) || !DECL_DEFAULTED_IN_CLASS_P (fn))
> > +    return;
> > +
> > +  DECL_DELETED_FN (fn) = true;
> > +
> > +  if (!warn_defaulted_fn_deleted)
> > +    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));
> > +  const bool pedwarn_p
> > +   = (cxx_dialect < cxx20
> > +      /* [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)))
> > +	      /* "or F1's non-object parameter type is not a reference,
> > +		 the program is ill-formed"  */
> > +	      || !TYPE_REF_P (parmtype))));
> 
> I'd keep the cases that are ill-formed in C++20 errors; pedwarn for
> pre-C++20 in the cases that are warnings in C++20.

I'm still a little confused what exactly we want to do here.  Does
this make sense?

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This PR points out the we're not implementing [dcl.fct.def.default]
properly.  Consider e.g.

  struct C {
     C(const C&&) = default;
  };

where we wrongly emit an error, but the move ctor should be just =deleted.
According to [dcl.fct.def.default], if the type of the special member
function differs from the type of the corresponding special member function
that would have been implicitly declared in a way other than as allowed
by 2.1-4, the function is defined as deleted.  There's an exception for
assignment operators in which case the program is ill-formed.

clang++ has a warning for when we delete an explicitly-defaulted function
so this patch adds it too.  I'm also downgrading an error to a pedwarn
in C++17 since the code compiles in C++20.

	PR c++/116162

gcc/c-family/ChangeLog:

	* c.opt (Wdefaulted-function-deleted): New.

gcc/cp/ChangeLog:

	* class.cc (check_bases_and_members): Don't set DECL_DELETED_FN here,
	leave it to defaulted_late_check.
	* cp-tree.h (maybe_delete_defaulted_fn): Declare.
	(defaulted_late_check): Add a tristate parameter.
	* method.cc (maybe_delete_defaulted_fn): New.
	(defaulted_late_check): Add a tristate parameter.  Call
	maybe_delete_defaulted_fn instead of giving an error.

gcc/ChangeLog:

	* doc/invoke.texi: Document -Wdefaulted-function-deleted.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/defaulted15.C: Add dg-warning/dg-error.
	* g++.dg/cpp0x/defaulted51.C: Likewise.
	* g++.dg/cpp0x/defaulted52.C: Likewise.
	* g++.dg/cpp0x/defaulted53.C: Likewise.
	* g++.dg/cpp0x/defaulted54.C: Likewise.
	* g++.dg/cpp0x/defaulted56.C: Likewise.
	* g++.dg/cpp0x/defaulted57.C: Likewise.
	* g++.dg/cpp0x/defaulted58.C: Likewise.
	* g++.dg/cpp0x/defaulted59.C: Likewise.
	* g++.dg/cpp0x/defaulted63.C: New test.
	* g++.dg/cpp0x/defaulted64.C: New test.
	* g++.dg/cpp0x/defaulted65.C: New test.
	* g++.dg/cpp0x/defaulted66.C: New test.
	* g++.dg/cpp23/defaulted1.C: New test.
---
 gcc/c-family/c.opt                       |  4 +
 gcc/cp/class.cc                          | 27 ++-----
 gcc/cp/cp-tree.h                         |  3 +-
 gcc/cp/method.cc                         | 95 +++++++++++++++++++++---
 gcc/doc/invoke.texi                      |  9 +++
 gcc/testsuite/g++.dg/cpp0x/defaulted15.C |  3 +-
 gcc/testsuite/g++.dg/cpp0x/defaulted51.C |  2 +-
 gcc/testsuite/g++.dg/cpp0x/defaulted52.C |  2 +-
 gcc/testsuite/g++.dg/cpp0x/defaulted53.C |  3 +-
 gcc/testsuite/g++.dg/cpp0x/defaulted54.C |  1 +
 gcc/testsuite/g++.dg/cpp0x/defaulted56.C |  6 +-
 gcc/testsuite/g++.dg/cpp0x/defaulted57.C |  6 +-
 gcc/testsuite/g++.dg/cpp0x/defaulted58.C |  1 +
 gcc/testsuite/g++.dg/cpp0x/defaulted59.C |  3 +-
 gcc/testsuite/g++.dg/cpp0x/defaulted63.C | 39 ++++++++++
 gcc/testsuite/g++.dg/cpp0x/defaulted64.C | 27 +++++++
 gcc/testsuite/g++.dg/cpp0x/defaulted65.C | 25 +++++++
 gcc/testsuite/g++.dg/cpp0x/defaulted66.C | 35 +++++++++
 gcc/testsuite/g++.dg/cpp23/defaulted1.C  | 23 ++++++
 19 files changed, 274 insertions(+), 40 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted63.C
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted64.C
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted65.C
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted66.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/defaulted1.C


base-commit: f5448384a2134f32c8733b401440da11bfe69252

Comments

Jason Merrill Sept. 17, 2024, 4:50 p.m. UTC | #1
On 9/16/24 7:14 PM, Marek Polacek wrote:
> On Fri, Sep 13, 2024 at 06:43:06PM +0200, Jason Merrill wrote:
>> On 9/12/24 3:12 PM, Marek Polacek wrote:
>>> On Wed, Sep 11, 2024 at 10:25:34PM -0400, Jason Merrill wrote:
>>>> On 9/11/24 4:08 PM, Marek Polacek wrote:
>>>>> @@ -6503,10 +6504,17 @@ check_bases_and_members (tree t)
>>>>>     	    bool fn_const_p = (copy == 2);
>>>>>     	    if (fn_const_p && !imp_const_p)
>>>>> -	      /* If the function is defaulted outside the class, we just
>>>>> -		 give the synthesis error.  Core Issue #1331 says this is
>>>>> -		 no longer ill-formed, it is defined as deleted instead.  */
>>>>> -	      DECL_DELETED_FN (fn) = true;
>>>>> +	      {
>>>>> +		tree implicit_fn
>>>>> +		  = implicitly_declare_fn (kind, DECL_CONTEXT (fn),
>>>>> +					   /*const_p=*/false,
>>>>> +					   /*pattern_fn=*/NULL_TREE,
>>>>> +					   /*inherited_parms=*/NULL_TREE);
>>>>> +		/* If the function is defaulted outside the class, we just
>>>>> +		   give the synthesis error.  Core Issue #1331 says this is
>>>>> +		   no longer ill-formed, it is defined as deleted instead.  */
>>>>> +		maybe_delete_defaulted_fn (fn, implicit_fn);
>>>>> +	      }
>>>>
>>>> Since we're about to call defaulted_late_check anyway, can we remove all the
>>>> copy ctor handling here?  I don't want to call implicitly_declare_fn twice
>>>> for the same function.
>>>
>>> Yeh, I should've done that; it wasn't complicated in the end.
>>>
>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>
>>> -- >8 --
>>> This PR points out the we're not implementing [dcl.fct.def.default]
>>> properly.  Consider e.g.
>>>
>>>     struct C {
>>>        C(const C&&) = default;
>>>     };
>>>
>>> where we wrongly emit an error, but the move ctor should be just =deleted.
>>> According to [dcl.fct.def.default], if the type of the special member
>>> function differs from the type of the corresponding special member function
>>> that would have been implicitly declared in a way other than as allowed
>>> by 2.1-4, the function is defined as deleted.  There's an exception for
>>> assignment operators in which case the program is ill-formed.
>>>
>>> clang++ has a warning for when we delete an explicitly-defaulted function
>>> so this patch adds it too.
>>
>> Does clang warn by default for template instantiations?  I'm concerned about
>> getting a bunch of noise warnings, but perhaps I'm overestimating how common
>> that would be.
> 
> Doesn't look like it.  I've added a DECL_TEMPLATE_INSTANTIATION check.  But
> I think we should still emit errors and I suppose pedwarns too.

Makes sense.

>>> +void
>>> +maybe_delete_defaulted_fn (tree fn, tree implicit_fn)
>>> +{
>>> +  if (DECL_ARTIFICIAL (fn) || !DECL_DEFAULTED_IN_CLASS_P (fn))
>>> +    return;
>>> +
>>> +  DECL_DELETED_FN (fn) = true;
>>> +
>>> +  if (!warn_defaulted_fn_deleted)
>>> +    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));
>>> +  const bool pedwarn_p
>>> +   = (cxx_dialect < cxx20
>>> +      /* [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)))
>>> +	      /* "or F1's non-object parameter type is not a reference,
>>> +		 the program is ill-formed"  */
>>> +	      || !TYPE_REF_P (parmtype))));
>>
>> I'd keep the cases that are ill-formed in C++20 errors; pedwarn for
>> pre-C++20 in the cases that are warnings in C++20.
> 
> I'm still a little confused what exactly we want to do here.  Does
> this make sense?
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> This PR points out the we're not implementing [dcl.fct.def.default]
> properly.  Consider e.g.
> 
>    struct C {
>       C(const C&&) = default;
>    };
> 
> where we wrongly emit an error, but the move ctor should be just =deleted.
> According to [dcl.fct.def.default], if the type of the special member
> function differs from the type of the corresponding special member function
> that would have been implicitly declared in a way other than as allowed
> by 2.1-4, the function is defined as deleted.  There's an exception for
> assignment operators in which case the program is ill-formed.
> 
> clang++ has a warning for when we delete an explicitly-defaulted function
> so this patch adds it too.  I'm also downgrading an error to a pedwarn
> in C++17 since the code compiles in C++20.
> 
> 	PR c++/116162
> 
> gcc/c-family/ChangeLog:
> 
> 	* c.opt (Wdefaulted-function-deleted): New.
> 
> gcc/cp/ChangeLog:
> 
> 	* class.cc (check_bases_and_members): Don't set DECL_DELETED_FN here,
> 	leave it to defaulted_late_check.
> 	* cp-tree.h (maybe_delete_defaulted_fn): Declare.
> 	(defaulted_late_check): Add a tristate parameter.
> 	* method.cc (maybe_delete_defaulted_fn): New.
> 	(defaulted_late_check): Add a tristate parameter.  Call
> 	maybe_delete_defaulted_fn instead of giving an error.
> 
> gcc/ChangeLog:
> 
> 	* doc/invoke.texi: Document -Wdefaulted-function-deleted.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/defaulted15.C: Add dg-warning/dg-error.
> 	* g++.dg/cpp0x/defaulted51.C: Likewise.
> 	* g++.dg/cpp0x/defaulted52.C: Likewise.
> 	* g++.dg/cpp0x/defaulted53.C: Likewise.
> 	* g++.dg/cpp0x/defaulted54.C: Likewise.
> 	* g++.dg/cpp0x/defaulted56.C: Likewise.
> 	* g++.dg/cpp0x/defaulted57.C: Likewise.
> 	* g++.dg/cpp0x/defaulted58.C: Likewise.
> 	* g++.dg/cpp0x/defaulted59.C: Likewise.
> 	* g++.dg/cpp0x/defaulted63.C: New test.
> 	* g++.dg/cpp0x/defaulted64.C: New test.
> 	* g++.dg/cpp0x/defaulted65.C: New test.
> 	* g++.dg/cpp0x/defaulted66.C: New test.
> 	* g++.dg/cpp23/defaulted1.C: New test.
> ---
>   gcc/c-family/c.opt                       |  4 +
>   gcc/cp/class.cc                          | 27 ++-----
>   gcc/cp/cp-tree.h                         |  3 +-
>   gcc/cp/method.cc                         | 95 +++++++++++++++++++++---
>   gcc/doc/invoke.texi                      |  9 +++
>   gcc/testsuite/g++.dg/cpp0x/defaulted15.C |  3 +-
>   gcc/testsuite/g++.dg/cpp0x/defaulted51.C |  2 +-
>   gcc/testsuite/g++.dg/cpp0x/defaulted52.C |  2 +-
>   gcc/testsuite/g++.dg/cpp0x/defaulted53.C |  3 +-
>   gcc/testsuite/g++.dg/cpp0x/defaulted54.C |  1 +
>   gcc/testsuite/g++.dg/cpp0x/defaulted56.C |  6 +-
>   gcc/testsuite/g++.dg/cpp0x/defaulted57.C |  6 +-
>   gcc/testsuite/g++.dg/cpp0x/defaulted58.C |  1 +
>   gcc/testsuite/g++.dg/cpp0x/defaulted59.C |  3 +-
>   gcc/testsuite/g++.dg/cpp0x/defaulted63.C | 39 ++++++++++
>   gcc/testsuite/g++.dg/cpp0x/defaulted64.C | 27 +++++++
>   gcc/testsuite/g++.dg/cpp0x/defaulted65.C | 25 +++++++
>   gcc/testsuite/g++.dg/cpp0x/defaulted66.C | 35 +++++++++
>   gcc/testsuite/g++.dg/cpp23/defaulted1.C  | 23 ++++++
>   19 files changed, 274 insertions(+), 40 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted63.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted64.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted65.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/defaulted66.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/defaulted1.C
> 
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index ec23249c959..98a35f043c7 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -629,6 +629,10 @@ Wdeclaration-missing-parameter-type
>   C ObjC Var(warn_declaration_missing_parameter) Warning Init(1)
>   Warn for missing parameter types in function declarations.
>   
> +Wdefaulted-function-deleted
> +C++ ObjC++ Var(warn_defaulted_fn_deleted) Init(1) Warning
> +Warn when an explicitly defaulted function is deleted.
> +
>   Wdelete-incomplete
>   C++ ObjC++ Var(warn_delete_incomplete) Init(1) Warning
>   Warn when deleting a pointer to incomplete type.
> diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
> index 950d83b0ea4..646072d4f20 100644
> --- a/gcc/cp/class.cc
> +++ b/gcc/cp/class.cc
> @@ -6488,27 +6488,14 @@ check_bases_and_members (tree t)
>     for (fn = TYPE_FIELDS (t); fn; fn = DECL_CHAIN (fn))
>       if (DECL_DECLARES_FUNCTION_P (fn)
>   	&& !DECL_ARTIFICIAL (fn)
> -	&& DECL_DEFAULTED_IN_CLASS_P (fn))
> -      {
> +	&& DECL_DEFAULTED_IN_CLASS_P (fn)
>   	/* ...except handle comparisons later, in finish_struct_1.  */
> -	if (special_function_p (fn) == sfk_comparison)
> -	  continue;
> -
> -	int copy = copy_fn_p (fn);
> -	if (copy > 0)
> -	  {
> -	    bool imp_const_p
> -	      = (DECL_CONSTRUCTOR_P (fn) ? !cant_have_const_ctor
> -		 : !no_const_asn_ref);
> -	    bool fn_const_p = (copy == 2);
> -
> -	    if (fn_const_p && !imp_const_p)
> -	      /* If the function is defaulted outside the class, we just
> -		 give the synthesis error.  Core Issue #1331 says this is
> -		 no longer ill-formed, it is defined as deleted instead.  */
> -	      DECL_DELETED_FN (fn) = true;
> -	  }
> -	defaulted_late_check (fn);
> +	&& special_function_p (fn) != sfk_comparison)
> +      {
> +	bool imp_const_p
> +	  = (DECL_CONSTRUCTOR_P (fn) ? !cant_have_const_ctor
> +	     : !no_const_asn_ref);
> +	defaulted_late_check (fn, imp_const_p);
>         }
>   
>     if (LAMBDA_TYPE_P (t))
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 7baa2ccbe1e..32252e7f32f 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -6929,7 +6929,8 @@ extern bool type_build_ctor_call		(tree);
>   extern bool type_build_dtor_call		(tree);
>   extern void explain_non_literal_class		(tree);
>   extern void inherit_targ_abi_tags		(tree);
> -extern void defaulted_late_check		(tree);
> +extern void maybe_delete_defaulted_fn		(tree, tree);
> +extern void defaulted_late_check		(tree, tristate = tristate::unknown ());
>   extern bool defaultable_fn_check		(tree);
>   extern void check_abi_tags			(tree);
>   extern tree missing_abi_tags			(tree);
> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> index 21c06c744c9..0771e508806 100644
> --- a/gcc/cp/method.cc
> +++ b/gcc/cp/method.cc
> @@ -3509,11 +3509,86 @@ implicitly_declare_fn (special_function_kind kind, tree type,
>     return fn;
>   }
>   
> +/* Mark an explicitly defaulted function FN as =deleted and warn.
> +   IMPLICIT_FN is the corresponding special member function that
> +   would have been implicitly declared.  */
> +
> +void
> +maybe_delete_defaulted_fn (tree fn, tree implicit_fn)
> +{
> +  if (DECL_ARTIFICIAL (fn) || !DECL_DEFAULTED_IN_CLASS_P (fn))
> +    return;
> +
> +  DECL_DELETED_FN (fn) = true;
> +
> +  if (!warn_defaulted_fn_deleted)
> +    return;

The flag shouldn't affect the error cases; I'd drop this check.

> +  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));
> +  const bool illformed_p
> +    /* [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)))
> +	   /* "or F1's non-object parameter type is not a reference,
> +	      the program is ill-formed"  */
> +	   || !TYPE_REF_P (parmtype)));
> +  /* Decide if we want to emit a pedwarn, error, or a warning.  */
> +  diagnostic_t diag_kind;
> +  if (cxx_dialect >= cxx20)
> +    diag_kind = illformed_p ? DK_ERROR : DK_WARNING;
> +  else
> +    diag_kind = DK_PEDWARN;

Error should be errors in all standard modes; it doesn't make sense to 
have a softer diagnostic in an older mode when it's ill-formed in all.

Non-errors should be warnings or pedwarns depending on the standard mode.

> +  /* Don't warn for template instantiations.  */
> +  if (DECL_TEMPLATE_INSTANTIATION (fn) && diag_kind == DK_WARNING)
> +    return;
> +
> +  const char *wmsg;
> +  switch (kind)
> +    {
> +    case sfk_copy_constructor:
> +      wmsg = G_("explicitly defaulted copy constructor is implicitly deleted "
> +		"because its declared type does not match the type of an "
> +		"implicit copy constructor");
> +      break;
> +    case sfk_move_constructor:
> +      wmsg = G_("explicitly defaulted move constructor is implicitly deleted "
> +		"because its declared type does not match the type of an "
> +		"implicit move constructor");
> +      break;
> +    case sfk_copy_assignment:
> +      wmsg = G_("explicitly defaulted copy assignment operator is implicitly "
> +		"deleted because its declared type does not match the type "
> +		"of an implicit copy assignment operator");
> +      break;
> +    case sfk_move_assignment:
> +      wmsg = G_("explicitly defaulted move assignment operator is implicitly "
> +		"deleted because its declared type does not match the type "
> +		"of an implicit move assignment operator");
> +      break;
> +    default:
> +      gcc_unreachable ();
> +    }
> +  if (emit_diagnostic (diag_kind, DECL_SOURCE_LOCATION (fn),
> +		       OPT_Wdefaulted_function_deleted, wmsg))

Let's not pass the OPT when DK_ERROR.

> +    inform (DECL_SOURCE_LOCATION (fn),
> +	    "expected signature: %qD", implicit_fn);
> +}
> +
>   /* Gives any errors about defaulted functions which need to be deferred
> -   until the containing class is complete.  */
> +   until the containing class is complete.  IMP_CONST is false or true
> +   if we are called from check_bases_and_members and signals whether
> +   the implicit function has a non-object parameter of type const C&.  */
>   
>   void
> -defaulted_late_check (tree fn)
> +defaulted_late_check (tree fn, tristate imp_const/*=tristate::unknown()*/)
>   {
>     /* Complain about invalid signature for defaulted fn.  */
>     tree ctx = DECL_CONTEXT (fn);
> @@ -3534,8 +3609,14 @@ defaulted_late_check (tree fn)
>       }
>   
>     bool fn_const_p = (copy_fn_p (fn) == 2);
> +  /* "if F2 has a non-object parameter of type const C&, the corresponding
> +     non-object parameter of F1 may be of type C&."  But not the other way
> +     around.  */
> +  if (fn_const_p && imp_const.is_false ())
> +    fn_const_p = false;
>     tree implicit_fn = implicitly_declare_fn (kind, ctx, fn_const_p,
> -					    NULL, NULL);
> +					    /*pattern_fn=*/NULL_TREE,
> +					    /*inherited_parms=*/NULL_TREE);
>     tree eh_spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (implicit_fn));
>   
>     /* Includes special handling for a default xobj operator.  */
> @@ -3564,13 +3645,7 @@ defaulted_late_check (tree fn)
>     if (!same_type_p (TREE_TYPE (TREE_TYPE (fn)),
>   		    TREE_TYPE (TREE_TYPE (implicit_fn)))
>         || !compare_fn_params (fn, implicit_fn))
> -    {
> -      auto_diagnostic_group d;
> -      error ("defaulted declaration %q+D does not match the "
> -	     "expected signature", fn);
> -      inform (DECL_SOURCE_LOCATION (fn),
> -	      "expected signature: %qD", implicit_fn);
> -    }
> +    maybe_delete_defaulted_fn (fn, implicit_fn);
>   
>     if (DECL_DELETED_FN (implicit_fn))
>       {
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 511b8c8d311..1d8edc3e485 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -251,6 +251,7 @@ in the following sections.
>   -Wcomma-subscript  -Wconditionally-supported
>   -Wno-conversion-null  -Wctad-maybe-unsupported
>   -Wctor-dtor-privacy  -Wdangling-reference
> +-Wno-defaulted-function-deleted
>   -Wno-delete-incomplete
>   -Wdelete-non-virtual-dtor  -Wno-deprecated-array-compare
>   -Wdeprecated-copy -Wdeprecated-copy-dtor
> @@ -4798,6 +4799,14 @@ not caught by reference. @option{-Wcatch-value} is enabled by @option{-Wall}.
>   @item -Wconditionally-supported @r{(C++ and Objective-C++ only)}
>   Warn for conditionally-supported (C++11 [intro.defs]) constructs.
>   
> +@opindex Wdefaulted-function-deleted
> +@opindex Wno-defaulted-function-deleted
> +@item -Wno-defaulted-function-deleted @r{(C++ and Objective-C++ only)}
> +Warn when an explicitly defaulted function is deleted by the compiler.
> +That can occur when the function's declared type does not match the type
> +of the function that would have been implicitly declared.  This warning
> +is enabled by default.
> +
>   @opindex Wdelete-incomplete
>   @opindex Wno-delete-incomplete
>   @item -Wno-delete-incomplete @r{(C++ and Objective-C++ only)}
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted15.C b/gcc/testsuite/g++.dg/cpp0x/defaulted15.C
> index 1e0b3545840..6bd02d74dc3 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted15.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted15.C
> @@ -48,7 +48,8 @@ struct F
>   
>   struct G: public F
>   {
> -  G(const G&) = default;
> +  G(const G&) = default;  // { dg-error "implicitly deleted" "" { target c++17_down } }
> +			  // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
>   };
>   
>   struct H
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted51.C b/gcc/testsuite/g++.dg/cpp0x/defaulted51.C
> index 0a7d308707c..d81baa0059d 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" }
> +  A(volatile A&) = default;  // { dg-error "defaulted" "" { target c++17_down } }
>   };
>   
>   struct B
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted52.C b/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
> index c617230b493..e6128f40aeb 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
> @@ -13,7 +13,7 @@ template<typename T> struct W
>   {
>     W();
>     // This should now compile and be =deleted.
> -  W(const W&) = default;
> +  W(const W&) = default; // { dg-error "implicitly deleted" "" { target c++17_down } }
>     T t;
>   };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted53.C b/gcc/testsuite/g++.dg/cpp0x/defaulted53.C
> index 8147e7e2ad1..cc039672277 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted53.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted53.C
> @@ -14,7 +14,8 @@ struct R
>   
>   struct S
>   {
> -  S& operator=(const S&) = default;
> +  S& operator=(const S&) = default; // { dg-error "implicitly deleted" "" { target c++17_down } }
> +				    // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
>     M m;
>   };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted54.C b/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
> index f8ddc4e47ce..04cc0cd7eeb 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
> @@ -11,6 +11,7 @@ template<typename T> struct W
>   {
>     W();
>     W(const W&) = default; // { dg-error "binding" }
> +// { dg-error "implicitly deleted" "" { target c++17_down } .-1 }
>     T t;
>   };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted56.C b/gcc/testsuite/g++.dg/cpp0x/defaulted56.C
> index e7ce12c5566..0e36fd293f6 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted56.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted56.C
> @@ -11,12 +11,14 @@ struct S
>   
>   struct T
>   {
> -  constexpr T(volatile T &) = default; // { dg-error "defaulted" }
> +  constexpr T(volatile T &) = default; // { dg-error "defaulted" "" { target c++17_down } }
> +				       // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
>   };
>   
>   struct U
>   {
> -  constexpr U(const volatile U &) = default; // { dg-error "defaulted" }
> +  constexpr U(const volatile U &) = default; // { dg-error "defaulted" "" { target c++17_down } }
> +					     // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
>   };
>   
>   struct V
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted57.C b/gcc/testsuite/g++.dg/cpp0x/defaulted57.C
> index 37fb7dd6e1d..feca9662b4a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted57.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted57.C
> @@ -11,12 +11,14 @@ struct S
>   
>   struct T
>   {
> -  T& operator=(volatile T &) = default; // { dg-error "defaulted" }
> +  T& operator=(volatile T &) = default; // { dg-error "defaulted" "" { target c++17_down } }
> +					// { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
>   };
>   
>   struct U
>   {
> -  U& operator=(const volatile U &) = default; // { dg-error "defaulted" }
> +  U& operator=(const volatile U &) = default; // { dg-error "defaulted" "" { target c++17_down } }
> +					      // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
>   };
>   
>   struct V
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted58.C b/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
> index 920a4ad0c6d..6ddae46cd63 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
> @@ -11,6 +11,7 @@ template<typename T> struct W
>   {
>     W() = default;
>     W& operator=(const W&) = default; // { dg-error "binding" }
> +// { dg-error "implicitly deleted" "" { target c++17_down } .-1 }
>     T t;
>   };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted59.C b/gcc/testsuite/g++.dg/cpp0x/defaulted59.C
> index 4f871d7f5b1..26510b4ee33 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/defaulted59.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted59.C
> @@ -8,5 +8,6 @@ struct M
>   
>   struct W : public M
>   {
> -  W(const W&) = default;
> +  W(const W&) = default; // { dg-error "implicitly deleted" "" { target c++17_down } }
> +			 // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
>   };
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted63.C b/gcc/testsuite/g++.dg/cpp0x/defaulted63.C
> new file mode 100644
> index 00000000000..99f92ff69c5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted63.C
> @@ -0,0 +1,39 @@
> +// PR c++/116162
> +// { dg-do compile { target c++11 } }
> +
> +struct C0 {
> +  C0(C0&) = default;
> +};
> +
> +struct C1 {
> +  C1(volatile C1&) = default; // { dg-warning "implicitly deleted" "" { target c++20 } }
> +			      // { dg-error "does not match" "" { target c++17_down } .-1 }
> +};
> +
> +struct C2 {
> +  C2(const C2&) = default;
> +};
> +
> +struct C3 {
> +  C3(const volatile C3&) = default;  // { dg-warning "implicitly deleted" "" { target c++20 } }
> +				      // { dg-error "does not match" "" { target c++17_down } .-1 }
> +};
> +
> +struct M0 {
> +  M0(M0&&) = default;
> +};
> +
> +struct M1 {
> +  M1(const M1&&) = default; // { dg-warning "implicitly deleted" "" { target c++20 } }
> +			    // { dg-error "does not match" "" { target c++17_down } .-1 }
> +};
> +
> +struct M2 {
> +  M2(volatile M2&&) = default;	// { dg-warning "implicitly deleted" "" { target c++20 } }
> +				// { dg-error "does not match" "" { target c++17_down } .-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 }
> +};
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted64.C b/gcc/testsuite/g++.dg/cpp0x/defaulted64.C
> new file mode 100644
> index 00000000000..f20030192c3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted64.C
> @@ -0,0 +1,27 @@
> +// PR c++/116162
> +// { dg-do compile { target c++11 } }
> +
> +struct M
> +{
> +  M& operator=(M&&);
> +};
> +
> +struct R
> +{
> +  R& operator=(R&&) = default;
> +  M m;
> +};
> +
> +struct S
> +{
> +  S& operator=(const S&&) = default; // { dg-warning "implicitly deleted" "" { target c++20 } }
> +				     // { dg-error "does not match" "" { target c++17_down } .-1 }
> +
> +  M m;
> +};
> +
> +struct T
> +{
> +  T operator=(T&&) = default; // { dg-error "defaulted" }
> +  M m;
> +};
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted65.C b/gcc/testsuite/g++.dg/cpp0x/defaulted65.C
> new file mode 100644
> index 00000000000..88ca1d96084
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted65.C
> @@ -0,0 +1,25 @@
> +// PR c++/116162
> +// { dg-do compile { target c++11 } }
> +
> +struct S
> +{
> +  S& operator=(S &&) = default;
> +};
> +
> +struct T
> +{
> +  T& operator=(volatile T &&) = default; // { dg-error "defaulted" "" { target c++17_down } }
> +					 // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
> +};
> +
> +struct U
> +{
> +  U& operator=(const volatile U &&) = default; // { dg-error "defaulted" "" { target c++17_down } }
> +					       // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
> +};
> +
> +struct V
> +{
> +  V& operator=(const V &&) = default; // { dg-error "defaulted" "" { target c++17_down } }
> +				      // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
> +};
> diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted66.C b/gcc/testsuite/g++.dg/cpp0x/defaulted66.C
> new file mode 100644
> index 00000000000..00d3e43e89f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/defaulted66.C
> @@ -0,0 +1,35 @@
> +// PR c++/116162
> +// { dg-do compile { target c++11 } }
> +// Check that there is no -Wdefaulted-function-deleted for template
> +// instantiations.
> +
> +template<typename>
> +struct C {
> +   C();
> +   C(const C&&) = default; // { dg-error "implicitly deleted" "" { target c++17_down} }
> +};
> +
> +struct D {
> +  D(const D&&) = default; // { dg-error "implicitly deleted" "" { target c++17_down} }
> +  // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
> +};
> +
> +struct M {
> +  M();
> +  // So that W wouldn't have had "const W&" copy ctor if it were
> +  // implicitly declared.
> +  M(M&);
> +};
> +
> +struct W {
> +   W();
> +   W(const W&) = default; // { dg-error "implicitly deleted" "" { target c++17_down} }
> +   // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
> +   M m;
> +};
> +
> +void
> +g ()
> +{
> +  C<int> c;
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/defaulted1.C b/gcc/testsuite/g++.dg/cpp23/defaulted1.C
> new file mode 100644
> index 00000000000..00cf894fa1d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/defaulted1.C
> @@ -0,0 +1,23 @@
> +// PR c++/116162
> +// { dg-do compile { target c++23 } }
> +
> +struct M
> +{
> +  M& operator=(M&);
> +};
> +
> +struct T
> +{
> +  // if F1 is an assignment operator, and the return type of F1 differs
> +  // from the return type,  the program is ill-formed.
> +  T operator=(this T&, T&) = default; // { dg-error "defaulted" }
> +  M m;
> +};
> +
> +struct U
> +{
> +  // if F1's non-object parameter type is not a reference, the program
> +  // is ill-formed.
> +  U& operator=(this U&, U) = default; // { dg-error "defaulted" }
> +  M m;
> +};
> 
> base-commit: f5448384a2134f32c8733b401440da11bfe69252
diff mbox series

Patch

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index ec23249c959..98a35f043c7 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -629,6 +629,10 @@  Wdeclaration-missing-parameter-type
 C ObjC Var(warn_declaration_missing_parameter) Warning Init(1)
 Warn for missing parameter types in function declarations.
 
+Wdefaulted-function-deleted
+C++ ObjC++ Var(warn_defaulted_fn_deleted) Init(1) Warning
+Warn when an explicitly defaulted function is deleted.
+
 Wdelete-incomplete
 C++ ObjC++ Var(warn_delete_incomplete) Init(1) Warning
 Warn when deleting a pointer to incomplete type.
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index 950d83b0ea4..646072d4f20 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -6488,27 +6488,14 @@  check_bases_and_members (tree t)
   for (fn = TYPE_FIELDS (t); fn; fn = DECL_CHAIN (fn))
     if (DECL_DECLARES_FUNCTION_P (fn)
 	&& !DECL_ARTIFICIAL (fn)
-	&& DECL_DEFAULTED_IN_CLASS_P (fn))
-      {
+	&& DECL_DEFAULTED_IN_CLASS_P (fn)
 	/* ...except handle comparisons later, in finish_struct_1.  */
-	if (special_function_p (fn) == sfk_comparison)
-	  continue;
-
-	int copy = copy_fn_p (fn);
-	if (copy > 0)
-	  {
-	    bool imp_const_p
-	      = (DECL_CONSTRUCTOR_P (fn) ? !cant_have_const_ctor
-		 : !no_const_asn_ref);
-	    bool fn_const_p = (copy == 2);
-
-	    if (fn_const_p && !imp_const_p)
-	      /* If the function is defaulted outside the class, we just
-		 give the synthesis error.  Core Issue #1331 says this is
-		 no longer ill-formed, it is defined as deleted instead.  */
-	      DECL_DELETED_FN (fn) = true;
-	  }
-	defaulted_late_check (fn);
+	&& special_function_p (fn) != sfk_comparison)
+      {
+	bool imp_const_p
+	  = (DECL_CONSTRUCTOR_P (fn) ? !cant_have_const_ctor
+	     : !no_const_asn_ref);
+	defaulted_late_check (fn, imp_const_p);
       }
 
   if (LAMBDA_TYPE_P (t))
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 7baa2ccbe1e..32252e7f32f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6929,7 +6929,8 @@  extern bool type_build_ctor_call		(tree);
 extern bool type_build_dtor_call		(tree);
 extern void explain_non_literal_class		(tree);
 extern void inherit_targ_abi_tags		(tree);
-extern void defaulted_late_check		(tree);
+extern void maybe_delete_defaulted_fn		(tree, tree);
+extern void defaulted_late_check		(tree, tristate = tristate::unknown ());
 extern bool defaultable_fn_check		(tree);
 extern void check_abi_tags			(tree);
 extern tree missing_abi_tags			(tree);
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 21c06c744c9..0771e508806 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -3509,11 +3509,86 @@  implicitly_declare_fn (special_function_kind kind, tree type,
   return fn;
 }
 
+/* Mark an explicitly defaulted function FN as =deleted and warn.
+   IMPLICIT_FN is the corresponding special member function that
+   would have been implicitly declared.  */
+
+void
+maybe_delete_defaulted_fn (tree fn, tree implicit_fn)
+{
+  if (DECL_ARTIFICIAL (fn) || !DECL_DEFAULTED_IN_CLASS_P (fn))
+    return;
+
+  DECL_DELETED_FN (fn) = true;
+
+  if (!warn_defaulted_fn_deleted)
+    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));
+  const bool illformed_p
+    /* [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)))
+	   /* "or F1's non-object parameter type is not a reference,
+	      the program is ill-formed"  */
+	   || !TYPE_REF_P (parmtype)));
+  /* Decide if we want to emit a pedwarn, error, or a warning.  */
+  diagnostic_t diag_kind;
+  if (cxx_dialect >= cxx20)
+    diag_kind = illformed_p ? DK_ERROR : DK_WARNING;
+  else
+    diag_kind = DK_PEDWARN;
+
+  /* Don't warn for template instantiations.  */
+  if (DECL_TEMPLATE_INSTANTIATION (fn) && diag_kind == DK_WARNING)
+    return;
+
+  const char *wmsg;
+  switch (kind)
+    {
+    case sfk_copy_constructor:
+      wmsg = G_("explicitly defaulted copy constructor is implicitly deleted "
+		"because its declared type does not match the type of an "
+		"implicit copy constructor");
+      break;
+    case sfk_move_constructor:
+      wmsg = G_("explicitly defaulted move constructor is implicitly deleted "
+		"because its declared type does not match the type of an "
+		"implicit move constructor");
+      break;
+    case sfk_copy_assignment:
+      wmsg = G_("explicitly defaulted copy assignment operator is implicitly "
+		"deleted because its declared type does not match the type "
+		"of an implicit copy assignment operator");
+      break;
+    case sfk_move_assignment:
+      wmsg = G_("explicitly defaulted move assignment operator is implicitly "
+		"deleted because its declared type does not match the type "
+		"of an implicit move assignment operator");
+      break;
+    default:
+      gcc_unreachable ();
+    }
+  if (emit_diagnostic (diag_kind, DECL_SOURCE_LOCATION (fn),
+		       OPT_Wdefaulted_function_deleted, wmsg))
+    inform (DECL_SOURCE_LOCATION (fn),
+	    "expected signature: %qD", implicit_fn);
+}
+
 /* Gives any errors about defaulted functions which need to be deferred
-   until the containing class is complete.  */
+   until the containing class is complete.  IMP_CONST is false or true
+   if we are called from check_bases_and_members and signals whether
+   the implicit function has a non-object parameter of type const C&.  */
 
 void
-defaulted_late_check (tree fn)
+defaulted_late_check (tree fn, tristate imp_const/*=tristate::unknown()*/)
 {
   /* Complain about invalid signature for defaulted fn.  */
   tree ctx = DECL_CONTEXT (fn);
@@ -3534,8 +3609,14 @@  defaulted_late_check (tree fn)
     }
 
   bool fn_const_p = (copy_fn_p (fn) == 2);
+  /* "if F2 has a non-object parameter of type const C&, the corresponding
+     non-object parameter of F1 may be of type C&."  But not the other way
+     around.  */
+  if (fn_const_p && imp_const.is_false ())
+    fn_const_p = false;
   tree implicit_fn = implicitly_declare_fn (kind, ctx, fn_const_p,
-					    NULL, NULL);
+					    /*pattern_fn=*/NULL_TREE,
+					    /*inherited_parms=*/NULL_TREE);
   tree eh_spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (implicit_fn));
 
   /* Includes special handling for a default xobj operator.  */
@@ -3564,13 +3645,7 @@  defaulted_late_check (tree fn)
   if (!same_type_p (TREE_TYPE (TREE_TYPE (fn)),
 		    TREE_TYPE (TREE_TYPE (implicit_fn)))
       || !compare_fn_params (fn, implicit_fn))
-    {
-      auto_diagnostic_group d;
-      error ("defaulted declaration %q+D does not match the "
-	     "expected signature", fn);
-      inform (DECL_SOURCE_LOCATION (fn),
-	      "expected signature: %qD", implicit_fn);
-    }
+    maybe_delete_defaulted_fn (fn, implicit_fn);
 
   if (DECL_DELETED_FN (implicit_fn))
     {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 511b8c8d311..1d8edc3e485 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -251,6 +251,7 @@  in the following sections.
 -Wcomma-subscript  -Wconditionally-supported
 -Wno-conversion-null  -Wctad-maybe-unsupported
 -Wctor-dtor-privacy  -Wdangling-reference
+-Wno-defaulted-function-deleted
 -Wno-delete-incomplete
 -Wdelete-non-virtual-dtor  -Wno-deprecated-array-compare
 -Wdeprecated-copy -Wdeprecated-copy-dtor
@@ -4798,6 +4799,14 @@  not caught by reference. @option{-Wcatch-value} is enabled by @option{-Wall}.
 @item -Wconditionally-supported @r{(C++ and Objective-C++ only)}
 Warn for conditionally-supported (C++11 [intro.defs]) constructs.
 
+@opindex Wdefaulted-function-deleted
+@opindex Wno-defaulted-function-deleted
+@item -Wno-defaulted-function-deleted @r{(C++ and Objective-C++ only)}
+Warn when an explicitly defaulted function is deleted by the compiler.
+That can occur when the function's declared type does not match the type
+of the function that would have been implicitly declared.  This warning
+is enabled by default.
+
 @opindex Wdelete-incomplete
 @opindex Wno-delete-incomplete
 @item -Wno-delete-incomplete @r{(C++ and Objective-C++ only)}
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted15.C b/gcc/testsuite/g++.dg/cpp0x/defaulted15.C
index 1e0b3545840..6bd02d74dc3 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted15.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted15.C
@@ -48,7 +48,8 @@  struct F
 
 struct G: public F
 {
-  G(const G&) = default;
+  G(const G&) = default;  // { dg-error "implicitly deleted" "" { target c++17_down } }
+			  // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
 };
 
 struct H
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted51.C b/gcc/testsuite/g++.dg/cpp0x/defaulted51.C
index 0a7d308707c..d81baa0059d 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" }
+  A(volatile A&) = default;  // { dg-error "defaulted" "" { target c++17_down } }
 };
 
 struct B
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted52.C b/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
index c617230b493..e6128f40aeb 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
@@ -13,7 +13,7 @@  template<typename T> struct W
 {
   W();
   // This should now compile and be =deleted.
-  W(const W&) = default;
+  W(const W&) = default; // { dg-error "implicitly deleted" "" { target c++17_down } }
   T t;
 };
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted53.C b/gcc/testsuite/g++.dg/cpp0x/defaulted53.C
index 8147e7e2ad1..cc039672277 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted53.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted53.C
@@ -14,7 +14,8 @@  struct R
 
 struct S
 {
-  S& operator=(const S&) = default;
+  S& operator=(const S&) = default; // { dg-error "implicitly deleted" "" { target c++17_down } }
+				    // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
   M m;
 };
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted54.C b/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
index f8ddc4e47ce..04cc0cd7eeb 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
@@ -11,6 +11,7 @@  template<typename T> struct W
 {
   W();
   W(const W&) = default; // { dg-error "binding" }
+// { dg-error "implicitly deleted" "" { target c++17_down } .-1 }
   T t;
 };
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted56.C b/gcc/testsuite/g++.dg/cpp0x/defaulted56.C
index e7ce12c5566..0e36fd293f6 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted56.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted56.C
@@ -11,12 +11,14 @@  struct S
 
 struct T
 {
-  constexpr T(volatile T &) = default; // { dg-error "defaulted" }
+  constexpr T(volatile T &) = default; // { dg-error "defaulted" "" { target c++17_down } }
+				       // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
 };
 
 struct U
 {
-  constexpr U(const volatile U &) = default; // { dg-error "defaulted" }
+  constexpr U(const volatile U &) = default; // { dg-error "defaulted" "" { target c++17_down } }
+					     // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
 };
 
 struct V
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted57.C b/gcc/testsuite/g++.dg/cpp0x/defaulted57.C
index 37fb7dd6e1d..feca9662b4a 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted57.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted57.C
@@ -11,12 +11,14 @@  struct S
 
 struct T
 {
-  T& operator=(volatile T &) = default; // { dg-error "defaulted" }
+  T& operator=(volatile T &) = default; // { dg-error "defaulted" "" { target c++17_down } }
+					// { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
 };
 
 struct U
 {
-  U& operator=(const volatile U &) = default; // { dg-error "defaulted" }
+  U& operator=(const volatile U &) = default; // { dg-error "defaulted" "" { target c++17_down } }
+					      // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
 };
 
 struct V
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted58.C b/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
index 920a4ad0c6d..6ddae46cd63 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
@@ -11,6 +11,7 @@  template<typename T> struct W
 {
   W() = default;
   W& operator=(const W&) = default; // { dg-error "binding" }
+// { dg-error "implicitly deleted" "" { target c++17_down } .-1 }
   T t;
 };
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted59.C b/gcc/testsuite/g++.dg/cpp0x/defaulted59.C
index 4f871d7f5b1..26510b4ee33 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted59.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted59.C
@@ -8,5 +8,6 @@  struct M
 
 struct W : public M
 {
-  W(const W&) = default;
+  W(const W&) = default; // { dg-error "implicitly deleted" "" { target c++17_down } }
+			 // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
 };
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted63.C b/gcc/testsuite/g++.dg/cpp0x/defaulted63.C
new file mode 100644
index 00000000000..99f92ff69c5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted63.C
@@ -0,0 +1,39 @@ 
+// PR c++/116162
+// { dg-do compile { target c++11 } }
+
+struct C0 {
+  C0(C0&) = default;
+};
+
+struct C1 {
+  C1(volatile C1&) = default; // { dg-warning "implicitly deleted" "" { target c++20 } }
+			      // { dg-error "does not match" "" { target c++17_down } .-1 }
+};
+
+struct C2 {
+  C2(const C2&) = default;
+};
+
+struct C3 {
+  C3(const volatile C3&) = default;  // { dg-warning "implicitly deleted" "" { target c++20 } }
+				      // { dg-error "does not match" "" { target c++17_down } .-1 }
+};
+
+struct M0 {
+  M0(M0&&) = default;
+};
+
+struct M1 {
+  M1(const M1&&) = default; // { dg-warning "implicitly deleted" "" { target c++20 } }
+			    // { dg-error "does not match" "" { target c++17_down } .-1 }
+};
+
+struct M2 {
+  M2(volatile M2&&) = default;	// { dg-warning "implicitly deleted" "" { target c++20 } }
+				// { dg-error "does not match" "" { target c++17_down } .-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 }
+};
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted64.C b/gcc/testsuite/g++.dg/cpp0x/defaulted64.C
new file mode 100644
index 00000000000..f20030192c3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted64.C
@@ -0,0 +1,27 @@ 
+// PR c++/116162
+// { dg-do compile { target c++11 } }
+
+struct M
+{
+  M& operator=(M&&);
+};
+
+struct R
+{
+  R& operator=(R&&) = default;
+  M m;
+};
+
+struct S
+{
+  S& operator=(const S&&) = default; // { dg-warning "implicitly deleted" "" { target c++20 } }
+				     // { dg-error "does not match" "" { target c++17_down } .-1 }
+
+  M m;
+};
+
+struct T
+{
+  T operator=(T&&) = default; // { dg-error "defaulted" }
+  M m;
+};
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted65.C b/gcc/testsuite/g++.dg/cpp0x/defaulted65.C
new file mode 100644
index 00000000000..88ca1d96084
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted65.C
@@ -0,0 +1,25 @@ 
+// PR c++/116162
+// { dg-do compile { target c++11 } }
+
+struct S
+{
+  S& operator=(S &&) = default;
+};
+
+struct T
+{
+  T& operator=(volatile T &&) = default; // { dg-error "defaulted" "" { target c++17_down } }
+					 // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
+};
+
+struct U
+{
+  U& operator=(const volatile U &&) = default; // { dg-error "defaulted" "" { target c++17_down } }
+					       // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
+};
+
+struct V
+{
+  V& operator=(const V &&) = default; // { dg-error "defaulted" "" { target c++17_down } }
+				      // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
+};
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted66.C b/gcc/testsuite/g++.dg/cpp0x/defaulted66.C
new file mode 100644
index 00000000000..00d3e43e89f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted66.C
@@ -0,0 +1,35 @@ 
+// PR c++/116162
+// { dg-do compile { target c++11 } }
+// Check that there is no -Wdefaulted-function-deleted for template
+// instantiations.
+
+template<typename>
+struct C {
+   C();
+   C(const C&&) = default; // { dg-error "implicitly deleted" "" { target c++17_down} }
+};
+
+struct D {
+  D(const D&&) = default; // { dg-error "implicitly deleted" "" { target c++17_down} }
+  // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
+};
+
+struct M {
+  M();
+  // So that W wouldn't have had "const W&" copy ctor if it were
+  // implicitly declared.
+  M(M&);
+};
+
+struct W {
+   W();
+   W(const W&) = default; // { dg-error "implicitly deleted" "" { target c++17_down} }
+   // { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
+   M m;
+};
+
+void
+g ()
+{
+  C<int> c;
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/defaulted1.C b/gcc/testsuite/g++.dg/cpp23/defaulted1.C
new file mode 100644
index 00000000000..00cf894fa1d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/defaulted1.C
@@ -0,0 +1,23 @@ 
+// PR c++/116162
+// { dg-do compile { target c++23 } }
+
+struct M
+{
+  M& operator=(M&);
+};
+
+struct T
+{
+  // if F1 is an assignment operator, and the return type of F1 differs
+  // from the return type,  the program is ill-formed.
+  T operator=(this T&, T&) = default; // { dg-error "defaulted" }
+  M m;
+};
+
+struct U
+{
+  // if F1's non-object parameter type is not a reference, the program
+  // is ill-formed.
+  U& operator=(this U&, U) = default; // { dg-error "defaulted" }
+  M m;
+};