diff mbox series

c++: noexcept and copy elision [PR109030]

Message ID 20230306235957.390533-1-polacek@redhat.com
State New
Headers show
Series c++: noexcept and copy elision [PR109030] | expand

Commit Message

Marek Polacek March 6, 2023, 11:59 p.m. UTC
When processing a noexcept, constructors aren't elided: build_over_call
has
	 /* 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))
so the assert I added recently needs to be relaxed a little bit.

Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

	PR c++/109030

gcc/cp/ChangeLog:

	* constexpr.cc (cxx_eval_call_expression): Relax assert.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/noexcept77.C: New test.
---
 gcc/cp/constexpr.cc                     | 6 +++++-
 gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
 2 files changed, 14 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C


base-commit: dfb14cdd796ad9df6b5f2def047ef36b29385902

Comments

Jason Merrill March 7, 2023, 2:55 p.m. UTC | #1
On 3/6/23 18:59, Marek Polacek wrote:
> When processing a noexcept, constructors aren't elided: build_over_call
> has
> 	 /* 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))
> so the assert I added recently needs to be relaxed a little bit.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

OK.

> 	PR c++/109030
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/noexcept77.C: New test.
> ---
>   gcc/cp/constexpr.cc                     | 6 +++++-
>   gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
>   2 files changed, 14 insertions(+), 1 deletion(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> 
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index 364695b762c..5384d0e8e46 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
>   
>     /* We used to shortcut trivial constructor/op= here, but nowadays
>        we can only get a trivial function here with -fno-elide-constructors.  */
> -  gcc_checking_assert (!trivial_fn_p (fun) || !flag_elide_constructors);
> +  gcc_checking_assert (!trivial_fn_p (fun)
> +		       || !flag_elide_constructors
> +		       /* We don't elide constructors when processing
> +			  a noexcept-expression.  */
> +		       || cp_noexcept_operand);
>   
>     bool non_constant_args = false;
>     new_call.bindings
> diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept77.C b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> new file mode 100644
> index 00000000000..16db8eb79ee
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> @@ -0,0 +1,9 @@
> +// PR c++/109030
> +// { dg-do compile { target c++11 } }
> +
> +struct foo { };
> +
> +struct __as_receiver {
> +  foo empty_env;
> +};
> +void sched(foo __fun) noexcept(noexcept(__as_receiver{__fun})) { }
> 
> base-commit: dfb14cdd796ad9df6b5f2def047ef36b29385902
Patrick Palka March 9, 2023, 7:32 p.m. UTC | #2
On Mon, 6 Mar 2023, Marek Polacek via Gcc-patches wrote:

> When processing a noexcept, constructors aren't elided: build_over_call
> has
> 	 /* 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))
> so the assert I added recently needs to be relaxed a little bit.
> 
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> 	PR c++/109030
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/noexcept77.C: New test.
> ---
>  gcc/cp/constexpr.cc                     | 6 +++++-
>  gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
>  2 files changed, 14 insertions(+), 1 deletion(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> 
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index 364695b762c..5384d0e8e46 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
>  
>    /* We used to shortcut trivial constructor/op= here, but nowadays
>       we can only get a trivial function here with -fno-elide-constructors.  */
> -  gcc_checking_assert (!trivial_fn_p (fun) || !flag_elide_constructors);
> +  gcc_checking_assert (!trivial_fn_p (fun)
> +		       || !flag_elide_constructors
> +		       /* We don't elide constructors when processing
> +			  a noexcept-expression.  */
> +		       || cp_noexcept_operand);

It seems weird that we're performing constant evaluation within an
unevaluated operand.  Would it make sense to also fix this a second way
by avoiding constant evaluation from maybe_constant_init when
cp_unevaluated_operand && !manifestly_const_eval, like in maybe_constant_value?
IIUC since we could still have an evaluated subexpression withis
noexcept, the two fixes would be complementary.

