c++: fix noexcept checking for trivial operations [PR96090]

Message ID 653db8c5.170a0220.c70e1.602f@mx.google.com
State Accepted
Headers
Series c++: fix noexcept checking for trivial operations [PR96090] |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

Nathaniel Shead Oct. 29, 2023, 1:43 a.m. UTC
  Bootstrapped and regtested on x86_64-pc-linux-gnu.

-- >8 --

This patch stops eager folding of trivial operations (construction and
assignment) from occurring when checking for noexceptness. This was
previously done in PR c++/53025, but only for copy/move construction,
and the __is_nothrow_xible builtins did not receive the same treatment
when they were added.

To handle `is_nothrow_default_constructible`, the patch also ensures
that when no parameters are passed we do value initialisation instead of
just building the constructor call: in particular, value-initialisation
doesn't necessarily actually invoke the constructor for trivial default
constructors, and so we need to handle this case as well.

	PR c++/96090
	PR c++/100470

gcc/cp/ChangeLog:

	* call.cc (build_over_call): Prevent folding of trivial special
	members when checking for noexcept.
	* method.cc (constructible_expr): Perform value-initialisation
	for empty parameter lists.
	(is_nothrow_xible): Treat as noexcept operator.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/noexcept81.C: New test.
	* g++.dg/ext/is_nothrow_constructible7.C: New test.
	* g++.dg/ext/is_nothrow_constructible8.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/call.cc                                | 17 ++---
 gcc/cp/method.cc                              | 19 ++++--
 gcc/testsuite/g++.dg/cpp0x/noexcept81.C       | 36 +++++++++++
 .../g++.dg/ext/is_nothrow_constructible7.C    | 20 ++++++
 .../g++.dg/ext/is_nothrow_constructible8.C    | 63 +++++++++++++++++++
 5 files changed, 141 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept81.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
  

Comments

Jason Merrill Dec. 9, 2023, 8:40 p.m. UTC | #1
On 11/27/23 06:07, Nathaniel Shead wrote:
> Ping for https://gcc.gnu.org/pipermail/gcc-patches/2023-October/634626.html.
> 
> I've been made aware since constructing this patch of CWG2820, which has
> a proposed resolution that would change the result of the testcase
> 'noexcept(yesthrow_t())' (and similarly for the library builtin), but as
> it hasn't yet been accepted I think at least ensuring the builtin
> matches the behaviour of the operator is probably still sensible.

OK.

