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. 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 <nathanielosh...@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 >