>  
>    bool non_constant_args = false;
>    new_call.bindings
> diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept77.C b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> new file mode 100644
> index 00000000000..16db8eb79ee
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> @@ -0,0 +1,9 @@
> +// PR c++/109030
> +// { dg-do compile { target c++11 } }
> +
> +struct foo { };
> +
> +struct __as_receiver {
> +  foo empty_env;
> +};
> +void sched(foo __fun) noexcept(noexcept(__as_receiver{__fun})) { }
> 
> base-commit: dfb14cdd796ad9df6b5f2def047ef36b29385902
> -- 
> 2.39.2
> 
>
Jason Merrill March 9, 2023, 11:12 p.m. UTC | #3
On 3/9/23 14:32, Patrick Palka wrote:
> On Mon, 6 Mar 2023, Marek Polacek via Gcc-patches wrote:
> 
>> When processing a noexcept, constructors aren't elided: build_over_call
>> has
>> 	 /* 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))
>> so the assert I added recently needs to be relaxed a little bit.
>>
>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>
>> 	PR c++/109030
>>
>> gcc/cp/ChangeLog:
>>
>> 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	* g++.dg/cpp0x/noexcept77.C: New test.
>> ---
>>   gcc/cp/constexpr.cc                     | 6 +++++-
>>   gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
>>   2 files changed, 14 insertions(+), 1 deletion(-)
>>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
>>
>> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
>> index 364695b762c..5384d0e8e46 100644
>> --- a/gcc/cp/constexpr.cc
>> +++ b/gcc/cp/constexpr.cc
>> @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
>>   
>>     /* We used to shortcut trivial constructor/op= here, but nowadays
>>        we can only get a trivial function here with -fno-elide-constructors.  */
>> -  gcc_checking_assert (!trivial_fn_p (fun) || !flag_elide_constructors);
>> +  gcc_checking_assert (!trivial_fn_p (fun)
>> +		       || !flag_elide_constructors
>> +		       /* We don't elide constructors when processing
>> +			  a noexcept-expression.  */
>> +		       || cp_noexcept_operand);
> 
> It seems weird that we're performing constant evaluation within an
> unevaluated operand.  Would it make sense to also fix this a second way
> by avoiding constant evaluation from maybe_constant_init when
> cp_unevaluated_operand && !manifestly_const_eval, like in maybe_constant_value?

Sounds good.

> IIUC since we could still have an evaluated subexpression withis
> noexcept, the two fixes would be complementary.
> 
>>   
>>     bool non_constant_args = false;
>>     new_call.bindings
>> diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept77.C b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
>> new file mode 100644
>> index 00000000000..16db8eb79ee
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
>> @@ -0,0 +1,9 @@
>> +// PR c++/109030
>> +// { dg-do compile { target c++11 } }
>> +
>> +struct foo { };
>> +
>> +struct __as_receiver {
>> +  foo empty_env;
>> +};
>> +void sched(foo __fun) noexcept(noexcept(__as_receiver{__fun})) { }
>>
>> base-commit: dfb14cdd796ad9df6b5f2def047ef36b29385902
>> -- 
>> 2.39.2
>>
>>
>
Patrick Palka March 15, 2023, 11:47 p.m. UTC | #4
On Thu, 9 Mar 2023, Jason Merrill wrote:

> On 3/9/23 14:32, Patrick Palka wrote:
> > On Mon, 6 Mar 2023, Marek Polacek via Gcc-patches wrote:
> > 
> > > When processing a noexcept, constructors aren't elided: build_over_call
> > > has
> > > 	 /* 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))
> > > so the assert I added recently needs to be relaxed a little bit.
> > > 
> > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > 
> > > 	PR c++/109030
> > > 
> > > gcc/cp/ChangeLog:
> > > 
> > > 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > > 	* g++.dg/cpp0x/noexcept77.C: New test.
> > > ---
> > >   gcc/cp/constexpr.cc                     | 6 +++++-
> > >   gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
> > >   2 files changed, 14 insertions(+), 1 deletion(-)
> > >   create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> > > 
> > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > > index 364695b762c..5384d0e8e46 100644
> > > --- a/gcc/cp/constexpr.cc
> > > +++ b/gcc/cp/constexpr.cc
> > > @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
> > > tree t,
> > >       /* We used to shortcut trivial constructor/op= here, but nowadays
> > >        we can only get a trivial function here with
> > > -fno-elide-constructors.  */
> > > -  gcc_checking_assert (!trivial_fn_p (fun) || !flag_elide_constructors);
> > > +  gcc_checking_assert (!trivial_fn_p (fun)
> > > +		       || !flag_elide_constructors
> > > +		       /* We don't elide constructors when processing
> > > +			  a noexcept-expression.  */
> > > +		       || cp_noexcept_operand);
> > 
> > It seems weird that we're performing constant evaluation within an
> > unevaluated operand.  Would it make sense to also fix this a second way
> > by avoiding constant evaluation from maybe_constant_init when
> > cp_unevaluated_operand && !manifestly_const_eval, like in
> > maybe_constant_value?
> 
> Sounds good.

Hmm, while working on this I noticed we currently don't reject a version of
g++.dg/cpp2a/constexpr-inst1.C that list initializes an aggregate instead of
int (ever since r12-4425-g1595fe44e11a96):

  struct A { int m; };
  template<typename T> constexpr int f() { return T::value; }
  template<bool B, typename T> void h(decltype(A{B ? f<T>() : 0})); // was int{...}
  template<bool B, typename T> void h(...);
  void x() {
    h<false, int>(0); // OK?
  }