> On Sun, Oct 29, 2023 at 12:43:28PM +1100, Nathaniel Shead wrote:
>> Bootstrapped and regtested on x86_64-pc-linux-gnu.
>>
>> -- >8 --
>>
>> This patch stops eager folding of trivial operations (construction and
>> assignment) from occurring when checking for noexceptness. This was
>> previously done in PR c++/53025, but only for copy/move construction,
>> and the __is_nothrow_xible builtins did not receive the same treatment
>> when they were added.
>>
>> To handle `is_nothrow_default_constructible`, the patch also ensures
>> that when no parameters are passed we do value initialisation instead of
>> just building the constructor call: in particular, value-initialisation
>> doesn't necessarily actually invoke the constructor for trivial default
>> constructors, and so we need to handle this case as well.
>>
>> 	PR c++/96090
>> 	PR c++/100470
>>
>> gcc/cp/ChangeLog:
>>
>> 	* call.cc (build_over_call): Prevent folding of trivial special
>> 	members when checking for noexcept.
>> 	* method.cc (constructible_expr): Perform value-initialisation
>> 	for empty parameter lists.
>> 	(is_nothrow_xible): Treat as noexcept operator.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	* g++.dg/cpp0x/noexcept81.C: New test.
>> 	* g++.dg/ext/is_nothrow_constructible7.C: New test.
>> 	* g++.dg/ext/is_nothrow_constructible8.C: New test.
>>
>> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
>> ---
>>   gcc/cp/call.cc                                | 17 ++---
>>   gcc/cp/method.cc                              | 19 ++++--
>>   gcc/testsuite/g++.dg/cpp0x/noexcept81.C       | 36 +++++++++++
>>   .../g++.dg/ext/is_nothrow_constructible7.C    | 20 ++++++
>>   .../g++.dg/ext/is_nothrow_constructible8.C    | 63 +++++++++++++++++++
>>   5 files changed, 141 insertions(+), 14 deletions(-)
>>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept81.C
>>   create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
>>   create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
>>
>> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
>> index c1fb8807d3f..ac02b0633ed 100644
>> --- a/gcc/cp/call.cc
>> +++ b/gcc/cp/call.cc
>> @@ -10231,15 +10231,16 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
>>     /* Avoid actually calling copy constructors and copy assignment operators,
>>        if possible.  */
>>   
>> -  if (! flag_elide_constructors && !force_elide)
>> +  if (!force_elide
>> +      && (!flag_elide_constructors
>> +	  /* It's unsafe to elide the operation when handling
>> +	     a noexcept-expression, it may evaluate to the wrong
>> +	     value (c++/53025, c++/96090).  */
>> +	  || cp_noexcept_operand != 0))
>>       /* Do things the hard way.  */;
>> -  else if (cand->num_convs == 1
>> -           && (DECL_COPY_CONSTRUCTOR_P (fn)
>> -               || DECL_MOVE_CONSTRUCTOR_P (fn))
>> -	   /* It's unsafe to elide the constructor when handling
>> -	      a noexcept-expression, it may evaluate to the wrong
>> -	      value (c++/53025).  */
>> -	   && (force_elide || cp_noexcept_operand == 0))
>> +  else if (cand->num_convs == 1
>> +	   && (DECL_COPY_CONSTRUCTOR_P (fn)
>> +	       || DECL_MOVE_CONSTRUCTOR_P (fn)))
>>       {
>>         tree targ;
>>         tree arg = argarray[num_artificial_parms_for (fn)];
>> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
>> index a70dd5d6adc..3c978e2369d 100644
>> --- a/gcc/cp/method.cc
>> +++ b/gcc/cp/method.cc
>> @@ -2091,6 +2091,7 @@ constructible_expr (tree to, tree from)
>>   {
>>     tree expr;
>>     cp_unevaluated cp_uneval_guard;
>> +  const int len = TREE_VEC_LENGTH (from);
>>     if (CLASS_TYPE_P (to))
>>       {
>>         tree ctype = to;
>> @@ -2098,11 +2099,16 @@ constructible_expr (tree to, tree from)
>>         if (!TYPE_REF_P (to))
>>   	to = cp_build_reference_type (to, /*rval*/false);
>>         tree ob = build_stub_object (to);
>> -      vec_alloc (args, TREE_VEC_LENGTH (from));
>> -      for (tree arg : tree_vec_range (from))
>> -	args->quick_push (build_stub_object (arg));
>> -      expr = build_special_member_call (ob, complete_ctor_identifier, &args,
>> -					ctype, LOOKUP_NORMAL, tf_none);
>> +      if (len == 0)
>> +	expr = build_value_init (ctype, tf_none);
>> +      else
>> +	{
>> +	  vec_alloc (args, TREE_VEC_LENGTH (from));
>> +	  for (tree arg : tree_vec_range (from))
>> +	    args->quick_push (build_stub_object (arg));
>> +	  expr = build_special_member_call (ob, complete_ctor_identifier, &args,
>> +					    ctype, LOOKUP_NORMAL, tf_none);
>> +	}
>>         if (expr == error_mark_node)
>>   	return error_mark_node;
>>         /* The current state of the standard vis-a-vis LWG 2116 is that
>> @@ -2120,7 +2126,6 @@ constructible_expr (tree to, tree from)
>>       }
>>     else
>>       {
>> -      const int len = TREE_VEC_LENGTH (from);
>>         if (len == 0)
>>   	return build_value_init (strip_array_types (to), tf_none);
>>         if (len > 1)
>> @@ -2216,7 +2221,9 @@ is_trivially_xible (enum tree_code code, tree to, tree from)
>>   bool
>>   is_nothrow_xible (enum tree_code code, tree to, tree from)
>>   {
>> +  ++cp_noexcept_operand;
>>     tree expr = is_xible_helper (code, to, from, /*trivial*/false);
>> +  --cp_noexcept_operand;
>>     if (expr == NULL_TREE || expr == error_mark_node)
>>       return false;
>>     return expr_noexcept_p (expr, tf_none);
>> diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept81.C b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
>> new file mode 100644
>> index 00000000000..a1481613b5d
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
>> @@ -0,0 +1,36 @@
>> +// { dg-do compile { target c++11 } }
>> +// PR c++/96090
>> +
>> +struct yesthrow_t {
>> +  yesthrow_t()                              noexcept(false) = default;
>> +  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
>> +  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
>> +  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
>> +  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
>> +};
>> +
>> +yesthrow_t yes;
>> +static_assert(not noexcept(yesthrow_t(static_cast<const yesthrow_t&>(yes))), "");
>> +static_assert(not noexcept(yesthrow_t(static_cast<yesthrow_t&&>(yes))), "");
>> +static_assert(not noexcept(yes = static_cast<const yesthrow_t&>(yes)), "");
>> +static_assert(not noexcept(yes = static_cast<yesthrow_t&&>(yes)), "");
>> +
>> +// Note: this is value-initialisation, and thus by [dcl.init.general] p9
>> +// a trivial non-user-provided non-deleted default constructor is not called.
>> +static_assert(noexcept(yesthrow_t()), "");
>> +
>> +struct nothrow_t {
>> +  nothrow_t()                             noexcept(true) = default;
>> +  nothrow_t(const nothrow_t&)             noexcept(true) = default;
>> +  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
>> +  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
>> +  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
>> +};
>> +
>> +nothrow_t no;
>> +static_assert(noexcept(nothrow_t()), "");
>> +static_assert(noexcept(nothrow_t(static_cast<const nothrow_t&>(no))), "");
>> +static_assert(noexcept(nothrow_t(static_cast<nothrow_t&&>(no))), "");
>> +static_assert(noexcept(no = static_cast<const nothrow_t&>(no)), "");
>> +static_assert(noexcept(no = static_cast<nothrow_t&&>(no)), "");
>> +
>> diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
>> new file mode 100644
>> index 00000000000..b63b13ac52f
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
>> @@ -0,0 +1,20 @@
>> +// { dg-do compile { target c++11 } }
>> +// PR c++/100470
>> +
>> +struct S1{
>> +    S1(S1&&) noexcept(false);
>> +};
>> +struct S2{
>> +    S2(S2&&) noexcept(false) = default;
>> +};
>> +struct S3{
>> +    S3(S3&&) noexcept(false){}
>> +};
>> +struct S4{
>> +    S4(S4&&) = default;
>> +};
>> +
>> +static_assert(!__is_nothrow_constructible(S1, S1), "");
>> +static_assert(!__is_nothrow_constructible(S2, S2), "");
>> +static_assert(!__is_nothrow_constructible(S3, S3), "");
>> +static_assert( __is_nothrow_constructible(S4, S4), "");
>> diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
>> new file mode 100644
>> index 00000000000..f23d48fa888
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
>> @@ -0,0 +1,63 @@
>> +// { dg-do compile { target c++11 } }
>> +// PR c++/96090
>> +
>> +template <typename T>
>> +constexpr bool is_nothrow_default_constructible_v
>> +  = __is_nothrow_constructible(T);
>> +template <typename T>
>> +constexpr bool is_nothrow_copy_constructible_v
>> +  = __is_nothrow_constructible(T, const T&);
>> +template <typename T>
>> +constexpr bool is_nothrow_move_constructible_v
>> +  = __is_nothrow_constructible(T, T&&);
>> +template <typename T>
>> +constexpr bool is_nothrow_copy_assignable_v
>> +  = __is_nothrow_assignable(T, const T&);
>> +template <typename T>
>> +constexpr bool is_nothrow_move_assignable_v
>> +  = __is_nothrow_assignable(T, T&&);
>> +
>> +struct yesthrow_t {
>> +  yesthrow_t()                              noexcept(false) = default;
>> +  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
>> +  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
>> +  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
>> +  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
>> +};
>> +
>> +static_assert(not is_nothrow_copy_constructible_v<yesthrow_t>, "");
>> +static_assert(not is_nothrow_copy_assignable_v<yesthrow_t>, "");
>> +static_assert(not is_nothrow_move_constructible_v<yesthrow_t>, "");
>> +static_assert(not is_nothrow_move_assignable_v<yesthrow_t>, "");
>> +
>> +// Note: by [meta.unary.prop] p9 this should be value-initialisation,
>> +// and thus by [dcl.init.general] p9 a trivial non-user-provided
>> +// non-deleted default constructor is not called.
>> +static_assert(is_nothrow_default_constructible_v<yesthrow_t>, "");
>> +
>> +struct nothrow_t {
>> +  nothrow_t()                             noexcept(true) = default;
>> +  nothrow_t(const nothrow_t&)             noexcept(true) = default;
>> +  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
>> +  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
>> +  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
>> +};
>> +
>> +static_assert(is_nothrow_default_constructible_v<nothrow_t>, "");
>> +static_assert(is_nothrow_copy_constructible_v<nothrow_t>, "");
>> +static_assert(is_nothrow_copy_assignable_v<nothrow_t>, "");
>> +static_assert(is_nothrow_move_constructible_v<nothrow_t>, "");
>> +static_assert(is_nothrow_move_assignable_v<nothrow_t>, "");
>> +
>> +struct A { A() noexcept(false) = default; };
>> +struct B { B(const B&) noexcept(false) = default; };
>> +struct C { C(C&&) noexcept(false) = default; };
>> +struct D { D& operator=(const D&) noexcept(false) = default; };
>> +struct E { E& operator=(E&&) noexcept(false) = default; };
>> +
>> +static_assert(is_nothrow_default_constructible_v<A>, "");  // see above
>> +static_assert(not is_nothrow_copy_constructible_v<B>, "");
>> +static_assert(not is_nothrow_move_constructible_v<C>, "");
>> +static_assert(not is_nothrow_copy_assignable_v<D>, "");
>> +static_assert(not is_nothrow_move_assignable_v<E>, "");
>> +
>> -- 
>> 2.42.0
>>
>
  
Nathaniel Shead Dec. 11, 2023, 2:04 a.m. UTC | #2
On Sat, Dec 09, 2023 at 03:40:44PM -0500, Jason Merrill wrote:
> On 11/27/23 06:07, Nathaniel Shead wrote:
> > Ping for https://gcc.gnu.org/pipermail/gcc-patches/2023-October/634626.html.
> > 
> > I've been made aware since constructing this patch of CWG2820, which has
> > a proposed resolution that would change the result of the testcase
> > 'noexcept(yesthrow_t())' (and similarly for the library builtin), but as
> > it hasn't yet been accepted I think at least ensuring the builtin
> > matches the behaviour of the operator is probably still sensible.
> 
> OK.

Thanks. Looking at the patch again as went go to push it I noticed a
couple of small things.

I've added another paragraph to the commit message to mention CWG2820,
fixed a trailing whitespace I'd missed before, and replaced a call to
'TREE_VEC_LENGTH (from)' with 'len' that I'd missed initially.

Bootstrapped and regtested on x86_64-pc-linux-gnu, still OK for trunk?

-- >8 --

This patch stops eager folding of trivial operations (construction and
assignment) from occurring when checking for noexceptness. This was
previously done in PR c++/53025, but only for copy/move construction,
and the __is_nothrow_xible builtins did not receive the same treatment
when they were added.

To handle `is_nothrow_default_constructible`, the patch also ensures
that when no parameters are passed we do value initialisation instead of
just building the constructor call: in particular, value-initialisation
doesn't necessarily actually invoke the constructor for trivial default
constructors, and so we need to handle this case as well.

This is contrary to the proposed resolution of CWG2820; for now we just
ensure it matches the behaviour of the `noexcept` operator and create
testcases formalising this, and if that issue gets accepted we can
revisit.

	PR c++/96090
	PR c++/100470

gcc/cp/ChangeLog:

	* call.cc (build_over_call): Prevent folding of trivial special
	members when checking for noexcept.
	* method.cc (constructible_expr): Perform value-initialisation
	for empty parameter lists.
	(is_nothrow_xible): Treat as noexcept operator.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/noexcept81.C: New test.
	* g++.dg/ext/is_nothrow_constructible7.C: New test.
	* g++.dg/ext/is_nothrow_constructible8.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/call.cc                                | 17 ++---
 gcc/cp/method.cc                              | 19 ++++--
 gcc/testsuite/g++.dg/cpp0x/noexcept81.C       | 36 +++++++++++
 .../g++.dg/ext/is_nothrow_constructible7.C    | 20 ++++++
 .../g++.dg/ext/is_nothrow_constructible8.C    | 63 +++++++++++++++++++
 5 files changed, 141 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept81.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
 create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c7efc5b077a..4f0abf8e93f 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -10247,15 +10247,16 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   /* Avoid actually calling copy constructors and copy assignment operators,
      if possible.  */
 
-  if (! flag_elide_constructors && !force_elide)
+  if (!force_elide
+      && (!flag_elide_constructors
+	  /* It's unsafe to elide the operation when handling
+	     a noexcept-expression, it may evaluate to the wrong
+	     value (c++/53025, c++/96090).  */
+	  || cp_noexcept_operand != 0))
     /* Do things the hard way.  */;
-  else if (cand->num_convs == 1 
-           && (DECL_COPY_CONSTRUCTOR_P (fn) 
-               || DECL_MOVE_CONSTRUCTOR_P (fn))
-	   /* It's unsafe to elide the constructor when handling
-	      a noexcept-expression, it may evaluate to the wrong
-	      value (c++/53025).  */
-	   && (force_elide || cp_noexcept_operand == 0))
+  else if (cand->num_convs == 1
+	   && (DECL_COPY_CONSTRUCTOR_P (fn)
+	       || DECL_MOVE_CONSTRUCTOR_P (fn)))
     {
       tree targ;
       tree arg = argarray[num_artificial_parms_for (fn)];
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index a70dd5d6adc..26e6eb79946 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2091,6 +2091,7 @@ constructible_expr (tree to, tree from)
 {
   tree expr;
   cp_unevaluated cp_uneval_guard;
+  const int len = TREE_VEC_LENGTH (from);
   if (CLASS_TYPE_P (to))
     {
       tree ctype = to;
@@ -2098,11 +2099,16 @@ constructible_expr (tree to, tree from)
       if (!TYPE_REF_P (to))
 	to = cp_build_reference_type (to, /*rval*/false);
       tree ob = build_stub_object (to);
-      vec_alloc (args, TREE_VEC_LENGTH (from));
-      for (tree arg : tree_vec_range (from))
-	args->quick_push (build_stub_object (arg));
-      expr = build_special_member_call (ob, complete_ctor_identifier, &args,
-					ctype, LOOKUP_NORMAL, tf_none);
+      if (len == 0)
+	expr = build_value_init (ctype, tf_none);
+      else
+	{
+	  vec_alloc (args, len);
+	  for (tree arg : tree_vec_range (from))
+	    args->quick_push (build_stub_object (arg));
+	  expr = build_special_member_call (ob, complete_ctor_identifier, &args,
+					    ctype, LOOKUP_NORMAL, tf_none);
+	}
       if (expr == error_mark_node)
 	return error_mark_node;
       /* The current state of the standard vis-a-vis LWG 2116 is that
@@ -2120,7 +2126,6 @@ constructible_expr (tree to, tree from)
     }
   else
     {
-      const int len = TREE_VEC_LENGTH (from);
       if (len == 0)
 	return build_value_init (strip_array_types (to), tf_none);
       if (len > 1)
@@ -2216,7 +2221,9 @@ is_trivially_xible (enum tree_code code, tree to, tree from)
 bool
 is_nothrow_xible (enum tree_code code, tree to, tree from)
 {
+  ++cp_noexcept_operand;
   tree expr = is_xible_helper (code, to, from, /*trivial*/false);
+  --cp_noexcept_operand;
   if (expr == NULL_TREE || expr == error_mark_node)
     return false;
   return expr_noexcept_p (expr, tf_none);
diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept81.C b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
new file mode 100644
index 00000000000..a1481613b5d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
@@ -0,0 +1,36 @@
+// { dg-do compile { target c++11 } }
+// PR c++/96090
+
+struct yesthrow_t {
+  yesthrow_t()                              noexcept(false) = default;
+  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
+  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
+  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
+  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
+};
+
+yesthrow_t yes;
+static_assert(not noexcept(yesthrow_t(static_cast<const yesthrow_t&>(yes))), "");
+static_assert(not noexcept(yesthrow_t(static_cast<yesthrow_t&&>(yes))), "");
+static_assert(not noexcept(yes = static_cast<const yesthrow_t&>(yes)), "");
+static_assert(not noexcept(yes = static_cast<yesthrow_t&&>(yes)), "");
+
+// Note: this is value-initialisation, and thus by [dcl.init.general] p9
+// a trivial non-user-provided non-deleted default constructor is not called.
+static_assert(noexcept(yesthrow_t()), "");
+
+struct nothrow_t {
+  nothrow_t()                             noexcept(true) = default;
+  nothrow_t(const nothrow_t&)             noexcept(true) = default;
+  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
+  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
+  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
+};
+
+nothrow_t no;
+static_assert(noexcept(nothrow_t()), "");
+static_assert(noexcept(nothrow_t(static_cast<const nothrow_t&>(no))), "");
+static_assert(noexcept(nothrow_t(static_cast<nothrow_t&&>(no))), "");
+static_assert(noexcept(no = static_cast<const nothrow_t&>(no)), "");
+static_assert(noexcept(no = static_cast<nothrow_t&&>(no)), "");
+
diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
new file mode 100644
index 00000000000..b63b13ac52f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
@@ -0,0 +1,20 @@
+// { dg-do compile { target c++11 } }
+// PR c++/100470
+
+struct S1{
+    S1(S1&&) noexcept(false);
+};
+struct S2{
+    S2(S2&&) noexcept(false) = default;
+};
+struct S3{
+    S3(S3&&) noexcept(false){}
+};
+struct S4{
+    S4(S4&&) = default;
+};
+
+static_assert(!__is_nothrow_constructible(S1, S1), "");
+static_assert(!__is_nothrow_constructible(S2, S2), "");
+static_assert(!__is_nothrow_constructible(S3, S3), "");
+static_assert( __is_nothrow_constructible(S4, S4), "");
diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
new file mode 100644
index 00000000000..f23d48fa888
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
@@ -0,0 +1,63 @@
+// { dg-do compile { target c++11 } }
+// PR c++/96090
+
+template <typename T>
+constexpr bool is_nothrow_default_constructible_v
+  = __is_nothrow_constructible(T);
+template <typename T>
+constexpr bool is_nothrow_copy_constructible_v
+  = __is_nothrow_constructible(T, const T&);
+template <typename T>
+constexpr bool is_nothrow_move_constructible_v
+  = __is_nothrow_constructible(T, T&&);
+template <typename T>
+constexpr bool is_nothrow_copy_assignable_v
+  = __is_nothrow_assignable(T, const T&);
+template <typename T>
+constexpr bool is_nothrow_move_assignable_v
+  = __is_nothrow_assignable(T, T&&);
+
+struct yesthrow_t {
+  yesthrow_t()                              noexcept(false) = default;
+  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
+  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
+  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
+  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
+};
+
+static_assert(not is_nothrow_copy_constructible_v<yesthrow_t>, "");
+static_assert(not is_nothrow_copy_assignable_v<yesthrow_t>, "");
+static_assert(not is_nothrow_move_constructible_v<yesthrow_t>, "");
+static_assert(not is_nothrow_move_assignable_v<yesthrow_t>, "");
+
+// Note: by [meta.unary.prop] p9 this should be value-initialisation,
+// and thus by [dcl.init.general] p9 a trivial non-user-provided
+// non-deleted default constructor is not called.
+static_assert(is_nothrow_default_constructible_v<yesthrow_t>, "");
+
+struct nothrow_t {
+  nothrow_t()                             noexcept(true) = default;
+  nothrow_t(const nothrow_t&)             noexcept(true) = default;
+  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
+  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
+  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
+};
+
+static_assert(is_nothrow_default_constructible_v<nothrow_t>, "");
+static_assert(is_nothrow_copy_constructible_v<nothrow_t>, "");
+static_assert(is_nothrow_copy_assignable_v<nothrow_t>, "");
+static_assert(is_nothrow_move_constructible_v<nothrow_t>, "");
+static_assert(is_nothrow_move_assignable_v<nothrow_t>, "");
+
+struct A { A() noexcept(false) = default; };
+struct B { B(const B&) noexcept(false) = default; };
+struct C { C(C&&) noexcept(false) = default; };
+struct D { D& operator=(const D&) noexcept(false) = default; };
+struct E { E& operator=(E&&) noexcept(false) = default; };
+
+static_assert(is_nothrow_default_constructible_v<A>, "");  // see above
+static_assert(not is_nothrow_copy_constructible_v<B>, "");
+static_assert(not is_nothrow_move_constructible_v<C>, "");
+static_assert(not is_nothrow_copy_assignable_v<D>, "");
+static_assert(not is_nothrow_move_assignable_v<E>, "");
+
  
Jason Merrill Dec. 11, 2023, 2:20 a.m. UTC | #3
On 12/10/23 21:04, Nathaniel Shead wrote:
> On Sat, Dec 09, 2023 at 03:40:44PM -0500, Jason Merrill wrote:
>> On 11/27/23 06:07, Nathaniel Shead wrote:
>>> Ping for https://gcc.gnu.org/pipermail/gcc-patches/2023-October/634626.html.
>>>
>>> I've been made aware since constructing this patch of CWG2820, which has
>>> a proposed resolution that would change the result of the testcase
>>> 'noexcept(yesthrow_t())' (and similarly for the library builtin), but as
>>> it hasn't yet been accepted I think at least ensuring the builtin
>>> matches the behaviour of the operator is probably still sensible.
>>
>> OK.
> 
> Thanks. Looking at the patch again as went go to push it I noticed a
> couple of small things.
> 
> I've added another paragraph to the commit message to mention CWG2820,
> fixed a trailing whitespace I'd missed before, and replaced a call to
> 'TREE_VEC_LENGTH (from)' with 'len' that I'd missed initially.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, still OK for trunk?

Please also mention CWG2820 in the testcase.  OK with that change.

> -- >8 --
> 
> This patch stops eager folding of trivial operations (construction and
> assignment) from occurring when checking for noexceptness. This was
> previously done in PR c++/53025, but only for copy/move construction,
> and the __is_nothrow_xible builtins did not receive the same treatment
> when they were added.
> 
> To handle `is_nothrow_default_constructible`, the patch also ensures
> that when no parameters are passed we do value initialisation instead of
> just building the constructor call: in particular, value-initialisation
> doesn't necessarily actually invoke the constructor for trivial default
> constructors, and so we need to handle this case as well.
> 
> This is contrary to the proposed resolution of CWG2820; for now we just
> ensure it matches the behaviour of the `noexcept` operator and create
> testcases formalising this, and if that issue gets accepted we can
> revisit.
> 
> 	PR c++/96090
> 	PR c++/100470
> 
> gcc/cp/ChangeLog:
> 
> 	* call.cc (build_over_call): Prevent folding of trivial special
> 	members when checking for noexcept.
> 	* method.cc (constructible_expr): Perform value-initialisation
> 	for empty parameter lists.
> 	(is_nothrow_xible): Treat as noexcept operator.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/noexcept81.C: New test.
> 	* g++.dg/ext/is_nothrow_constructible7.C: New test.
> 	* g++.dg/ext/is_nothrow_constructible8.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>   gcc/cp/call.cc                                | 17 ++---
>   gcc/cp/method.cc                              | 19 ++++--
>   gcc/testsuite/g++.dg/cpp0x/noexcept81.C       | 36 +++++++++++
>   .../g++.dg/ext/is_nothrow_constructible7.C    | 20 ++++++
>   .../g++.dg/ext/is_nothrow_constructible8.C    | 63 +++++++++++++++++++
>   5 files changed, 141 insertions(+), 14 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept81.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
> 
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index c7efc5b077a..4f0abf8e93f 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -10247,15 +10247,16 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
>     /* Avoid actually calling copy constructors and copy assignment operators,
>        if possible.  */
>   
> -  if (! flag_elide_constructors && !force_elide)
> +  if (!force_elide
> +      && (!flag_elide_constructors
> +	  /* It's unsafe to elide the operation when handling
> +	     a noexcept-expression, it may evaluate to the wrong
> +	     value (c++/53025, c++/96090).  */
> +	  || cp_noexcept_operand != 0))
>       /* Do things the hard way.  */;
> -  else if (cand->num_convs == 1
> -           && (DECL_COPY_CONSTRUCTOR_P (fn)
> -               || DECL_MOVE_CONSTRUCTOR_P (fn))
> -	   /* It's unsafe to elide the constructor when handling
> -	      a noexcept-expression, it may evaluate to the wrong
> -	      value (c++/53025).  */
> -	   && (force_elide || cp_noexcept_operand == 0))
> +  else if (cand->num_convs == 1
> +	   && (DECL_COPY_CONSTRUCTOR_P (fn)
> +	       || DECL_MOVE_CONSTRUCTOR_P (fn)))
>       {
>         tree targ;
>         tree arg = argarray[num_artificial_parms_for (fn)];
> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> index a70dd5d6adc..26e6eb79946 100644
> --- a/gcc/cp/method.cc
> +++ b/gcc/cp/method.cc
> @@ -2091,6 +2091,7 @@ constructible_expr (tree to, tree from)
>   {
>     tree expr;
>     cp_unevaluated cp_uneval_guard;
> +  const int len = TREE_VEC_LENGTH (from);
>     if (CLASS_TYPE_P (to))
>       {
>         tree ctype = to;
> @@ -2098,11 +2099,16 @@ constructible_expr (tree to, tree from)
>         if (!TYPE_REF_P (to))
>   	to = cp_build_reference_type (to, /*rval*/false);
>         tree ob = build_stub_object (to);
> -      vec_alloc (args, TREE_VEC_LENGTH (from));
> -      for (tree arg : tree_vec_range (from))
> -	args->quick_push (build_stub_object (arg));
> -      expr = build_special_member_call (ob, complete_ctor_identifier, &args,
> -					ctype, LOOKUP_NORMAL, tf_none);
> +      if (len == 0)
> +	expr = build_value_init (ctype, tf_none);
> +      else
> +	{
> +	  vec_alloc (args, len);
> +	  for (tree arg : tree_vec_range (from))
> +	    args->quick_push (build_stub_object (arg));
> +	  expr = build_special_member_call (ob, complete_ctor_identifier, &args,
> +					    ctype, LOOKUP_NORMAL, tf_none);
> +	}
>         if (expr == error_mark_node)
>   	return error_mark_node;
>         /* The current state of the standard vis-a-vis LWG 2116 is that
> @@ -2120,7 +2126,6 @@ constructible_expr (tree to, tree from)
>       }
>     else
>       {
> -      const int len = TREE_VEC_LENGTH (from);
>         if (len == 0)
>   	return build_value_init (strip_array_types (to), tf_none);
>         if (len > 1)
> @@ -2216,7 +2221,9 @@ is_trivially_xible (enum tree_code code, tree to, tree from)
>   bool
>   is_nothrow_xible (enum tree_code code, tree to, tree from)
>   {
> +  ++cp_noexcept_operand;
>     tree expr = is_xible_helper (code, to, from, /*trivial*/false);
> +  --cp_noexcept_operand;
>     if (expr == NULL_TREE || expr == error_mark_node)
>       return false;
>     return expr_noexcept_p (expr, tf_none);
> diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept81.C b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
> new file mode 100644
> index 00000000000..a1481613b5d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
> @@ -0,0 +1,36 @@
> +// { dg-do compile { target c++11 } }
> +// PR c++/96090
> +
> +struct yesthrow_t {
> +  yesthrow_t()                              noexcept(false) = default;
> +  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
> +  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
> +  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
> +  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
> +};
> +
> +yesthrow_t yes;
> +static_assert(not noexcept(yesthrow_t(static_cast<const yesthrow_t&>(yes))), "");
> +static_assert(not noexcept(yesthrow_t(static_cast<yesthrow_t&&>(yes))), "");
> +static_assert(not noexcept(yes = static_cast<const yesthrow_t&>(yes)), "");
> +static_assert(not noexcept(yes = static_cast<yesthrow_t&&>(yes)), "");
> +
> +// Note: this is value-initialisation, and thus by [dcl.init.general] p9
> +// a trivial non-user-provided non-deleted default constructor is not called.
> +static_assert(noexcept(yesthrow_t()), "");
> +
> +struct nothrow_t {
> +  nothrow_t()                             noexcept(true) = default;
> +  nothrow_t(const nothrow_t&)             noexcept(true) = default;
> +  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
> +  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
> +  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
> +};
> +
> +nothrow_t no;
> +static_assert(noexcept(nothrow_t()), "");
> +static_assert(noexcept(nothrow_t(static_cast<const nothrow_t&>(no))), "");
> +static_assert(noexcept(nothrow_t(static_cast<nothrow_t&&>(no))), "");
> +static_assert(noexcept(no = static_cast<const nothrow_t&>(no)), "");
> +static_assert(noexcept(no = static_cast<nothrow_t&&>(no)), "");
> +
> diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
> new file mode 100644
> index 00000000000..b63b13ac52f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
> @@ -0,0 +1,20 @@
> +// { dg-do compile { target c++11 } }
> +// PR c++/100470
> +
> +struct S1{
> +    S1(S1&&) noexcept(false);
> +};
> +struct S2{
> +    S2(S2&&) noexcept(false) = default;
> +};
> +struct S3{
> +    S3(S3&&) noexcept(false){}
> +};
> +struct S4{
> +    S4(S4&&) = default;
> +};
> +
> +static_assert(!__is_nothrow_constructible(S1, S1), "");
> +static_assert(!__is_nothrow_constructible(S2, S2), "");
> +static_assert(!__is_nothrow_constructible(S3, S3), "");
> +static_assert( __is_nothrow_constructible(S4, S4), "");
> diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
> new file mode 100644
> index 00000000000..f23d48fa888
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
> @@ -0,0 +1,63 @@
> +// { dg-do compile { target c++11 } }
> +// PR c++/96090
> +
> +template <typename T>
> +constexpr bool is_nothrow_default_constructible_v
> +  = __is_nothrow_constructible(T);
> +template <typename T>
> +constexpr bool is_nothrow_copy_constructible_v
> +  = __is_nothrow_constructible(T, const T&);
> +template <typename T>
> +constexpr bool is_nothrow_move_constructible_v
> +  = __is_nothrow_constructible(T, T&&);
> +template <typename T>
> +constexpr bool is_nothrow_copy_assignable_v
> +  = __is_nothrow_assignable(T, const T&);
> +template <typename T>
> +constexpr bool is_nothrow_move_assignable_v
> +  = __is_nothrow_assignable(T, T&&);
> +
> +struct yesthrow_t {
> +  yesthrow_t()                              noexcept(false) = default;
> +  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
> +  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
> +  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
> +  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
> +};
> +
> +static_assert(not is_nothrow_copy_constructible_v<yesthrow_t>, "");
> +static_assert(not is_nothrow_copy_assignable_v<yesthrow_t>, "");
> +static_assert(not is_nothrow_move_constructible_v<yesthrow_t>, "");
> +static_assert(not is_nothrow_move_assignable_v<yesthrow_t>, "");
> +
> +// Note: by [meta.unary.prop] p9 this should be value-initialisation,
> +// and thus by [dcl.init.general] p9 a trivial non-user-provided
> +// non-deleted default constructor is not called.
> +static_assert(is_nothrow_default_constructible_v<yesthrow_t>, "");
> +
> +struct nothrow_t {
> +  nothrow_t()                             noexcept(true) = default;
> +  nothrow_t(const nothrow_t&)             noexcept(true) = default;
> +  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
> +  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
> +  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
> +};
> +
> +static_assert(is_nothrow_default_constructible_v<nothrow_t>, "");
> +static_assert(is_nothrow_copy_constructible_v<nothrow_t>, "");
> +static_assert(is_nothrow_copy_assignable_v<nothrow_t>, "");
> +static_assert(is_nothrow_move_constructible_v<nothrow_t>, "");
> +static_assert(is_nothrow_move_assignable_v<nothrow_t>, "");
> +
> +struct A { A() noexcept(false) = default; };
> +struct B { B(const B&) noexcept(false) = default; };
> +struct C { C(C&&) noexcept(false) = default; };
> +struct D { D& operator=(const D&) noexcept(false) = default; };
> +struct E { E& operator=(E&&) noexcept(false) = default; };
> +
> +static_assert(is_nothrow_default_constructible_v<A>, "");  // see above
> +static_assert(not is_nothrow_copy_constructible_v<B>, "");
> +static_assert(not is_nothrow_move_constructible_v<C>, "");
> +static_assert(not is_nothrow_copy_assignable_v<D>, "");
> +static_assert(not is_nothrow_move_assignable_v<E>, "");
> +
  

Patch

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c1fb8807d3f..ac02b0633ed 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -10231,15 +10231,16 @@  build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   /* Avoid actually calling copy constructors and copy assignment operators,
      if possible.  */
 
-  if (! flag_elide_constructors && !force_elide)
+  if (!force_elide 
+      && (!flag_elide_constructors
+	  /* It's unsafe to elide the operation when handling
+	     a noexcept-expression, it may evaluate to the wrong
+	     value (c++/53025, c++/96090).  */
+	  || cp_noexcept_operand != 0))
     /* Do things the hard way.  */;
-  else if (cand->num_convs == 1 
-           && (DECL_COPY_CONSTRUCTOR_P (fn) 
-               || DECL_MOVE_CONSTRUCTOR_P (fn))
-	   /* It's unsafe to elide the constructor when handling
-	      a noexcept-expression, it may evaluate to the wrong
-	      value (c++/53025).  */
-	   && (force_elide || cp_noexcept_operand == 0))
+  else if (cand->num_convs == 1
+	   && (DECL_COPY_CONSTRUCTOR_P (fn)
+	       || DECL_MOVE_CONSTRUCTOR_P (fn)))
     {
       tree targ;
       tree arg = argarray[num_artificial_parms_for (fn)];
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index a70dd5d6adc..3c978e2369d 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -2091,6 +2091,7 @@  constructible_expr (tree to, tree from)
 {
   tree expr;
   cp_unevaluated cp_uneval_guard;
+  const int len = TREE_VEC_LENGTH (from);
   if (CLASS_TYPE_P (to))
     {
       tree ctype = to;
@@ -2098,11 +2099,16 @@  constructible_expr (tree to, tree from)
       if (!TYPE_REF_P (to))
 	to = cp_build_reference_type (to, /*rval*/false);
       tree ob = build_stub_object (to);
-      vec_alloc (args, TREE_VEC_LENGTH (from));
-      for (tree arg : tree_vec_range (from))
-	args->quick_push (build_stub_object (arg));
-      expr = build_special_member_call (ob, complete_ctor_identifier, &args,
-					ctype, LOOKUP_NORMAL, tf_none);
+      if (len == 0)
+	expr = build_value_init (ctype, tf_none);
+      else
+	{
+	  vec_alloc (args, TREE_VEC_LENGTH (from));
+	  for (tree arg : tree_vec_range (from))
+	    args->quick_push (build_stub_object (arg));
+	  expr = build_special_member_call (ob, complete_ctor_identifier, &args,
+					    ctype, LOOKUP_NORMAL, tf_none);
+	}
       if (expr == error_mark_node)
 	return error_mark_node;
       /* The current state of the standard vis-a-vis LWG 2116 is that
@@ -2120,7 +2126,6 @@  constructible_expr (tree to, tree from)
     }
   else
     {
-      const int len = TREE_VEC_LENGTH (from);
       if (len == 0)
 	return build_value_init (strip_array_types (to), tf_none);
       if (len > 1)
@@ -2216,7 +2221,9 @@  is_trivially_xible (enum tree_code code, tree to, tree from)
 bool
 is_nothrow_xible (enum tree_code code, tree to, tree from)
 {
+  ++cp_noexcept_operand;
   tree expr = is_xible_helper (code, to, from, /*trivial*/false);
+  --cp_noexcept_operand;
   if (expr == NULL_TREE || expr == error_mark_node)
     return false;
   return expr_noexcept_p (expr, tf_none);
diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept81.C b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
new file mode 100644
index 00000000000..a1481613b5d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
@@ -0,0 +1,36 @@ 
+// { dg-do compile { target c++11 } }
+// PR c++/96090
+
+struct yesthrow_t {
+  yesthrow_t()                              noexcept(false) = default;
+  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
+  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
+  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
+  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
+};
+
+yesthrow_t yes;
+static_assert(not noexcept(yesthrow_t(static_cast<const yesthrow_t&>(yes))), "");
+static_assert(not noexcept(yesthrow_t(static_cast<yesthrow_t&&>(yes))), "");
+static_assert(not noexcept(yes = static_cast<const yesthrow_t&>(yes)), "");
+static_assert(not noexcept(yes = static_cast<yesthrow_t&&>(yes)), "");
+
+// Note: this is value-initialisation, and thus by [dcl.init.general] p9
+// a trivial non-user-provided non-deleted default constructor is not called.
+static_assert(noexcept(yesthrow_t()), "");
+
+struct nothrow_t {
+  nothrow_t()                             noexcept(true) = default;
+  nothrow_t(const nothrow_t&)             noexcept(true) = default;
+  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
+  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
+  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
+};
+
+nothrow_t no;
+static_assert(noexcept(nothrow_t()), "");
+static_assert(noexcept(nothrow_t(static_cast<const nothrow_t&>(no))), "");
+static_assert(noexcept(nothrow_t(static_cast<nothrow_t&&>(no))), "");
+static_assert(noexcept(no = static_cast<const nothrow_t&>(no)), "");
+static_assert(noexcept(no = static_cast<nothrow_t&&>(no)), "");
+
diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
new file mode 100644
index 00000000000..b63b13ac52f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible7.C
@@ -0,0 +1,20 @@ 
+// { dg-do compile { target c++11 } }
+// PR c++/100470
+
+struct S1{
+    S1(S1&&) noexcept(false);
+};
+struct S2{
+    S2(S2&&) noexcept(false) = default;
+};
+struct S3{
+    S3(S3&&) noexcept(false){}
+};
+struct S4{
+    S4(S4&&) = default;
+};
+
+static_assert(!__is_nothrow_constructible(S1, S1), "");
+static_assert(!__is_nothrow_constructible(S2, S2), "");
+static_assert(!__is_nothrow_constructible(S3, S3), "");
+static_assert( __is_nothrow_constructible(S4, S4), "");
diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
new file mode 100644
index 00000000000..f23d48fa888
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
@@ -0,0 +1,63 @@ 
+// { dg-do compile { target c++11 } }
+// PR c++/96090
+
+template <typename T>
+constexpr bool is_nothrow_default_constructible_v
+  = __is_nothrow_constructible(T);
+template <typename T>
+constexpr bool is_nothrow_copy_constructible_v
+  = __is_nothrow_constructible(T, const T&);
+template <typename T>
+constexpr bool is_nothrow_move_constructible_v
+  = __is_nothrow_constructible(T, T&&);
+template <typename T>
+constexpr bool is_nothrow_copy_assignable_v
+  = __is_nothrow_assignable(T, const T&);
+template <typename T>
+constexpr bool is_nothrow_move_assignable_v
+  = __is_nothrow_assignable(T, T&&);
+
+struct yesthrow_t {
+  yesthrow_t()                              noexcept(false) = default;
+  yesthrow_t(const yesthrow_t&)             noexcept(false) = default;
+  yesthrow_t(yesthrow_t&&)                  noexcept(false) = default;
+  yesthrow_t& operator=(const yesthrow_t&)  noexcept(false) = default;
+  yesthrow_t& operator=(yesthrow_t&&)       noexcept(false) = default;
+};
+
+static_assert(not is_nothrow_copy_constructible_v<yesthrow_t>, "");
+static_assert(not is_nothrow_copy_assignable_v<yesthrow_t>, "");
+static_assert(not is_nothrow_move_constructible_v<yesthrow_t>, "");
+static_assert(not is_nothrow_move_assignable_v<yesthrow_t>, "");
+
+// Note: by [meta.unary.prop] p9 this should be value-initialisation,
+// and thus by [dcl.init.general] p9 a trivial non-user-provided
+// non-deleted default constructor is not called.
+static_assert(is_nothrow_default_constructible_v<yesthrow_t>, "");
+
+struct nothrow_t {
+  nothrow_t()                             noexcept(true) = default;
+  nothrow_t(const nothrow_t&)             noexcept(true) = default;
+  nothrow_t(nothrow_t&&)                  noexcept(true) = default;
+  nothrow_t& operator=(const nothrow_t&)  noexcept(true) = default;
+  nothrow_t& operator=(nothrow_t&&)       noexcept(true) = default;
+};
+
+static_assert(is_nothrow_default_constructible_v<nothrow_t>, "");
+static_assert(is_nothrow_copy_constructible_v<nothrow_t>, "");
+static_assert(is_nothrow_copy_assignable_v<nothrow_t>, "");
+static_assert(is_nothrow_move_constructible_v<nothrow_t>, "");
+static_assert(is_nothrow_move_assignable_v<nothrow_t>, "");
+
+struct A { A() noexcept(false) = default; };
+struct B { B(const B&) noexcept(false) = default; };
+struct C { C(C&&) noexcept(false) = default; };
+struct D { D& operator=(const D&) noexcept(false) = default; };
+struct E { E& operator=(E&&) noexcept(false) = default; };
+
+static_assert(is_nothrow_default_constructible_v<A>, "");  // see above
+static_assert(not is_nothrow_copy_constructible_v<B>, "");
+static_assert(not is_nothrow_move_constructible_v<C>, "");
+static_assert(not is_nothrow_copy_assignable_v<D>, "");
+static_assert(not is_nothrow_move_assignable_v<E>, "");
+