On Wed, Sep 11, 2024 at 01:19:53PM -0400, Jason Merrill wrote:
On 9/11/24 12:54 PM, Marek Polacek wrote:
+ auto_diagnostic_group d;
+ /* We used to emit a hard error, so this uses 0 rather than
+ OPT_Wpedantic. */
+ if (pedwarn (DECL_SOURCE_LOCATION (fn), 0,
+ "defaulted declaration %q+D does not match the "
+ "expected signature", fn))
+ inform (DECL_SOURCE_LOCATION (fn),
+ "expected signature: %qD", implicit_fn);
This should also depend on -Wdefaulted-function-deleted, and set
DECL_DELETED_FN. And the C++20 case should show the expected signature.
Really, the two cases should share the same code, only the diagnostic kind
should change.
How about this?
dg.exp passed; running the full testing.
-- >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): Call delete_defaulted_fn to set
DECL_DELETED_FN.
* cp-tree.h (delete_defaulted_fn): Declare.
* method.cc (delete_defaulted_fn): New.
(defaulted_late_check): Call 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/cpp23/defaulted1.C: New test.
---
gcc/c-family/c.opt | 4 ++
gcc/cp/class.cc | 18 ++++--
gcc/cp/cp-tree.h | 1 +
gcc/cp/method.cc | 77 +++++++++++++++++++++---
gcc/doc/invoke.texi | 9 +++
gcc/testsuite/g++.dg/cpp0x/defaulted15.C | 3 +-
gcc/testsuite/g++.dg/cpp0x/defaulted51.C | 3 +-
gcc/testsuite/g++.dg/cpp0x/defaulted52.C | 3 +-
gcc/testsuite/g++.dg/cpp0x/defaulted53.C | 3 +-
gcc/testsuite/g++.dg/cpp0x/defaulted54.C | 2 +
gcc/testsuite/g++.dg/cpp0x/defaulted56.C | 6 +-
gcc/testsuite/g++.dg/cpp0x/defaulted57.C | 6 +-
gcc/testsuite/g++.dg/cpp0x/defaulted58.C | 2 +
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/cpp23/defaulted1.C | 23 +++++++
18 files changed, 232 insertions(+), 22 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/cpp23/defaulted1.C
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 491aa02e1a3..f5136fd2341 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -619,6 +619,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..2d85681dc72 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -6490,8 +6490,9 @@ check_bases_and_members (tree t)
&& !DECL_ARTIFICIAL (fn)
&& DECL_DEFAULTED_IN_CLASS_P (fn))
{
+ special_function_kind kind = special_function_p (fn);
/* ...except handle comparisons later, in finish_struct_1. */
- if (special_function_p (fn) == sfk_comparison)
+ if (kind == sfk_comparison)
continue;
int copy = copy_fn_p (fn);
@@ -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);
+ }
}
defaulted_late_check (fn);
}
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 7baa2ccbe1e..5f3ac601f12 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6929,6 +6929,7 @@ 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 maybe_delete_defaulted_fn (tree, tree);
extern void defaulted_late_check (tree);
extern bool defaultable_fn_check (tree);
extern void check_abi_tags (tree);
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 68a776d2c5a..46335ea2f37 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -3503,6 +3503,72 @@ 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 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))));
+
+ 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 (pedwarn_p ? DK_PEDWARN : DK_WARNING,
+ 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. */
@@ -3529,7 +3595,8 @@ defaulted_late_check (tree fn)
bool fn_const_p = (copy_fn_p (fn) == 2);
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. */
@@ -3558,13 +3625,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 b9a86a9a181..ca9e745f62e 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
@@ -4797,6 +4798,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..561f8998177 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted51.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted51.C
@@ -4,7 +4,8 @@
template<int> struct A
{
A();
- A(volatile A&) = default; // { dg-error "defaulted" }
+ A(volatile A&) = default; // { dg-error "defaulted" "" { target c++17_down
} }
+ // { dg-warning "implicitly deleted" "" { target
c++20 } .-1 }
};
struct B
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
b/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
index c617230b493..9c10079362c 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted52.C
@@ -13,7 +13,8 @@ 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 } }
+// { dg-warning "implicitly deleted" "" { target c++20 } .-1 }
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..2ca078acab5 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted54.C
@@ -11,6 +11,8 @@ template<typename T> struct W
{
W();
W(const W&) = default; // { dg-error "binding" }
+// { dg-error "implicitly deleted" "" { target c++17_down } .-1 }
+// { dg-warning "implicitly deleted" "" { target c++20 } .-2 }
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..8cb3e0effb9 100644
--- a/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted58.C
@@ -11,6 +11,8 @@ template<typename T> struct W
{
W() = default;
W& operator=(const W&) = default; // { dg-error "binding" }
+// { dg-error "implicitly deleted" "" { target c++17_down } .-1 }
+// { dg-warning "implicitly deleted" "" { target c++20 } .-2 }
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/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: 670cfd5fe6433ee8f2e86eedb197d2523dbb033b