ISTM we should instantiate f<int> here for the same reason we do in the
original version of the testcase, and for that to happen we need to
pass manifestly_const_eval=true in massage_init_elt.  Does that seem
reasonable?
Patrick Palka March 16, 2023, 2:09 p.m. UTC | #5
On Wed, 15 Mar 2023, Patrick Palka wrote:

> On Thu, 9 Mar 2023, Jason Merrill wrote:
> 
> > On 3/9/23 14:32, Patrick Palka wrote:
> > > On Mon, 6 Mar 2023, Marek Polacek via Gcc-patches wrote:
> > > 
> > > > When processing a noexcept, constructors aren't elided: build_over_call
> > > > has
> > > > 	 /* 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))
> > > > so the assert I added recently needs to be relaxed a little bit.
> > > > 
> > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > > 
> > > > 	PR c++/109030
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > > 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
> > > > 
> > > > gcc/testsuite/ChangeLog:
> > > > 
> > > > 	* g++.dg/cpp0x/noexcept77.C: New test.
> > > > ---
> > > >   gcc/cp/constexpr.cc                     | 6 +++++-
> > > >   gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
> > > >   2 files changed, 14 insertions(+), 1 deletion(-)
> > > >   create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> > > > 
> > > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > > > index 364695b762c..5384d0e8e46 100644
> > > > --- a/gcc/cp/constexpr.cc
> > > > +++ b/gcc/cp/constexpr.cc
> > > > @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
> > > > tree t,
> > > >       /* We used to shortcut trivial constructor/op= here, but nowadays
> > > >        we can only get a trivial function here with
> > > > -fno-elide-constructors.  */
> > > > -  gcc_checking_assert (!trivial_fn_p (fun) || !flag_elide_constructors);
> > > > +  gcc_checking_assert (!trivial_fn_p (fun)
> > > > +		       || !flag_elide_constructors
> > > > +		       /* We don't elide constructors when processing
> > > > +			  a noexcept-expression.  */
> > > > +		       || cp_noexcept_operand);
> > > 
> > > It seems weird that we're performing constant evaluation within an
> > > unevaluated operand.  Would it make sense to also fix this a second way
> > > by avoiding constant evaluation from maybe_constant_init when
> > > cp_unevaluated_operand && !manifestly_const_eval, like in
> > > maybe_constant_value?
> > 
> > Sounds good.
> 
> Hmm, while working on this I noticed we currently don't reject a version of
> g++.dg/cpp2a/constexpr-inst1.C that list initializes an aggregate instead of
> int (ever since r12-4425-g1595fe44e11a96):
> 
>   struct A { int m; };
>   template<typename T> constexpr int f() { return T::value; }
>   template<bool B, typename T> void h(decltype(A{B ? f<T>() : 0})); // was int{...}
>   template<bool B, typename T> void h(...);
>   void x() {
>     h<false, int>(0); // OK?
>   }
> 
> ISTM we should instantiate f<int> here for the same reason we do in the
> original version of the testcase, and for that to happen we need to
> pass manifestly_const_eval=true in massage_init_elt.  Does that seem
> reasonable?
> 

FWIW the reason this came up is because I tried contriving a testcase
for the aforementioned maybe_constant_init change, and I came up with:

  struct __as_receiver {
    int empty_env;
  };

  template<class T>
  constexpr int f(T t) {
    return t.fail;
  };

  using type = decltype(__as_receiver{f(0)}); // OK, f<int> no longer instantiated

which we used to reject and afterwards accept.  But since the elements
of an initializer list are potentially constant evaluated, I wonder if
that that means f<int> should be instantiated here after all despite the
unevaluated context?

Here's the full patch for reference:

-- >8 --

Subject: [PATCH] c++: maybe_constant_init and unevaluated operands [PR109030]

This testcase in this PR (already fixed by r13-6526-ge4692319fd5fc7)
illustrates that maybe_constant_init can be called on an unevaluated
operand (from massage_init_elt), so this entry point should limit
constant evaluation in that case, like maybe_constant_value does.

	PR c++/109030

gcc/cp/ChangeLog:

	* constexpr.cc (maybe_constant_init_1): For an unevaluated
	non-manifestly-constant operand, don't constant evaluate
	and instead call fold_to_constant.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/decltype83.C: New test.
---
 gcc/cp/constexpr.cc                     |  2 ++
 gcc/testsuite/g++.dg/cpp0x/decltype83.C | 14 ++++++++++++++
 2 files changed, 16 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/decltype83.C

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 8683c00596a..f325af375c8 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -8795,6 +8795,8 @@ maybe_constant_init_1 (tree t, tree decl, bool allow_non_constant,
 			&& (TREE_STATIC (decl) || DECL_EXTERNAL (decl)));
       if (is_static)
 	manifestly_const_eval = true;
