diff mbox series

C++ PATCH for c++/90537 - Implement P1286R2, Contra CWG DR1778

Message ID 20190905181435.GZ14737@redhat.com
State New
Headers show
Series C++ PATCH for c++/90537 - Implement P1286R2, Contra CWG DR1778 | expand

Commit Message

Marek Polacek Sept. 5, 2019, 6:14 p.m. UTC
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.

Comments

Jason Merrill Sept. 5, 2019, 8:15 p.m. UTC | #1
On Thu, Sep 5, 2019 at 2:14 PM Marek Polacek <polacek@redhat.com> wrote:
>
> 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?

Ah, interesting.  It seems to me that a trivial function should not be
considered potentially-throwing.  I'm not sure whether it's best to
address this by making it non-trivial or overriding the exception
specification.  I think the definition of potentially-throwing is not
the right place to fix it.  Do you want to email CWG or shall I?

Jason
Marek Polacek Sept. 5, 2019, 8:21 p.m. UTC | #2
On Thu, Sep 05, 2019 at 04:15:06PM -0400, Jason Merrill wrote:
> On Thu, Sep 5, 2019 at 2:14 PM Marek Polacek <polacek@redhat.com> wrote:
> >
> > 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?
> 
> Ah, interesting.  It seems to me that a trivial function should not be
> considered potentially-throwing.  I'm not sure whether it's best to
> address this by making it non-trivial or overriding the exception
> specification.  I think the definition of potentially-throwing is not
> the right place to fix it.  Do you want to email CWG or shall I?

I'll do it.  Please chime in :).

Marek
diff mbox series

Patch

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), "");