[gcc r14-6395] c++: Fix noexcept checking for trivial operations [PR96090]

Nathaniel Shead nshead@gcc.gnu.org
Mon Dec 11 02:34:29 GMT 2023


https://gcc.gnu.org/g:4719b6f5ae4d758f193a17bbd5fb6cbacd702a23

commit r14-6395-g4719b6f5ae4d758f193a17bbd5fb6cbacd702a23
Author: Nathaniel Shead <nathanieloshead@gmail.com>
Date:   Sat Oct 28 16:04:52 2023 +1100

    c++: Fix noexcept checking for trivial operations [PR96090]
    
    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>

Diff:
---
 gcc/cp/call.cc                                     | 17 +++---
 gcc/cp/method.cc                                   | 19 +++++--
 gcc/testsuite/g++.dg/cpp0x/noexcept81.C            | 37 +++++++++++++
 .../g++.dg/ext/is_nothrow_constructible7.C         | 20 +++++++
 .../g++.dg/ext/is_nothrow_constructible8.C         | 64 ++++++++++++++++++++++
 5 files changed, 143 insertions(+), 14 deletions(-)

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..8310f7d910a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/noexcept81.C
@@ -0,0 +1,37 @@
+// { 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.
+// However, CWG2820 proposes to change this behaviour.
+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..c2a0b93ae97
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/is_nothrow_constructible8.C
@@ -0,0 +1,64 @@
+// { 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.
+// However, CWG2820 proposes to change this behaviour.
+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>, "");
+


More information about the Gcc-cvs mailing list