+      if (cp_unevaluated_operand && !manifestly_const_eval)
+	return fold_to_constant (t);
       t = cxx_eval_outermost_constant_expr (t, allow_non_constant, !is_static,
 					    mce_value (manifestly_const_eval),
 					    false, decl);
diff --git a/gcc/testsuite/g++.dg/cpp0x/decltype83.C b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
new file mode 100644
index 00000000000..17005a92eb5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
@@ -0,0 +1,14 @@
+// { dg-do compile { target c++11 } }
+
+struct __as_receiver {
+  int empty_env;
+};
+
+template<class T>
+constexpr int f(T t) {
+  return t.fail;
+};
+
+int main() {
+  using type = decltype(__as_receiver{f(0)}); // OK, f<int> not instantiated
+}
Jason Merrill March 16, 2023, 2:38 p.m. UTC | #6
On 3/16/23 10:09, Patrick Palka wrote:
> On Wed, 15 Mar 2023, Patrick Palka wrote:
> 
>> On Thu, 9 Mar 2023, Jason Merrill wrote:
>>
>>> On 3/9/23 14:32, Patrick Palka wrote:
>>>> On Mon, 6 Mar 2023, Marek Polacek via Gcc-patches wrote:
>>>>
>>>>> When processing a noexcept, constructors aren't elided: build_over_call
>>>>> has
>>>>> 	 /* 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))
>>>>> so the assert I added recently needs to be relaxed a little bit.
>>>>>
>>>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>>>
>>>>> 	PR c++/109030
>>>>>
>>>>> gcc/cp/ChangeLog:
>>>>>
>>>>> 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>> 	* g++.dg/cpp0x/noexcept77.C: New test.
>>>>> ---
>>>>>    gcc/cp/constexpr.cc                     | 6 +++++-
>>>>>    gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
>>>>>    2 files changed, 14 insertions(+), 1 deletion(-)
>>>>>    create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
>>>>>
>>>>> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
>>>>> index 364695b762c..5384d0e8e46 100644
>>>>> --- a/gcc/cp/constexpr.cc
>>>>> +++ b/gcc/cp/constexpr.cc
>>>>> @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
>>>>> tree t,
>>>>>        /* We used to shortcut trivial constructor/op= here, but nowadays
>>>>>         we can only get a trivial function here with
>>>>> -fno-elide-constructors.  */
>>>>> -  gcc_checking_assert (!trivial_fn_p (fun) || !flag_elide_constructors);
>>>>> +  gcc_checking_assert (!trivial_fn_p (fun)
>>>>> +		       || !flag_elide_constructors
>>>>> +		       /* We don't elide constructors when processing
>>>>> +			  a noexcept-expression.  */
>>>>> +		       || cp_noexcept_operand);
>>>>
>>>> It seems weird that we're performing constant evaluation within an
>>>> unevaluated operand.  Would it make sense to also fix this a second way
>>>> by avoiding constant evaluation from maybe_constant_init when
>>>> cp_unevaluated_operand && !manifestly_const_eval, like in
>>>> maybe_constant_value?
>>>
>>> Sounds good.
>>
>> Hmm, while working on this I noticed we currently don't reject a version of
>> g++.dg/cpp2a/constexpr-inst1.C that list initializes an aggregate instead of
>> int (ever since r12-4425-g1595fe44e11a96):
>>
>>    struct A { int m; };
>>    template<typename T> constexpr int f() { return T::value; }
>>    template<bool B, typename T> void h(decltype(A{B ? f<T>() : 0})); // was int{...}
>>    template<bool B, typename T> void h(...);
>>    void x() {
>>      h<false, int>(0); // OK?
>>    }
>>
>> ISTM we should instantiate f<int> here for the same reason we do in the
>> original version of the testcase, and for that to happen we need to
>> pass manifestly_const_eval=true in massage_init_elt.  Does that seem
>> reasonable?
>>
> 
> FWIW the reason this came up is because I tried contriving a testcase
> for the aforementioned maybe_constant_init change, and I came up with:
> 
>    struct __as_receiver {
>      int empty_env;
>    };
> 
>    template<class T>
>    constexpr int f(T t) {
>      return t.fail;
>    };
> 
>    using type = decltype(__as_receiver{f(0)}); // OK, f<int> no longer instantiated
> 
> which we used to reject and afterwards accept.  But since the elements
> of an initializer list are potentially constant evaluated, I wonder if
> that that means f<int> should be instantiated here after all despite the
> unevaluated context?

The relevant section of the standard would seem to be
https://eel.is/c++draft/expr.const#20 ; an immediate subexpression of a 
braced-init-list is potentially constant-evaluated even though it isn't 
potentially-evaluated or manifestly constant-evaluated.

It seems like the call to fold_non_dependent_expr in check_narrowing 
ought to cause instantiation in this case, why doesn't it?

