This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
C++ PATCH for c++/90537 - Implement P1286R2, Contra CWG DR1778
- From: Marek Polacek <polacek at redhat dot com>
- To: GCC Patches <gcc-patches at gcc dot gnu dot org>, Jason Merrill <jason at redhat dot com>, Nathan Sidwell <nathan at acm dot org>
- Date: Thu, 5 Sep 2019 14:14:35 -0400
- Subject: C++ PATCH for c++/90537 - Implement P1286R2, Contra CWG DR1778
This patch implements C++20 P1286R2, Contra CWG DR1778 as described here
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1286r2.html>.
Essentially, it removes the restriction that the exception specification of
a defaulted special member matches the implicit exception specification.
"Implementing" it means just removing some code (woo!) that was marking
a function DECL_DELETED_FN if the exception-specifications didn't match...
...except there's a snag. We no longer reject
struct A {
A() noexcept(false) = default;
};
because A::A() is no longer deleted. But this assert fails:
static_assert(!noexcept(A()), "");
and it doesn't seem to me it should, but it's sort of Catch 22. The default
constructor *is* trivial as per [class.default.ctor]/3, so trivial_fn_p is
true. When build_over_call sees a default_ctor_p that is trivial_fn_p, it
elides the call and creates a TARGET_EXPR <D, void_cst>. When evaluating
noexcept(A()) expr_noexcept_p gets TARGET_EXPR <D, {}> and since there is
no CALL_EXPR/AGGR_INIT_EXPR that throws, it says "ok, this can't throw".
[except.spec]/6: An expression e is potentially-throwing if
(6.1) e is a function call [...] with a potentially-throwing exception specification, or
(6.2) e implicitly invokes a function [...] that is potentially-throwing, or
...
(6.6) any of the immediate subexpressions of e is potentially-throwing.
which of these apply here, 6.1? But such a TARGET_EXPR can't throw, can it?
I commented out the failing asserts in noexcept55.C.
I'd love to hear your thoughts on this.
Bootstrapped/regtested on x86_64-linux.
2019-09-05 Marek Polacek <polacek@redhat.com>
PR c++/90537 - Implement P1286R2, Contra CWG DR1778.
* cp-tree.h (after_nsdmi_defaulted_late_checks): Remove.
* method.c (maybe_explain_implicit_delete): Don't compare
noexcept-specifiers.
(defaulted_late_check): Likewise.
(after_nsdmi_defaulted_late_checks): Remove function.
* parser.c (unparsed_classes): Remove macro.
(push_unparsed_function_queues): Adjust initializer.
(cp_parser_class_specifier_1): Remove unparsed_classes. Don't call
after_nsdmi_defaulted_late_checks.
* parser.h (cp_unparsed_functions_entry): Remove the member holding
nested classes.
* g++.dg/cpp0x/defaulted23.C: Remove dg-error.
* g++.dg/cpp0x/defaulted43.C: Likewise.
* g++.dg/cpp0x/noexcept55.C: New test.
* g++.dg/cpp0x/noexcept56.C: New test.
* g++.dg/cpp0x/noexcept57.C: New test.
* g++.dg/cpp0x/noexcept58.C: New test.
* g++.dg/cpp0x/noexcept59.C: New test.
* g++.dg/cpp0x/noexcept60.C: New test.
diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h
index 038f58d10f8..165daac4cdc 100644
--- gcc/cp/cp-tree.h
+++ gcc/cp/cp-tree.h
@@ -6698,7 +6698,6 @@ extern tree forward_parm (tree);
extern bool is_trivially_xible (enum tree_code, tree, tree);
extern bool is_xible (enum tree_code, tree, tree);
extern tree get_defaulted_eh_spec (tree, tsubst_flags_t = tf_warning_or_error);
-extern void after_nsdmi_defaulted_late_checks (tree);
extern bool maybe_explain_implicit_delete (tree);
extern void explain_implicit_non_constexpr (tree);
extern void deduce_inheriting_ctor (tree);
diff --git gcc/cp/method.c gcc/cp/method.c
index 53fa85b9790..1cfbb1cec13 100644
--- gcc/cp/method.c
+++ gcc/cp/method.c
@@ -1857,13 +1857,6 @@ maybe_explain_implicit_delete (tree decl)
NULL, NULL, &deleted_p, NULL, true,
&inh, parms);
}
- else if (!comp_except_specs
- (TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)),
- raises, ce_normal))
- inform (DECL_SOURCE_LOCATION (decl), "%q#F is implicitly "
- "deleted because its exception-specification does not "
- "match the implicit exception-specification %qX",
- decl, raises);
else if (flag_checking)
gcc_unreachable ();
@@ -2198,12 +2191,14 @@ defaulted_late_check (tree fn)
return;
}
- /* 8.4.2/2: An explicitly-defaulted function (...) may have an explicit
- exception-specification only if it is compatible (15.4) with the
- exception-specification on the implicit declaration. If a function
- is explicitly defaulted on its first declaration, (...) it is
- implicitly considered to have the same exception-specification as if
- it had been implicitly declared. */
+ /* [dcl.fct.def.default] An explicitly-defaulted function may have differing
+ exception-specifications from the exception-specification on the implicit
+ declaration.
+
+ [except.spec]p3 If a declaration of a function does not have
+ a noexcept-specifier [and] is defaulted on its first declaration, the
+ exception specification is as specified below and no other declaration
+ or that function shall have a noexcept-specifier. */
maybe_instantiate_noexcept (fn);
tree fn_spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn));
if (!fn_spec)
@@ -2211,27 +2206,6 @@ defaulted_late_check (tree fn)
if (DECL_DEFAULTED_IN_CLASS_P (fn))
TREE_TYPE (fn) = build_exception_variant (TREE_TYPE (fn), eh_spec);
}
- else if (UNEVALUATED_NOEXCEPT_SPEC_P (fn_spec))
- /* Equivalent to the implicit spec. */;
- else if (DECL_DEFAULTED_IN_CLASS_P (fn)
- && !CLASSTYPE_TEMPLATE_INSTANTIATION (ctx))
- /* We can't compare an explicit exception-specification on a
- constructor defaulted in the class body to the implicit
- exception-specification until after we've parsed any NSDMI; see
- after_nsdmi_defaulted_late_checks. */;
- else
- {
- tree eh_spec = get_defaulted_eh_spec (fn);
- if (!comp_except_specs (fn_spec, eh_spec, ce_normal))
- {
- if (DECL_DEFAULTED_IN_CLASS_P (fn))
- DECL_DELETED_FN (fn) = true;
- else
- error ("function %q+D defaulted on its redeclaration "
- "with an exception-specification that differs from "
- "the implicit exception-specification %qX", fn, eh_spec);
- }
- }
if (DECL_DEFAULTED_IN_CLASS_P (fn)
&& DECL_DECLARED_CONSTEXPR_P (implicit_fn))
@@ -2258,35 +2232,6 @@ defaulted_late_check (tree fn)
}
}
-/* OK, we've parsed the NSDMI for class T, now we can check any explicit
- exception-specifications on functions defaulted in the class body. */
-
-void
-after_nsdmi_defaulted_late_checks (tree t)
-{
- if (uses_template_parms (t))
- return;
- if (t == error_mark_node)
- return;
- for (tree fn = TYPE_FIELDS (t); fn; fn = DECL_CHAIN (fn))
- if (!DECL_ARTIFICIAL (fn)
- && DECL_DECLARES_FUNCTION_P (fn)
- && DECL_DEFAULTED_IN_CLASS_P (fn))
- {
- tree fn_spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn));
- if (UNEVALUATED_NOEXCEPT_SPEC_P (fn_spec))
- continue;
-
- tree eh_spec = get_defaulted_eh_spec (fn);
- if (eh_spec == error_mark_node)
- continue;
-
- if (!comp_except_specs (TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn)),
- eh_spec, ce_normal))
- DECL_DELETED_FN (fn) = true;
- }
-}
-
/* Returns true iff FN can be explicitly defaulted, and gives any
errors if defaulting FN is ill-formed. */
diff --git gcc/cp/parser.c gcc/cp/parser.c
index baa60b8834e..66b38e30b6a 100644
--- gcc/cp/parser.c
+++ gcc/cp/parser.c
@@ -2003,16 +2003,13 @@ cp_parser_context_new (cp_parser_context* next)
parser->unparsed_queues->last ().funs_with_definitions
#define unparsed_nsdmis \
parser->unparsed_queues->last ().nsdmis
-#define unparsed_classes \
- parser->unparsed_queues->last ().classes
#define unparsed_noexcepts \
parser->unparsed_queues->last ().noexcepts
static void
push_unparsed_function_queues (cp_parser *parser)
{
- cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL,
- NULL };
+ cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL };
vec_safe_push (parser->unparsed_queues, e);
}
@@ -23648,7 +23645,6 @@ cp_parser_class_specifier_1 (cp_parser* parser)
error recovery (c++/71169, c++/71832). */
vec_safe_truncate (unparsed_funs_with_default_args, 0);
vec_safe_truncate (unparsed_nsdmis, 0);
- vec_safe_truncate (unparsed_classes, 0);
vec_safe_truncate (unparsed_funs_with_definitions, 0);
}
@@ -23703,12 +23699,6 @@ cp_parser_class_specifier_1 (cp_parser* parser)
if (pushed_scope)
pop_scope (pushed_scope);
- /* Now do some post-NSDMI bookkeeping. */
- FOR_EACH_VEC_SAFE_ELT (unparsed_classes, ix, class_type)
- after_nsdmi_defaulted_late_checks (class_type);
- vec_safe_truncate (unparsed_classes, 0);
- after_nsdmi_defaulted_late_checks (type);
-
/* If there are noexcept-specifiers that have not yet been processed,
take care of them now. */
class_type = NULL_TREE;
@@ -23779,8 +23769,6 @@ cp_parser_class_specifier_1 (cp_parser* parser)
cp_parser_late_parsing_for_member (parser, decl);
vec_safe_truncate (unparsed_funs_with_definitions, 0);
}
- else
- vec_safe_push (unparsed_classes, type);
/* Put back any saved access checks. */
pop_deferring_access_checks ();
diff --git gcc/cp/parser.h gcc/cp/parser.h
index 2890788f489..089056d7af3 100644
--- gcc/cp/parser.h
+++ gcc/cp/parser.h
@@ -163,10 +163,6 @@ struct GTY(()) cp_unparsed_functions_entry {
FIELD_DECLs appear in this list in declaration order. */
vec<tree, va_gc> *nsdmis;
- /* Nested classes go in this vector, so that we can do some final
- processing after parsing any NSDMIs. */
- vec<tree, va_gc> *classes;
-
/* Functions with noexcept-specifiers that require post-processing. */
vec<tree, va_gc> *noexcepts;
};
diff --git gcc/testsuite/g++.dg/cpp0x/defaulted23.C gcc/testsuite/g++.dg/cpp0x/defaulted23.C
index dfbdd2f2ed1..8609b21a8fe 100644
--- gcc/testsuite/g++.dg/cpp0x/defaulted23.C
+++ gcc/testsuite/g++.dg/cpp0x/defaulted23.C
@@ -10,10 +10,10 @@ A a;
struct B
{
- B() throw (int) = default; // { dg-message "exception-specification" "" { target { ! c++17 } } }
-}; // { dg-error "dynamic exception specification" "" { target c++17 } .-1 }
- // { dg-warning "deprecated" "" { target { ! c++17 } } .-2 }
-B b; // { dg-error "deleted" "" { target { ! c++17 } } }
+ B() throw (int) = default; // { dg-error "dynamic exception specification" "" { target c++17 } }
+}; // { dg-warning "deprecated" "" { target { ! c++17 } } .-1 }
+
+B b;
struct C
{
diff --git gcc/testsuite/g++.dg/cpp0x/defaulted43.C gcc/testsuite/g++.dg/cpp0x/defaulted43.C
index f2846fe390c..0be3e2d0c9d 100644
--- gcc/testsuite/g++.dg/cpp0x/defaulted43.C
+++ gcc/testsuite/g++.dg/cpp0x/defaulted43.C
@@ -17,8 +17,8 @@ struct A
T t;
};
-A::A() noexcept = default; // { dg-error "defaulted" }
-A::~A() noexcept = default; // { dg-error "defaulted" }
+A::A() noexcept = default;
+A::~A() noexcept = default;
struct U
{
@@ -51,10 +51,10 @@ V v;
struct C
{
- C() noexcept = default; // { dg-message "exception-specification" }
- ~C() noexcept = default; // { dg-message "exception-specification" }
+ C() noexcept = default;
+ ~C() noexcept = default;
V v;
};
-C c; // { dg-error "deleted" }
+C c;
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept55.C gcc/testsuite/g++.dg/cpp0x/noexcept55.C
new file mode 100644
index 00000000000..1c36d39bffb
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept55.C
@@ -0,0 +1,64 @@
+// PR c++/90537 - Implement P1286R2, Contra CWG DR1778.
+// { dg-do compile { target c++11 } }
+
+// An explicitly-defaulted function may have a different exception-specification
+// from the exception-specification on an implicit declaration. This didn't
+// compile pre-P1286R2.
+struct A {
+ A() noexcept(false) = default;
+ A(const A&) noexcept(false) = default;
+ A(A&&) noexcept(false) = default;
+ A &operator=(const A&) noexcept(false) = default;
+ A &operator=(A&&) noexcept(false) = default;
+ ~A() noexcept(false) = default;
+};
+
+A a;
+// FIXME The default ctor is trivial_fn_p, so build_over_call elides the call
+// and creates TARGET_EXPR <D, {}>, and expr_noexcept_p thinks it's noexcept.
+#if 0
+static_assert(!noexcept(A()), "");
+static_assert(!noexcept(A(A())), "");
+static_assert(!noexcept(A(a)), "");
+static_assert(!noexcept(A(static_cast<A&&>(a))), "");
+static_assert(!noexcept(a = a), "");
+static_assert(!noexcept(a = A()), "");
+static_assert(!noexcept(a = static_cast<A&&>(a)), "");
+#endif
+
+// This compiled even pre-P1286R2. Make sure it continues to do so.
+struct B {
+ B() noexcept(true) = default;
+ B(const B&) noexcept(true) = default;
+ B(B&&) noexcept(true) = default;
+ B &operator=(const B&) noexcept(true) = default;
+ B &operator=(B&&) noexcept(true) = default;
+ ~B() noexcept(true) = default;
+};
+
+B b;
+static_assert(noexcept(B()), "");
+static_assert(noexcept(B(B())), "");
+static_assert(noexcept(B(b)), "");
+static_assert(noexcept(B(static_cast<B&&>(b))), "");
+static_assert(noexcept(b = b), "");
+static_assert(noexcept(b = B()), "");
+static_assert(noexcept(b = static_cast<B&&>(b)), "");
+
+struct C {
+ C() = default;
+ C(const C&) = default;
+ C(C&&) = default;
+ C &operator=(const C&) = default;
+ C &operator=(C&&) = default;
+ ~C() = default;
+};
+
+C c;
+static_assert(noexcept(C()), "");
+static_assert(noexcept(C(C())), "");
+static_assert(noexcept(C(c)), "");
+static_assert(noexcept(C(static_cast<C&&>(c))), "");
+static_assert(noexcept(c = c), "");
+static_assert(noexcept(c = C()), "");
+static_assert(noexcept(c = static_cast<C&&>(c)), "");
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept56.C gcc/testsuite/g++.dg/cpp0x/noexcept56.C
new file mode 100644
index 00000000000..14fbfbdee51
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept56.C
@@ -0,0 +1,16 @@
+// PR c++/90537 - Implement P1286R2, Contra CWG DR1778.
+// { dg-do compile { target c++11 } }
+
+struct X { X(); };
+
+struct A {
+ struct B {
+ B() noexcept(A::value) = default;
+ X x;
+ };
+ decltype(B()) b;
+ static constexpr bool value = true;
+};
+A::B b;
+
+static_assert(noexcept(A::B()), "");
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept57.C gcc/testsuite/g++.dg/cpp0x/noexcept57.C
new file mode 100644
index 00000000000..b03f05fb4dc
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept57.C
@@ -0,0 +1,62 @@
+// PR c++/90537 - Implement P1286R2, Contra CWG DR1778.
+// { dg-do compile { target c++11 } }
+
+// A base that throws.
+struct B {
+ B() noexcept(false);
+ B(const B&) noexcept(false);
+ B(B&&) noexcept(false);
+ B &operator=(const B&) noexcept(false);
+ B &operator=(B&&) noexcept(false);
+ ~B() noexcept(false);
+};
+
+struct D1 : B {
+ D1() noexcept(false) = default;
+ D1(const D1&) noexcept(false) = default;
+ D1(D1&&) noexcept(false) = default;
+ D1 &operator=(const D1&) noexcept(false) = default;
+ D1 &operator=(D1&&) noexcept(false) = default;
+ ~D1() noexcept(false) = default;
+};
+
+D1 d1;
+static_assert(!noexcept(D1()), "");
+static_assert(!noexcept(D1(static_cast<D1&&>(d1))), "");
+static_assert(!noexcept(D1(d1)), "");
+static_assert(!noexcept(d1 = static_cast<D1&&>(d1)), "");
+static_assert(!noexcept(d1 = d1), "");
+
+struct D2 : B {
+ D2() = default;
+ D2(const D2&) = default;
+ D2(D2&&) = default;
+ D2 &operator=(const D2&) = default;
+ D2 &operator=(D2&&) = default;
+ ~D2() = default;
+};
+
+D2 d2;
+static_assert(!noexcept(D2()), "");
+static_assert(!noexcept(D2(static_cast<D2&&>(d2))), "");
+static_assert(!noexcept(D2(d2)), "");
+static_assert(!noexcept(d2 = static_cast<D2&&>(d2)), "");
+static_assert(!noexcept(d2 = d2), "");
+
+// This didn't compile pre-P1286R2 -- the implicit exception-specification
+// is noexcept(false).
+struct D3 : B {
+ D3() noexcept(true) = default;
+ D3(const D3&) noexcept(true) = default;
+ D3(D3&&) noexcept(true) = default;
+ D3 &operator=(const D3&) noexcept(true) = default;
+ D3 &operator=(D3&&) noexcept(true) = default;
+ ~D3() noexcept(true) = default;
+};
+
+D3 d3;
+static_assert(noexcept(D3()), "");
+static_assert(noexcept(D3(static_cast<D3&&>(d3))), "");
+static_assert(noexcept(D3(d3)), "");
+static_assert(noexcept(d3 = static_cast<D3&&>(d3)), "");
+static_assert(noexcept(d3 = d3), "");
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept58.C gcc/testsuite/g++.dg/cpp0x/noexcept58.C
new file mode 100644
index 00000000000..1e6c4ea5f02
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept58.C
@@ -0,0 +1,8 @@
+// PR c++/90537 - Implement P1286R2, Contra CWG DR1778.
+// { dg-do compile { target c++11 } }
+// [dcl.fct.def.default]/4
+
+struct T { T(); T(T &&) noexcept(false); };
+struct U { T t; U(); U(U &&) noexcept = default; };
+U u1;
+U u2 = static_cast<U&&>(u1); // OK, calls std::terminate if T::T(T&&) throws
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept59.C gcc/testsuite/g++.dg/cpp0x/noexcept59.C
new file mode 100644
index 00000000000..df4f30ff2d6
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept59.C
@@ -0,0 +1,87 @@
+// PR c++/90537 - Implement P1286R2, Contra CWG DR1778.
+// { dg-do compile { target c++11 } }
+
+// An explicitly-defaulted function may have a different exception-specification
+// from the exception-specification on an implicit declaration. This didn't
+// compile pre-P1286R2.
+struct A {
+ A() noexcept(false);
+ A(const A&) noexcept(false);
+ A(A&&) noexcept(false);
+ A &operator=(const A&) noexcept(false);
+ A &operator=(A&&) noexcept(false);
+ ~A() noexcept(false);
+};
+
+A::A() noexcept(false) = default;
+A::A(const A&) noexcept(false) = default;
+A::A(A&&) noexcept(false) = default;
+A& A::operator=(const A&) noexcept(false) = default;
+A& A::operator=(A&&) noexcept(false) = default;
+A::~A() noexcept(false) = default;
+
+A a;
+static_assert(!noexcept(A()), "");
+static_assert(!noexcept(A(A())), "");
+static_assert(!noexcept(A(a)), "");
+static_assert(!noexcept(A(static_cast<A&&>(a))), "");
+static_assert(!noexcept(a = a), "");
+static_assert(!noexcept(a = A()), "");
+static_assert(!noexcept(a = static_cast<A&&>(a)), "");
+
+// This compiled even pre-P1286R2. Make sure it continues to do so.
+struct B {
+ B() noexcept(true);
+ B(const B&) noexcept(true);
+ B(B&&) noexcept(true);
+ B &operator=(const B&) noexcept(true);
+ B &operator=(B&&) noexcept(true);
+ ~B() noexcept(true);
+};
+
+B::B() noexcept(true) = default;
+B::B(const B&) noexcept(true) = default;
+B::B(B&&) noexcept(true) = default;
+B& B::operator=(const B&) noexcept(true) = default;
+B& B::operator=(B&&) noexcept(true) = default;
+B::~B() noexcept(true) = default;
+
+B b;
+static_assert(noexcept(B()), "");
+static_assert(noexcept(B(B())), "");
+static_assert(noexcept(B(b)), "");
+static_assert(noexcept(B(static_cast<B&&>(b))), "");
+static_assert(noexcept(b = b), "");
+static_assert(noexcept(b = B()), "");
+static_assert(noexcept(b = static_cast<B&&>(b)), "");
+
+struct C {
+ C();
+ C(const C&);
+ C(C&&);
+ C &operator=(const C&);
+ C &operator=(C&&);
+ ~C();
+};
+
+// [except.spec] If a declaration of a function does not have
+// a noexcept-specifier, the declaration has a potentially throwing
+// exception specification unless it is a destructor or a deallocation
+// function or is defaulted on its first declaration, in which cases the
+// exception specification is as specified below
+// Here it's not defaulted on its first declaration.
+C::C() = default;
+C::C(const C&) = default;
+C::C(C&&) = default;
+C& C::operator=(const C&) = default;
+C& C::operator=(C&&) = default;
+C::~C() = default;
+
+C c;
+static_assert(!noexcept(C()), "");
+static_assert(!noexcept(C(C())), "");
+static_assert(!noexcept(C(c)), "");
+static_assert(!noexcept(C(static_cast<C&&>(c))), "");
+static_assert(!noexcept(c = c), "");
+static_assert(!noexcept(c = C()), "");
+static_assert(!noexcept(c = static_cast<C&&>(c)), "");
diff --git gcc/testsuite/g++.dg/cpp0x/noexcept60.C gcc/testsuite/g++.dg/cpp0x/noexcept60.C
new file mode 100644
index 00000000000..eeab1ed6c1c
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp0x/noexcept60.C
@@ -0,0 +1,81 @@
+// PR c++/90537 - Implement P1286R2, Contra CWG DR1778.
+// { dg-do compile { target c++11 } }
+
+// A base that throws.
+struct B {
+ B() noexcept(false);
+ B(const B&) noexcept(false);
+ B(B&&) noexcept(false);
+ B &operator=(const B&) noexcept(false);
+ B &operator=(B&&) noexcept(false);
+ ~B() noexcept(false);
+};
+
+struct D1 : B {
+ D1() noexcept(false);
+ D1(const D1&) noexcept(false);
+ D1(D1&&) noexcept(false);
+ D1 &operator=(const D1&) noexcept(false);
+ D1 &operator=(D1&&) noexcept(false);
+ ~D1() noexcept(false);
+};
+
+D1::D1() noexcept(false) = default;
+D1::D1(const D1&) noexcept(false) = default;
+D1::D1(D1&&) noexcept(false) = default;
+D1& D1::operator=(const D1&) noexcept(false) = default;
+D1& D1::operator=(D1&&) noexcept(false) = default;
+D1::~D1() noexcept(false) = default;
+
+D1 d1;
+static_assert(!noexcept(D1()), "");
+static_assert(!noexcept(D1(static_cast<D1&&>(d1))), "");
+static_assert(!noexcept(D1(d1)), "");
+static_assert(!noexcept(d1 = static_cast<D1&&>(d1)), "");
+static_assert(!noexcept(d1 = d1), "");
+
+struct D2 : B {
+ D2();
+ D2(const D2&);
+ D2(D2&&);
+ D2 &operator=(const D2&);
+ D2 &operator=(D2&&);
+ ~D2();
+};
+
+D2::D2() = default;
+D2::D2(const D2&) = default;
+D2::D2(D2&&) = default;
+D2& D2::operator=(const D2&) = default;
+D2& D2::operator=(D2&&) = default;
+D2::~D2() = default;
+
+D2 d2;
+static_assert(!noexcept(D2()), "");
+static_assert(!noexcept(D2(static_cast<D2&&>(d2))), "");
+static_assert(!noexcept(D2(d2)), "");
+static_assert(!noexcept(d2 = static_cast<D2&&>(d2)), "");
+static_assert(!noexcept(d2 = d2), "");
+
+struct D3 : B {
+ D3() noexcept(true);
+ D3(const D3&) noexcept(true);
+ D3(D3&&) noexcept(true);
+ D3 &operator=(const D3&) noexcept(true);
+ D3 &operator=(D3&&) noexcept(true);
+ ~D3() noexcept(true);
+};
+
+D3::D3() noexcept(true) = default;
+D3::D3(const D3&) noexcept(true) = default;
+D3::D3(D3&&) noexcept(true) = default;
+D3& D3::operator=(const D3&) noexcept(true) = default;
+D3& D3::operator=(D3&&) noexcept(true) = default;
+D3::~D3() noexcept(true) = default;
+
+D3 d3;
+static_assert(noexcept(D3()), "");
+static_assert(noexcept(D3(static_cast<D3&&>(d3))), "");
+static_assert(noexcept(D3(d3)), "");
+static_assert(noexcept(d3 = static_cast<D3&&>(d3)), "");
+static_assert(noexcept(d3 = d3), "");