> Here's the full patch for reference:
> 
> -- >8 --
> 
> Subject: [PATCH] c++: maybe_constant_init and unevaluated operands [PR109030]
> 
> This testcase in this PR (already fixed by r13-6526-ge4692319fd5fc7)
> illustrates that maybe_constant_init can be called on an unevaluated
> operand (from massage_init_elt), so this entry point should limit
> constant evaluation in that case, like maybe_constant_value does.
> 
> 	PR c++/109030
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.cc (maybe_constant_init_1): For an unevaluated
> 	non-manifestly-constant operand, don't constant evaluate
> 	and instead call fold_to_constant.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/decltype83.C: New test.
> ---
>   gcc/cp/constexpr.cc                     |  2 ++
>   gcc/testsuite/g++.dg/cpp0x/decltype83.C | 14 ++++++++++++++
>   2 files changed, 16 insertions(+)
>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/decltype83.C
> 
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index 8683c00596a..f325af375c8 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -8795,6 +8795,8 @@ maybe_constant_init_1 (tree t, tree decl, bool allow_non_constant,
>   			&& (TREE_STATIC (decl) || DECL_EXTERNAL (decl)));
>         if (is_static)
>   	manifestly_const_eval = true;
> +      if (cp_unevaluated_operand && !manifestly_const_eval)
> +	return fold_to_constant (t);
>         t = cxx_eval_outermost_constant_expr (t, allow_non_constant, !is_static,
>   					    mce_value (manifestly_const_eval),
>   					    false, decl);
> diff --git a/gcc/testsuite/g++.dg/cpp0x/decltype83.C b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
> new file mode 100644
> index 00000000000..17005a92eb5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
> @@ -0,0 +1,14 @@
> +// { dg-do compile { target c++11 } }
> +
> +struct __as_receiver {
> +  int empty_env;
> +};
> +
> +template<class T>
> +constexpr int f(T t) {
> +  return t.fail;
> +};
> +
> +int main() {
> +  using type = decltype(__as_receiver{f(0)}); // OK, f<int> not instantiated
> +}
Patrick Palka March 16, 2023, 3:48 p.m. UTC | #7
On Thu, 16 Mar 2023, Jason Merrill wrote:

> On 3/16/23 10:09, Patrick Palka wrote:
> > On Wed, 15 Mar 2023, Patrick Palka wrote:
> > 
> > > On Thu, 9 Mar 2023, Jason Merrill wrote:
> > > 
> > > > On 3/9/23 14:32, Patrick Palka wrote:
> > > > > On Mon, 6 Mar 2023, Marek Polacek via Gcc-patches wrote:
> > > > > 
> > > > > > When processing a noexcept, constructors aren't elided:
> > > > > > build_over_call
> > > > > > has
> > > > > > 	 /* 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))
> > > > > > so the assert I added recently needs to be relaxed a little bit.
> > > > > > 
> > > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > > > > 
> > > > > > 	PR c++/109030
> > > > > > 
> > > > > > gcc/cp/ChangeLog:
> > > > > > 
> > > > > > 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
> > > > > > 
> > > > > > gcc/testsuite/ChangeLog:
> > > > > > 
> > > > > > 	* g++.dg/cpp0x/noexcept77.C: New test.
> > > > > > ---
> > > > > >    gcc/cp/constexpr.cc                     | 6 +++++-
> > > > > >    gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
> > > > > >    2 files changed, 14 insertions(+), 1 deletion(-)
> > > > > >    create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
> > > > > > 
> > > > > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > > > > > index 364695b762c..5384d0e8e46 100644
> > > > > > --- a/gcc/cp/constexpr.cc
> > > > > > +++ b/gcc/cp/constexpr.cc
> > > > > > @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx
> > > > > > *ctx,
> > > > > > tree t,
> > > > > >        /* We used to shortcut trivial constructor/op= here, but
> > > > > > nowadays
> > > > > >         we can only get a trivial function here with
> > > > > > -fno-elide-constructors.  */
> > > > > > -  gcc_checking_assert (!trivial_fn_p (fun) ||
> > > > > > !flag_elide_constructors);
> > > > > > +  gcc_checking_assert (!trivial_fn_p (fun)
> > > > > > +		       || !flag_elide_constructors
> > > > > > +		       /* We don't elide constructors when processing
> > > > > > +			  a noexcept-expression.  */
> > > > > > +		       || cp_noexcept_operand);
> > > > > 
> > > > > It seems weird that we're performing constant evaluation within an
> > > > > unevaluated operand.  Would it make sense to also fix this a second
> > > > > way
> > > > > by avoiding constant evaluation from maybe_constant_init when
> > > > > cp_unevaluated_operand && !manifestly_const_eval, like in
> > > > > maybe_constant_value?
> > > > 
> > > > Sounds good.
> > > 
> > > Hmm, while working on this I noticed we currently don't reject a version
> > > of
> > > g++.dg/cpp2a/constexpr-inst1.C that list initializes an aggregate instead
> > > of
> > > int (ever since r12-4425-g1595fe44e11a96):
> > > 
> > >    struct A { int m; };
> > >    template<typename T> constexpr int f() { return T::value; }
> > >    template<bool B, typename T> void h(decltype(A{B ? f<T>() : 0})); //
> > > was int{...}
> > >    template<bool B, typename T> void h(...);
> > >    void x() {
> > >      h<false, int>(0); // OK?
> > >    }
> > > 
> > > ISTM we should instantiate f<int> here for the same reason we do in the
> > > original version of the testcase, and for that to happen we need to
> > > pass manifestly_const_eval=true in massage_init_elt.  Does that seem
> > > reasonable?
> > > 
> > 
> > FWIW the reason this came up is because I tried contriving a testcase
> > for the aforementioned maybe_constant_init change, and I came up with:
> > 
> >    struct __as_receiver {
> >      int empty_env;
> >    };
> > 
> >    template<class T>
> >    constexpr int f(T t) {
> >      return t.fail;
> >    };
> > 
> >    using type = decltype(__as_receiver{f(0)}); // OK, f<int> no longer
> > instantiated
> > 
> > which we used to reject and afterwards accept.  But since the elements
> > of an initializer list are potentially constant evaluated, I wonder if
> > that that means f<int> should be instantiated here after all despite the
> > unevaluated context?
> 
> The relevant section of the standard would seem to be
> https://eel.is/c++draft/expr.const#20 ; an immediate subexpression of a
> braced-init-list is potentially constant-evaluated even though it isn't
> potentially-evaluated or manifestly constant-evaluated.
> 
> It seems like the call to fold_non_dependent_expr in check_narrowing ought to
> cause instantiation in this case, why doesn't it?

Looks like check_narrowing isn't called at all in this aggr init case.
The call from e.g. convert_like_internal isn't reached because the
conversion for the initializer element is ck_identity, and don't ever
set conversion::check_narrowing for ck_identity conversions I think.

Yet for using 'type = decltype(int{f(0)});' (similar to the example in
[temp.inst]/8) we do call check_narrowing directly from
finish_compound_literal, despite the conversion effectively being an
identity conversion.

> 
> > Here's the full patch for reference:
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: maybe_constant_init and unevaluated operands
> > [PR109030]
> > 
> > This testcase in this PR (already fixed by r13-6526-ge4692319fd5fc7)
> > illustrates that maybe_constant_init can be called on an unevaluated
> > operand (from massage_init_elt), so this entry point should limit
> > constant evaluation in that case, like maybe_constant_value does.
> > 
> > 	PR c++/109030
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constexpr.cc (maybe_constant_init_1): For an unevaluated
> > 	non-manifestly-constant operand, don't constant evaluate
> > 	and instead call fold_to_constant.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp0x/decltype83.C: New test.
> > ---
> >   gcc/cp/constexpr.cc                     |  2 ++
> >   gcc/testsuite/g++.dg/cpp0x/decltype83.C | 14 ++++++++++++++
> >   2 files changed, 16 insertions(+)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp0x/decltype83.C
> > 
> > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > index 8683c00596a..f325af375c8 100644
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -8795,6 +8795,8 @@ maybe_constant_init_1 (tree t, tree decl, bool
> > allow_non_constant,
> >   			&& (TREE_STATIC (decl) || DECL_EXTERNAL (decl)));
> >         if (is_static)
> >   	manifestly_const_eval = true;
> > +      if (cp_unevaluated_operand && !manifestly_const_eval)
> > +	return fold_to_constant (t);
> >         t = cxx_eval_outermost_constant_expr (t, allow_non_constant,
> > !is_static,
> >   					    mce_value (manifestly_const_eval),
> >   					    false, decl);
> > diff --git a/gcc/testsuite/g++.dg/cpp0x/decltype83.C
> > b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
> > new file mode 100644
> > index 00000000000..17005a92eb5
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
> > @@ -0,0 +1,14 @@
> > +// { dg-do compile { target c++11 } }
> > +
> > +struct __as_receiver {
> > +  int empty_env;
> > +};
> > +
> > +template<class T>
> > +constexpr int f(T t) {
> > +  return t.fail;
> > +};
> > +
> > +int main() {
> > +  using type = decltype(__as_receiver{f(0)}); // OK, f<int> not
> > instantiated
> > +}
> 
>
Jason Merrill March 16, 2023, 3:59 p.m. UTC | #8
On 3/16/23 11:48, Patrick Palka wrote:
> On Thu, 16 Mar 2023, Jason Merrill wrote:
> 
>> On 3/16/23 10:09, Patrick Palka wrote:
>>> On Wed, 15 Mar 2023, Patrick Palka wrote:
>>>
>>>> On Thu, 9 Mar 2023, Jason Merrill wrote:
>>>>
>>>>> On 3/9/23 14:32, Patrick Palka wrote:
>>>>>> On Mon, 6 Mar 2023, Marek Polacek via Gcc-patches wrote:
>>>>>>
>>>>>>> When processing a noexcept, constructors aren't elided:
>>>>>>> build_over_call
>>>>>>> has
>>>>>>> 	 /* 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))
>>>>>>> so the assert I added recently needs to be relaxed a little bit.
>>>>>>>
>>>>>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>>>>>
>>>>>>> 	PR c++/109030
>>>>>>>
>>>>>>> gcc/cp/ChangeLog:
>>>>>>>
>>>>>>> 	* constexpr.cc (cxx_eval_call_expression): Relax assert.
>>>>>>>
>>>>>>> gcc/testsuite/ChangeLog:
>>>>>>>
>>>>>>> 	* g++.dg/cpp0x/noexcept77.C: New test.
>>>>>>> ---
>>>>>>>     gcc/cp/constexpr.cc                     | 6 +++++-
>>>>>>>     gcc/testsuite/g++.dg/cpp0x/noexcept77.C | 9 +++++++++
>>>>>>>     2 files changed, 14 insertions(+), 1 deletion(-)
>>>>>>>     create mode 100644 gcc/testsuite/g++.dg/cpp0x/noexcept77.C
>>>>>>>
>>>>>>> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
>>>>>>> index 364695b762c..5384d0e8e46 100644
>>>>>>> --- a/gcc/cp/constexpr.cc
>>>>>>> +++ b/gcc/cp/constexpr.cc
>>>>>>> @@ -2869,7 +2869,11 @@ cxx_eval_call_expression (const constexpr_ctx
>>>>>>> *ctx,
>>>>>>> tree t,
>>>>>>>         /* We used to shortcut trivial constructor/op= here, but
>>>>>>> nowadays
>>>>>>>          we can only get a trivial function here with
>>>>>>> -fno-elide-constructors.  */
>>>>>>> -  gcc_checking_assert (!trivial_fn_p (fun) ||
>>>>>>> !flag_elide_constructors);
>>>>>>> +  gcc_checking_assert (!trivial_fn_p (fun)
>>>>>>> +		       || !flag_elide_constructors
>>>>>>> +		       /* We don't elide constructors when processing
>>>>>>> +			  a noexcept-expression.  */
>>>>>>> +		       || cp_noexcept_operand);
>>>>>>
>>>>>> It seems weird that we're performing constant evaluation within an
>>>>>> unevaluated operand.  Would it make sense to also fix this a second
>>>>>> way
>>>>>> by avoiding constant evaluation from maybe_constant_init when
>>>>>> cp_unevaluated_operand && !manifestly_const_eval, like in
>>>>>> maybe_constant_value?
>>>>>
>>>>> Sounds good.
>>>>
>>>> Hmm, while working on this I noticed we currently don't reject a version
>>>> of
>>>> g++.dg/cpp2a/constexpr-inst1.C that list initializes an aggregate instead
>>>> of
>>>> int (ever since r12-4425-g1595fe44e11a96):
>>>>
>>>>     struct A { int m; };
>>>>     template<typename T> constexpr int f() { return T::value; }
>>>>     template<bool B, typename T> void h(decltype(A{B ? f<T>() : 0})); //
>>>> was int{...}
>>>>     template<bool B, typename T> void h(...);
>>>>     void x() {
>>>>       h<false, int>(0); // OK?
>>>>     }
>>>>
>>>> ISTM we should instantiate f<int> here for the same reason we do in the
>>>> original version of the testcase, and for that to happen we need to
>>>> pass manifestly_const_eval=true in massage_init_elt.  Does that seem
>>>> reasonable?
>>>>
>>>
>>> FWIW the reason this came up is because I tried contriving a testcase
>>> for the aforementioned maybe_constant_init change, and I came up with:
>>>
>>>     struct __as_receiver {
>>>       int empty_env;
>>>     };
>>>
>>>     template<class T>
>>>     constexpr int f(T t) {
>>>       return t.fail;
>>>     };
>>>
>>>     using type = decltype(__as_receiver{f(0)}); // OK, f<int> no longer
>>> instantiated
>>>
>>> which we used to reject and afterwards accept.  But since the elements
>>> of an initializer list are potentially constant evaluated, I wonder if
>>> that that means f<int> should be instantiated here after all despite the
>>> unevaluated context?
>>
>> The relevant section of the standard would seem to be
>> https://eel.is/c++draft/expr.const#20 ; an immediate subexpression of a
>> braced-init-list is potentially constant-evaluated even though it isn't
>> potentially-evaluated or manifestly constant-evaluated.
>>
>> It seems like the call to fold_non_dependent_expr in check_narrowing ought to
>> cause instantiation in this case, why doesn't it?
> 
> Looks like check_narrowing isn't called at all in this aggr init case.
> The call from e.g. convert_like_internal isn't reached because the
> conversion for the initializer element is ck_identity, and don't ever
> set conversion::check_narrowing for ck_identity conversions I think.

Ah, yes, that makes sense; an identity conversion can never be 
narrowing, so we don't care about the constant value.  So not 
instantiating seems correct, and the patch is OK.

> Yet for using 'type = decltype(int{f(0)});' (similar to the example in
> [temp.inst]/8) we do call check_narrowing directly from
> finish_compound_literal, despite the conversion effectively being an
> identity conversion.

Hmm, maybe check_narrowing should defer constant evaluation until after 
deciding that the target type is not a superset of the source type...

>>> Here's the full patch for reference:
>>>
>>> -- >8 --
>>>
>>> Subject: [PATCH] c++: maybe_constant_init and unevaluated operands
>>> [PR109030]
>>>
>>> This testcase in this PR (already fixed by r13-6526-ge4692319fd5fc7)
>>> illustrates that maybe_constant_init can be called on an unevaluated
>>> operand (from massage_init_elt), so this entry point should limit
>>> constant evaluation in that case, like maybe_constant_value does.
>>>
>>> 	PR c++/109030
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* constexpr.cc (maybe_constant_init_1): For an unevaluated
>>> 	non-manifestly-constant operand, don't constant evaluate
>>> 	and instead call fold_to_constant.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/cpp0x/decltype83.C: New test.
>>> ---
>>>    gcc/cp/constexpr.cc                     |  2 ++
>>>    gcc/testsuite/g++.dg/cpp0x/decltype83.C | 14 ++++++++++++++
>>>    2 files changed, 16 insertions(+)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp0x/decltype83.C
>>>
>>> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
>>> index 8683c00596a..f325af375c8 100644
>>> --- a/gcc/cp/constexpr.cc
>>> +++ b/gcc/cp/constexpr.cc
>>> @@ -8795,6 +8795,8 @@ maybe_constant_init_1 (tree t, tree decl, bool
>>> allow_non_constant,
>>>    			&& (TREE_STATIC (decl) || DECL_EXTERNAL (decl)));
>>>          if (is_static)
>>>    	manifestly_const_eval = true;
>>> +      if (cp_unevaluated_operand && !manifestly_const_eval)
>>> +	return fold_to_constant (t);
>>>          t = cxx_eval_outermost_constant_expr (t, allow_non_constant,
>>> !is_static,
>>>    					    mce_value (manifestly_const_eval),
>>>    					    false, decl);
>>> diff --git a/gcc/testsuite/g++.dg/cpp0x/decltype83.C
>>> b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
>>> new file mode 100644
>>> index 00000000000..17005a92eb5
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp0x/decltype83.C
>>> @@ -0,0 +1,14 @@
>>> +// { dg-do compile { target c++11 } }
>>> +
>>> +struct __as_receiver {
>>> +  int empty_env;
>>> +};
>>> +
>>> +template<class T>
>>> +constexpr int f(T t) {
>>> +  return t.fail;
>>> +};
>>> +
>>> +int main() {
>>> +  using type = decltype(__as_receiver{f(0)}); // OK, f<int> not
>>> instantiated
>>> +}
>>
>>
>
diff mbox series

Patch

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 364695b762c..5384d0e8e46 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -2869,7 +2869,11 @@  cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 
   /* We used to shortcut trivial constructor/op= here, but nowadays
      we can only get a trivial function here with -fno-elide-constructors.  */
-  gcc_checking_assert (!trivial_fn_p (fun) || !flag_elide_constructors);
+  gcc_checking_assert (!trivial_fn_p (fun)
+		       || !flag_elide_constructors
+		       /* We don't elide constructors when processing
+			  a noexcept-expression.  */
+		       || cp_noexcept_operand);
 
   bool non_constant_args = false;
   new_call.bindings
diff --git a/gcc/testsuite/g++.dg/cpp0x/noexcept77.C b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
new file mode 100644
index 00000000000..16db8eb79ee
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/noexcept77.C
@@ -0,0 +1,9 @@ 
+// PR c++/109030
+// { dg-do compile { target c++11 } }
+
+struct foo { };
+
+struct __as_receiver {
+  foo empty_env;
+};
+void sched(foo __fun) noexcept(noexcept(__as_receiver{__fun})) { }