public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jason Merrill <jason@redhat.com>
To: Marek Polacek <polacek@redhat.com>,
	GCC Patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH] c++: P2448 - Relaxing some constexpr restrictions [PR106649]
Date: Mon, 14 Nov 2022 18:00:58 -0500	[thread overview]
Message-ID: <5cfabd39-8afe-931c-7872-1b4360d2e5b5@redhat.com> (raw)
In-Reply-To: <20221109205305.96262-1-polacek@redhat.com>

On 11/9/22 10:53, Marek Polacek wrote:
> This patch implements C++23 P2448, which lifts more restrictions on the
> constexpr keyword.  It's effectively going the way of being just a hint
> (hello, inline!).
> 
> This gist is relatively simple: in C++23, a constexpr function's return
> type/parameter type doesn't have to be a literal type; and you can have
> a constexpr function for which no invocation satisfies the requirements
> of a core constant expression.  For example,
> 
>    void f(int& i); // not constexpr
> 
>    constexpr void g(int& i) {
>      f(i); // unconditionally calls a non-constexpr function
>    }
> 
> is now OK, even though there isn't an invocation of 'g' that would be
> a constant expression.  Maybe 'f' will be made constexpr soon, or maybe
> this depends on the version of C++ used, and similar.  The patch is
> unfortunately not that trivial.  The important bit is to use the new
> require_potential_rvalue_constant_expression_fncheck in
> maybe_save_constexpr_fundef (and where appropriate).  It has a new flag
> that says that we're checking the body of a constexpr function, and in
> that case it's OK to find constructs that aren't a constant expression.
> 
> Since it's useful to be able to check for problematic constructs even
> in C++23, this patch implements a new warning, -Winvalid-constexpr,
> which is a pedwarn turned on by default in C++20 and earlier, and which
> can be turned on in C++23 as well, in which case it's an ordinary warning.
> This I implemented by using the new function constexpr_error, used in
> p_c_e_1 and friends.  (In some cases I believe fundef_p will be always
> false (= hard error), but it made sense to me to be consistent and use
> constexpr_error throughout p_c_e_1.)
> 
> While working on this I think I found a bug, see constexpr-nonlit15.C
> and <https://gcc.gnu.org/PR107598>.  This patch doesn't address that.
> 
> I also don't love that in C++23, if you don't use -Winvalid-constexpr,
> and call a constexpr function that in fact isn't constexpr-ready yet,
> sometimes all you get is an error saying "called in a constant expression"
> like in constexpr-nonlit12.C.  This could be remedied by some tweaks to
> explain_invalid_constexpr_fn, I reckon (it gives up on !DECL_DEFAULTED_FN).

And also in maybe_save_constexpr_fn: if -Wno-invalid-constexpr, 
"complain" should be false so we save the definition for 
explain_invalid_constexpr_fn to refer to.

> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> 	PR c++/106649
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-cppbuiltin.cc (c_cpp_builtins): Update value of __cpp_constexpr for
> 	C++23.
> 	* c-opts.cc (c_common_post_options): Set warn_invalid_constexpr
> 	depending on cxx_dialect.
> 	* c.opt (Winvalid-constexpr): New option.
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.cc (constexpr_error): New function.
> 	(is_valid_constexpr_fn): Use constexpr_error.
> 	(maybe_save_constexpr_fundef): Call
> 	require_potential_rvalue_constant_expression_fncheck rather than
> 	require_potential_rvalue_constant_expression.
> 	(non_const_var_error): Add a bool parameter.  Use constexpr_error.
> 	(inline_asm_in_constexpr_error): Likewise.
> 	(cxx_eval_constant_expression): Adjust calls to non_const_var_error
> 	and inline_asm_in_constexpr_error.
> 	(potential_constant_expression_1): Add a bool parameter.  Use
> 	constexpr_error.
> 	(require_potential_rvalue_constant_expression_fncheck): New function.
> 	* cp-tree.h (require_potential_rvalue_constant_expression_fncheck):
> 	Declare.
> 	* method.cc (struct comp_info): Call
> 	require_potential_rvalue_constant_expression_fncheck rather than
> 	require_potential_rvalue_constant_expression.
> 
> gcc/ChangeLog:
> 
> 	* doc/gcc/gcc-command-options/option-summary.rst: Add
> 	-Winvalid-constexpr.
> 	* doc/gcc/gcc-command-options/options-controlling-c++-dialect.rst:
> 	Document -Winvalid-constexpr.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/constexpr-ctor2.C: Expect an error in c++20_down only.
> 	* g++.dg/cpp0x/constexpr-default-ctor.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-diag3.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-ex1.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-friend.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-generated1.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-ice5.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-ice6.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-memfn1.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-neg2.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-non-const-arg.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-reinterpret1.C: Likewise.
> 	* g++.dg/cpp0x/pr65327.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-105050.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-89285-2.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-89285.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-89785-2.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-local4.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-neg1.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-nsdmi7b.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-throw.C: Likewise.
> 	* g++.dg/cpp23/constexpr-nonlit3.C: Remove dg-error.
> 	* g++.dg/cpp23/constexpr-nonlit6.C: Expect an error in c++20_down only.
> 	Call the test functions.
> 	* g++.dg/cpp23/feat-cxx2b.C: Adjust the expected value of
> 	__cpp_constexpr.
> 	* g++.dg/cpp2a/consteval3.C: Remove dg-error.
> 	* g++.dg/cpp2a/constexpr-new7.C: Expect an error in c++20_down only.
> 	* g++.dg/cpp2a/constexpr-try5.C: Remove dg-error.
> 	* g++.dg/cpp2a/spaceship-constexpr1.C: Expect an error in c++20_down
> 	only.
> 	* g++.dg/cpp2a/spaceship-eq3.C: Likewise.
> 	* g++.dg/diagnostic/constexpr1.C: Remove dg-error.
> 	* g++.dg/gomp/pr79664.C: Use -Winvalid-constexpr -pedantic-errors.
> 	* g++.dg/ubsan/vptr-4.C: Likewise.
> 	* g++.dg/cpp23/constexpr-nonlit10.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit11.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit12.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit13.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit14.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit15.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit16.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit8.C: New test.
> 	* g++.dg/cpp23/constexpr-nonlit9.C: New test.
> ---
>   gcc/c-family/c-cppbuiltin.cc                  |   2 +-
>   gcc/c-family/c-opts.cc                        |   4 +
>   gcc/c-family/c.opt                            |   4 +
>   gcc/cp/constexpr.cc                           | 272 ++++++++++++------
>   gcc/cp/cp-tree.h                              |   1 +
>   gcc/cp/method.cc                              |   8 +-
>   .../gcc-command-options/option-summary.rst    |   2 +-
>   .../options-controlling-c++-dialect.rst       |  28 ++
>   gcc/testsuite/g++.dg/cpp0x/constexpr-ctor2.C  |   2 +-
>   .../g++.dg/cpp0x/constexpr-default-ctor.C     |   6 +-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C  |   2 +-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-ex1.C    |   2 +-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-friend.C |   2 +-
>   .../g++.dg/cpp0x/constexpr-generated1.C       |   2 +-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-ice5.C   |   2 +-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-ice6.C   |   4 +-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-memfn1.C |   4 +-
>   gcc/testsuite/g++.dg/cpp0x/constexpr-neg2.C   |   6 +-
>   .../g++.dg/cpp0x/constexpr-non-const-arg.C    |   2 +-
>   .../g++.dg/cpp0x/constexpr-reinterpret1.C     |   2 +-
>   gcc/testsuite/g++.dg/cpp0x/pr65327.C          |   2 +-
>   gcc/testsuite/g++.dg/cpp1y/constexpr-105050.C |   2 +-
>   .../g++.dg/cpp1y/constexpr-89285-2.C          |   2 +-
>   gcc/testsuite/g++.dg/cpp1y/constexpr-89285.C  |   2 +-
>   .../g++.dg/cpp1y/constexpr-89785-2.C          |   4 +-
>   gcc/testsuite/g++.dg/cpp1y/constexpr-local4.C |   2 +-
>   gcc/testsuite/g++.dg/cpp1y/constexpr-neg1.C   |   2 +-
>   .../g++.dg/cpp1y/constexpr-nsdmi7b.C          |   2 +-
>   gcc/testsuite/g++.dg/cpp1y/constexpr-throw.C  |   6 +-
>   .../g++.dg/cpp23/constexpr-nonlit10.C         |  96 +++++++
>   .../g++.dg/cpp23/constexpr-nonlit11.C         |  53 ++++
>   .../g++.dg/cpp23/constexpr-nonlit12.C         |  32 +++
>   .../g++.dg/cpp23/constexpr-nonlit13.C         |  14 +
>   .../g++.dg/cpp23/constexpr-nonlit14.C         |  26 ++
>   .../g++.dg/cpp23/constexpr-nonlit15.C         |  43 +++
>   .../g++.dg/cpp23/constexpr-nonlit16.C         |  23 ++
>   .../g++.dg/cpp23/constexpr-nonlit3.C          |   2 +-
>   .../g++.dg/cpp23/constexpr-nonlit6.C          |  19 +-
>   .../g++.dg/cpp23/constexpr-nonlit8.C          |  96 +++++++
>   .../g++.dg/cpp23/constexpr-nonlit9.C          |  53 ++++
>   gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
>   gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   1 -
>   gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C   |   4 +-
>   gcc/testsuite/g++.dg/cpp2a/constexpr-try5.C   |   2 -
>   .../g++.dg/cpp2a/spaceship-constexpr1.C       |   2 +-
>   gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C    |   4 +-
>   gcc/testsuite/g++.dg/diagnostic/constexpr1.C  |   2 -
>   gcc/testsuite/g++.dg/gomp/pr79664.C           |   2 +-
>   gcc/testsuite/g++.dg/ubsan/vptr-4.C           |   2 +-
>   49 files changed, 717 insertions(+), 144 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit10.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit11.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit12.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit13.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit14.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit15.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit16.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit8.C
>   create mode 100644 gcc/testsuite/g++.dg/cpp23/constexpr-nonlit9.C
> 
> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
> index cdb658f6ac9..08f7f6d5f75 100644
> --- a/gcc/c-family/c-cppbuiltin.cc
> +++ b/gcc/c-family/c-cppbuiltin.cc
> @@ -1074,7 +1074,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	  /* Set feature test macros for C++23.  */
>   	  cpp_define (pfile, "__cpp_size_t_suffix=202011L");
>   	  cpp_define (pfile, "__cpp_if_consteval=202106L");
> -	  cpp_define (pfile, "__cpp_constexpr=202110L");
> +	  cpp_define (pfile, "__cpp_constexpr=202207L");
>   	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
>   	  cpp_define (pfile, "__cpp_named_character_escapes=202207L");
>   	  cpp_define (pfile, "__cpp_static_call_operator=202207L");
> diff --git a/gcc/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
> index 9e0494b2a45..94e92dc56b9 100644
> --- a/gcc/c-family/c-opts.cc
> +++ b/gcc/c-family/c-opts.cc
> @@ -1059,6 +1059,10 @@ c_common_post_options (const char **pfilename)
>     if (flag_sized_deallocation == -1)
>       flag_sized_deallocation = (cxx_dialect >= cxx14);
>   
> +  /* Pedwarn about invalid constexpr functions before C++23.  */
> +  if (warn_invalid_constexpr == -1)
> +    warn_invalid_constexpr = (cxx_dialect < cxx23);
> +
>     /* char8_t support is implicitly enabled in C++20 and C2X.  */
>     if (flag_char8_t == -1)
>       flag_char8_t = (cxx_dialect >= cxx20) || flag_isoc2x;
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index 63a300ecd7c..3daeab85531 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -817,6 +817,10 @@ Wint-to-pointer-cast
>   C ObjC C++ ObjC++ Var(warn_int_to_pointer_cast) Init(1) Warning
>   Warn when there is a cast to a pointer from an integer of a different size.
>   
> +Winvalid-constexpr
> +C++ ObjC++ Var(warn_invalid_constexpr) Init(-1) Warning
> +Warn when a function never produces a constant expression.
> +
>   Winvalid-offsetof
>   C++ ObjC++ Var(warn_invalid_offsetof) Init(1) Warning
>   Warn about invalid uses of the \"offsetof\" macro.
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index 15b4f2c4a08..5641b72cd30 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -139,6 +139,42 @@ ensure_literal_type_for_constexpr_object (tree decl)
>     return decl;
>   }
>   
> +/* Issue a diagnostic with text GMSGID for constructs that are invalid in
> +   constexpr functions.  CONSTEXPR_FUNDEF_P is true if we're checking
> +   a constexpr function body; if so, don't report hard errors and issue
> +   a pedwarn pre-C++23, or a warning in C++23, if requested by
> +   -Winvalid-constexpr.  Otherwise, we're not in the context where we are
> +   checking if a function can be marked 'constexpr', so give a hard error.  */
> +
> +ATTRIBUTE_GCC_DIAG(3,4)
> +static bool
> +constexpr_error (location_t location, bool constexpr_fundef_p,
> +		 const char *gmsgid, ...)
> +{
> +  diagnostic_info diagnostic;
> +  va_list ap;
> +  rich_location richloc (line_table, location);
> +  va_start (ap, gmsgid);
> +  bool ret;
> +  if (!constexpr_fundef_p)
> +    {
> +      /* Report an error that cannot be suppressed.  */
> +      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_ERROR);
> +      ret = diagnostic_report_diagnostic (global_dc, &diagnostic);
> +    }
> +  else if (warn_invalid_constexpr)
> +    {
> +      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
> +			   cxx_dialect < cxx23 ? DK_PEDWARN : DK_WARNING);
> +      diagnostic.option_index = OPT_Winvalid_constexpr;
> +      ret = diagnostic_report_diagnostic (global_dc, &diagnostic);
> +    }
> +  else
> +    ret = false;
> +  va_end (ap);
> +  return ret;
> +}
> +
>   struct constexpr_fundef_hasher : ggc_ptr_hash<constexpr_fundef>
>   {
>     static hashval_t hash (const constexpr_fundef *);
> @@ -208,9 +244,11 @@ is_valid_constexpr_fn (tree fun, bool complain)
>   	    if (complain)
>   	      {
>   		auto_diagnostic_group d;
> -		error ("invalid type for parameter %d of %<constexpr%> "
> -		       "function %q+#D", DECL_PARM_INDEX (parm), fun);
> -		explain_non_literal_class (TREE_TYPE (parm));
> +		if (constexpr_error (input_location, /*constexpr_fundef_p*/true,
> +				     "invalid type for parameter %d of "
> +				     "%<constexpr%> function %q+#D",
> +				     DECL_PARM_INDEX (parm), fun))
> +		  explain_non_literal_class (TREE_TYPE (parm));
>   	      }
>   	  }
>       }
> @@ -242,9 +280,10 @@ is_valid_constexpr_fn (tree fun, bool complain)
>   	  if (complain)
>   	    {
>   	      auto_diagnostic_group d;
> -	      error ("invalid return type %qT of %<constexpr%> function %q+D",
> -		     rettype, fun);
> -	      explain_non_literal_class (rettype);
> +	      if (constexpr_error (input_location, /*constexpr_fundef_p*/true,
> +				   "invalid return type %qT of %<constexpr%> "
> +				   "function %q+D", rettype, fun))
> +		explain_non_literal_class (rettype);
>   	    }
>   	}
>   
> @@ -918,7 +957,7 @@ maybe_save_constexpr_fundef (tree fun)
>   
>     bool potential = potential_rvalue_constant_expression (massaged);
>     if (!potential && complain)
> -    require_potential_rvalue_constant_expression (massaged);
> +    require_potential_rvalue_constant_expression_fncheck (massaged);
>   
>     if (DECL_CONSTRUCTOR_P (fun) && potential
>         && !DECL_DEFAULTED_FN (fun))
> @@ -933,7 +972,7 @@ maybe_save_constexpr_fundef (tree fun)
>   	  massaged = DECL_SAVED_TREE (fun);
>   	  potential = potential_rvalue_constant_expression (massaged);
>   	  if (!potential && complain)
> -	    require_potential_rvalue_constant_expression (massaged);
> +	    require_potential_rvalue_constant_expression_fncheck (massaged);
>   	}
>       }
>   
> @@ -5612,11 +5651,12 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
>   }
>   
>   /* Complain about R, a VAR_DECL, not being usable in a constant expression.
> +   FUNDEF_P is true if we're checking a constexpr function body.
>      Shared between potential_constant_expression and
>      cxx_eval_constant_expression.  */
>   
>   static void
> -non_const_var_error (location_t loc, tree r)
> +non_const_var_error (location_t loc, tree r, bool fundef_p)
>   {
>     auto_diagnostic_group d;
>     tree type = TREE_TYPE (r);
> @@ -5625,20 +5665,21 @@ non_const_var_error (location_t loc, tree r)
>         || DECL_NAME (r) == heap_vec_uninit_identifier
>         || DECL_NAME (r) == heap_vec_identifier)
>       {
> -      error_at (loc, "the content of uninitialized storage is not usable "
> -		"in a constant expression");
> -      inform (DECL_SOURCE_LOCATION (r), "allocated here");
> +      if (constexpr_error (loc, fundef_p, "the content of uninitialized "
> +			   "storage is not usable in a constant expression"))
> +	inform (DECL_SOURCE_LOCATION (r), "allocated here");
>         return;
>       }
>     if (DECL_NAME (r) == heap_deleted_identifier)
>       {
> -      error_at (loc, "use of allocated storage after deallocation in a "
> -		"constant expression");
> -      inform (DECL_SOURCE_LOCATION (r), "allocated here");
> +      if (constexpr_error (loc, fundef_p, "use of allocated storage after "
> +			   "deallocation in a constant expression"))
> +	inform (DECL_SOURCE_LOCATION (r), "allocated here");
>         return;
>       }
> -  error_at (loc, "the value of %qD is not usable in a constant "
> -	    "expression", r);
> +  if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in "
> +			"a constant expression", r))
> +    return;
>     /* Avoid error cascade.  */
>     if (DECL_INITIAL (r) == error_mark_node)
>       return;
> @@ -6697,15 +6738,17 @@ lookup_placeholder (const constexpr_ctx *ctx, value_cat lval, tree type)
>     return ob;
>   }
>   
> -/* Complain about an attempt to evaluate inline assembly.  */
> +/* Complain about an attempt to evaluate inline assembly.  If FUNDEF_P is
> +   true, we're checking a constexpr function body.  */
>   
>   static void
> -inline_asm_in_constexpr_error (location_t loc)
> +inline_asm_in_constexpr_error (location_t loc, bool fundef_p)
>   {
>     auto_diagnostic_group d;
> -  error_at (loc, "inline assembly is not a constant expression");
> -  inform (loc, "only unevaluated inline assembly is allowed in a "
> -	  "%<constexpr%> function in C++20");
> +  if (constexpr_error (loc, fundef_p, "inline assembly is not a "
> +		       "constant expression"))
> +    inform (loc, "only unevaluated inline assembly is allowed in a "
> +	    "%<constexpr%> function in C++20");
>   }
>   
>   /* We're getting the constant value of DECL in a manifestly constant-evaluated
> @@ -6983,7 +7026,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>         if (DECL_P (r))
>   	{
>   	  if (!ctx->quiet)
> -	    non_const_var_error (loc, r);
> +	    non_const_var_error (loc, r, /*fundef_p*/false);
>   	  *non_constant_p = true;
>   	}
>         break;
> @@ -7874,7 +7917,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>   
>       case ASM_EXPR:
>         if (!ctx->quiet)
> -	inline_asm_in_constexpr_error (loc);
> +	inline_asm_in_constexpr_error (loc, /*constexpr_fundef_p*/false);
>         *non_constant_p = true;
>         return t;
>   
> @@ -8759,7 +8802,8 @@ check_for_return_continue (tree *tp, int *walk_subtrees, void *data)
>      diagnostic as appropriate under control of FLAGS.  If WANT_RVAL is true,
>      an lvalue-rvalue conversion is implied.  If NOW is true, we want to
>      consider the expression in the current context, independent of constexpr
> -   substitution.
> +   substitution.  If FUNDEF_P is true, we're checking a constexpr function body
> +   and hard errors should not be reported by constexpr_error.
>   
>      C++0x [expr.const] used to say
>   
> @@ -8776,10 +8820,12 @@ check_for_return_continue (tree *tp, int *walk_subtrees, void *data)
>   
>   static bool
>   potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
> -				 tsubst_flags_t flags, tree *jump_target)
> +				 bool fundef_p, tsubst_flags_t flags,
> +				 tree *jump_target)
>   {
>   #define RECUR(T,RV) \
> -  potential_constant_expression_1 ((T), (RV), strict, now, flags, jump_target)
> +  potential_constant_expression_1 ((T), (RV), strict, now, fundef_p, flags, \
> +				   jump_target)
>   
>     enum { any = false, rval = true };
>     int i;
> @@ -8801,8 +8847,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>     if (TREE_THIS_VOLATILE (t) && want_rval)
>       {
>         if (flags & tf_error)
> -	error_at (loc, "lvalue-to-rvalue conversion of a volatile lvalue "
> -		  "%qE with type %qT", t, TREE_TYPE (t));
> +	constexpr_error (loc, fundef_p, "lvalue-to-rvalue conversion of "
> +			 "a volatile lvalue %qE with type %qT", t,
> +			 TREE_TYPE (t));
>         return false;
>       }
>     if (CONSTANT_CLASS_P (t))
> @@ -8861,7 +8908,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	    /* An empty class has no data to read.  */
>   	    return true;
>   	  if (flags & tf_error)
> -	    error ("%qE is not a constant expression", t);
> +	    constexpr_error (input_location, fundef_p,
> +			     "%qE is not a constant expression", t);
>   	  return false;
>   	}
>         return true;
> @@ -8910,7 +8958,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	      {
>   		/* fold_call_expr can't do anything with IFN calls.  */
>   		if (flags & tf_error)
> -		  error_at (loc, "call to internal function %qE", t);
> +		  constexpr_error (loc, fundef_p,
> +				   "call to internal function %qE", t);
>   		return false;
>   	      }
>   	  }
> @@ -8940,12 +8989,11 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   			|| !is_std_construct_at (current_function_decl))
>   		    && !cxx_dynamic_cast_fn_p (fun))
>   		  {
> -		    if (flags & tf_error)
> -		      {
> -			error_at (loc, "call to non-%<constexpr%> function %qD",
> -				  fun);
> -			explain_invalid_constexpr_fn (fun);
> -		      }
> +		    if ((flags & tf_error)
> +			&& constexpr_error (loc, fundef_p,
> +					    "call to non-%<constexpr%> "
> +					    "function %qD", fun))
> +		      explain_invalid_constexpr_fn (fun);
>   		    return false;
>   		  }
>   		/* A call to a non-static member function takes the address
> @@ -8962,8 +9010,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   		       constexpr substitution might not use the value.  */
>   		    bool sub_now = false;
>   		    if (!potential_constant_expression_1 (x, rval, strict,
> -							  sub_now, flags,
> -							  jump_target))
> +							  sub_now, fundef_p,
> +							  flags, jump_target))
>   		      return false;
>   		    i = 1;
>   		  }
> @@ -8997,7 +9045,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	       substitution might not use the value of the argument.  */
>   	    bool sub_now = false;
>   	    if (!potential_constant_expression_1 (x, rv, strict,
> -						  sub_now, flags, jump_target))
> +						  sub_now, fundef_p, flags,
> +						  jump_target))
>   	      return false;
>             }
>           return true;
> @@ -9035,9 +9084,10 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	      if (flags & tf_error)
>   		{
>   		  tree cap = DECL_CAPTURED_VARIABLE (t);
> -		  error ("lambda capture of %qE is not a constant expression",
> -			 cap);
> -		  if (decl_constant_var_p (cap))
> +		  if (constexpr_error (input_location, fundef_p,
> +				       "lambda capture of %qE is not a "
> +				       "constant expression", cap)
> +		      && decl_constant_var_p (cap))
>   		    inform (input_location, "because it is used as a glvalue");
>   		}
>   	      return false;
> @@ -9060,8 +9110,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	  && COMPLETE_TYPE_P (TREE_TYPE (t))
>   	  && !is_really_empty_class (TREE_TYPE (t), /*ignore_vptr*/false))
>           {
> -          if (flags & tf_error)
> -	    non_const_var_error (loc, t);
> +	  if (flags & tf_error)
> +	    non_const_var_error (loc, t, fundef_p);
>             return false;
>           }
>         return true;
> @@ -9070,7 +9120,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>         if (REINTERPRET_CAST_P (t))
>   	{
>   	  if (flags & tf_error)
> -	    error_at (loc, "%<reinterpret_cast%> is not a constant expression");
> +	    constexpr_error (loc, fundef_p, "%<reinterpret_cast%> is not a "
> +			     "constant expression");
>   	  return false;
>   	}
>         /* FALLTHRU */
> @@ -9092,8 +9143,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   		&& !integer_zerop (from))
>   	      {
>   		if (flags & tf_error)
> -		  error_at (loc,
> -			    "%<reinterpret_cast%> from integer to pointer");
> +		  constexpr_error (loc, fundef_p,
> +				   "%<reinterpret_cast%> from integer to "
> +				   "pointer");
>   		return false;
>   	      }
>   	  }
> @@ -9165,7 +9217,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	    if (!var_in_maybe_constexpr_fn (x))
>   	      {
>   		if (flags & tf_error)
> -		  error_at (loc, "use of %<this%> in a constant expression");
> +		  constexpr_error (loc, fundef_p, "use of %<this%> in a "
> +				   "constant expression");
>   		return false;
>   	      }
>   	    return true;
> @@ -9313,8 +9366,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	/* In C++17 lambdas can be constexpr, don't give up yet.  */
>   	return true;
>         else if (flags & tf_error)
> -	error_at (loc, "lambda-expression is not a constant expression "
> -		  "before C++17");
> +	constexpr_error (loc, fundef_p, "lambda-expression is not a "
> +			 "constant expression before C++17");
>         return false;
>   
>       case NEW_EXPR:
> @@ -9325,8 +9378,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	/* In C++20, new-expressions are potentially constant.  */
>   	return true;
>         else if (flags & tf_error)
> -	error_at (loc, "new-expression is not a constant expression "
> -		  "before C++20");
> +	constexpr_error (loc, fundef_p, "new-expression is not a "
> +			 "constant expression before C++20");
>         return false;
>   
>       case DYNAMIC_CAST_EXPR:
> @@ -9375,12 +9428,13 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>       case AT_ENCODE_EXPR:
>       fail:
>         if (flags & tf_error)
> -	error_at (loc, "expression %qE is not a constant expression", t);
> +	 constexpr_error (loc, fundef_p, "expression %qE is not a constant "
> +			  "expression", t);
>         return false;
>   
>       case ASM_EXPR:
>         if (flags & tf_error)
> -	inline_asm_in_constexpr_error (loc);
> +	inline_asm_in_constexpr_error (loc, fundef_p);
>         return false;
>   
>       case OBJ_TYPE_REF:
> @@ -9388,8 +9442,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	/* In C++20 virtual calls can be constexpr, don't give up yet.  */
>   	return true;
>         else if (flags & tf_error)
> -	error_at (loc,
> -		  "virtual functions cannot be %<constexpr%> before C++20");
> +	constexpr_error (loc, fundef_p, "virtual functions cannot be "
> +			 "%<constexpr%> before C++20");
>         return false;
>   
>       case TYPEID_EXPR:
> @@ -9404,8 +9458,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	    && TYPE_POLYMORPHIC_P (TREE_TYPE (e)))
>             {
>               if (flags & tf_error)
> -	      error_at (loc, "%<typeid%> is not a constant expression "
> -			"because %qE is of polymorphic type", e);
> +	      constexpr_error (loc, fundef_p, "%<typeid%> is not a "
> +			       "constant expression because %qE is "
> +			       "of polymorphic type", e);
>               return false;
>             }
>           return true;
> @@ -9467,9 +9522,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	   constant expression.  */
>   	{
>   	  if (flags & tf_error)
> -	    error_at (loc,
> -		      "cast to non-integral type %qT in a constant expression",
> -		      TREE_TYPE (t));
> +	    constexpr_error (loc, fundef_p,
> +			     "cast to non-integral type %qT in a constant "
> +			     "expression", TREE_TYPE (t));
>   	  return false;
>   	}
>         /* This might be a conversion from a class to a (potentially) literal
> @@ -9525,15 +9580,17 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	  if (CP_DECL_THREAD_LOCAL_P (tmp) && !DECL_REALLY_EXTERN (tmp))
>   	    {
>   	      if (flags & tf_error)
> -		error_at (DECL_SOURCE_LOCATION (tmp), "%qD defined "
> -			  "%<thread_local%> in %<constexpr%> context", tmp);
> +		constexpr_error (DECL_SOURCE_LOCATION (tmp), fundef_p,
> +				 "%qD defined %<thread_local%> in "
> +				 "%<constexpr%> context", tmp);
>   	      return false;
>   	    }
>   	  else if (TREE_STATIC (tmp))
>   	    {
>   	      if (flags & tf_error)
> -		error_at (DECL_SOURCE_LOCATION (tmp), "%qD defined "
> -			  "%<static%> in %<constexpr%> context", tmp);
> +		constexpr_error (DECL_SOURCE_LOCATION (tmp), fundef_p,
> +				 "%qD defined %<static%> in %<constexpr%> "
> +				 "context", tmp);
>   	      return false;
>   	    }
>   	  else if (!check_for_uninitialized_const_var
> @@ -9556,9 +9613,10 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	  if (flags & tf_error)
>   	    {
>   	      auto_diagnostic_group d;
> -	      error_at (loc, "temporary of non-literal type %qT in a "
> -			"constant expression", TREE_TYPE (t));
> -	      explain_non_literal_class (TREE_TYPE (t));
> +	      if (constexpr_error (loc, fundef_p,
> +				   "temporary of non-literal type %qT in a "
> +				   "constant expression", TREE_TYPE (t)))
> +		explain_non_literal_class (TREE_TYPE (t));
>   	    }
>   	  return false;
>   	}
> @@ -9605,7 +9663,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	if (integer_zerop (denom))
>   	  {
>   	    if (flags & tf_error)
> -	      error ("division by zero is not a constant expression");
> +	      constexpr_error (input_location, fundef_p,
> +			       "division by zero is not a constant expression");
>   	    return false;
>   	  }
>   	else
> @@ -9706,7 +9765,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>         if (COND_EXPR_IS_VEC_DELETE (t) && cxx_dialect < cxx20)
>   	{
>   	  if (flags & tf_error)
> -	    error_at (loc, "%<delete[]%> is not a constant expression");
> +	    constexpr_error (loc, fundef_p, "%<delete[]%> is not a "
> +			     "constant expression");
>   	  return false;
>   	}
>         /* Fall through.  */
> @@ -9736,7 +9796,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	{
>   	  tree this_jump_target = tmp;
>   	  if (potential_constant_expression_1 (TREE_OPERAND (t, i),
> -					       want_rval, strict, now,
> +					       want_rval, strict, now, fundef_p,
>   					       tf_none, &this_jump_target))
>   	    {
>   	      if (returns (&this_jump_target))
> @@ -9774,9 +9834,11 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>         if (flags & tf_error)
>   	{
>   	  if (TREE_CODE (t) == IF_STMT)
> -	    error_at (loc, "neither branch of %<if%> is a constant expression");
> +	    constexpr_error (loc, fundef_p, "neither branch of %<if%> is a "
> +			     "constant expression");
>   	  else
> -	    error_at (loc, "expression %qE is not a constant expression", t);
> +	    constexpr_error (loc, fundef_p, "expression %qE is not a "
> +			     "constant expression", t);
>   	}
>         return false;
>   
> @@ -9785,8 +9847,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	return true;
>         if (flags & tf_error)
>   	{
> -	  error_at (loc, "non-constant array initialization");
> -	  diagnose_non_constexpr_vec_init (t);
> +	  if (constexpr_error (loc, fundef_p, "non-constant array "
> +			       "initialization"))
> +	    diagnose_non_constexpr_vec_init (t);
>   	}
>         return false;
>   
> @@ -9815,7 +9878,8 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	    return true;
>   	  }
>   	if (flags & tf_error)
> -	  error_at (loc, "%<goto%> is not a constant expression");
> +	  constexpr_error (loc, fundef_p, "%<goto%> is not a constant "
> +			   "expression");
>   	return false;
>         }
>   
> @@ -9824,8 +9888,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>         if (DECL_ARTIFICIAL (t) || cxx_dialect >= cxx23)
>   	return true;
>         else if (flags & tf_error)
> -	error_at (loc, "label definition in %<constexpr%> function only "
> -		       "available with %<-std=c++2b%> or %<-std=gnu++2b%>");
> +	constexpr_error (loc, fundef_p, "label definition in %<constexpr%> "
> +			 "function only available with %<-std=c++2b%> or "
> +			 "%<-std=gnu++2b%>");
>         return false;
>   
>       case ANNOTATE_EXPR:
> @@ -9863,7 +9928,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   
>   bool
>   potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
> -				 tsubst_flags_t flags)
> +				 bool fundef_p, tsubst_flags_t flags)
>   {
>     if (flags & tf_error)
>       {
> @@ -9871,13 +9936,14 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   	 efficiently in some cases (currently only for TRUTH_*_EXPR).  If
>   	 that fails, replay the check noisily to give errors.  */
>         flags &= ~tf_error;
> -      if (potential_constant_expression_1 (t, want_rval, strict, now, flags))
> +      if (potential_constant_expression_1 (t, want_rval, strict, now, fundef_p,
> +					   flags))
>   	return true;
>         flags |= tf_error;
>       }
>   
>     tree target = NULL_TREE;
> -  return potential_constant_expression_1 (t, want_rval, strict, now,
> +  return potential_constant_expression_1 (t, want_rval, strict, now, fundef_p,
>   					  flags, &target);
>   }
>   
> @@ -9886,7 +9952,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
>   bool
>   potential_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, false, true, false, tf_none);
> +  return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
> +					  /*now*/false, /*fundef_p*/false,
> +					  tf_none);
>   }
>   
>   /* As above, but require a constant rvalue.  */
> @@ -9894,7 +9962,9 @@ potential_constant_expression (tree t)
>   bool
>   potential_rvalue_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, true, true, false, tf_none);
> +  return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
> +					  /*now*/false, /*fundef_p*/false,
> +					  tf_none);
>   }
>   
>   /* Like above, but complain about non-constant expressions.  */
> @@ -9902,7 +9972,8 @@ potential_rvalue_constant_expression (tree t)
>   bool
>   require_potential_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, false, true, false,
> +  return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
> +					  /*now*/false, /*fundef_p*/false,
>   					  tf_warning_or_error);
>   }
>   
> @@ -9911,7 +9982,18 @@ require_potential_constant_expression (tree t)
>   bool
>   require_potential_rvalue_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, true, true, false,
> +  return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
> +					  /*now*/false, /*fundef_p*/false,
> +					  tf_warning_or_error);
> +}
> +
> +/* Like require_potential_rvalue_constant_expression, but fundef_p is true.  */
> +
> +bool
> +require_potential_rvalue_constant_expression_fncheck (tree t)
> +{
> +  return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
> +					  /*now*/false, /*fundef_p*/true,
>   					  tf_warning_or_error);
>   }
>   
> @@ -9920,7 +10002,8 @@ require_potential_rvalue_constant_expression (tree t)
>   bool
>   require_rvalue_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, true, true, true,
> +  return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
> +					  /*now*/true, /*fundef_p*/false,
>   					  tf_warning_or_error);
>   }
>   
> @@ -9934,7 +10017,9 @@ require_rvalue_constant_expression (tree t)
>   bool
>   is_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, false, true, true, tf_none);
> +  return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
> +					  /*now*/true, /*fundef_p*/false,
> +					  tf_none);
>   }
>   
>   /* As above, but expect an rvalue.  */
> @@ -9942,7 +10027,9 @@ is_constant_expression (tree t)
>   bool
>   is_rvalue_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, true, true, true, tf_none);
> +  return potential_constant_expression_1 (t, /*want_rval*/true, /*strict*/true,
> +					  /*now*/true, /*fundef_p*/false,
> +					  tf_none);
>   }
>   
>   /* Like above, but complain about non-constant expressions.  */
> @@ -9950,7 +10037,8 @@ is_rvalue_constant_expression (tree t)
>   bool
>   require_constant_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, false, true, true,
> +  return potential_constant_expression_1 (t, /*want_rval*/false, /*strict*/true,
> +					  /*now*/true, /*fundef_p*/false,
>   					  tf_warning_or_error);
>   }
>   
> @@ -9960,7 +10048,9 @@ require_constant_expression (tree t)
>   bool
>   is_static_init_expression (tree t)
>   {
> -  return potential_constant_expression_1 (t, false, false, true, tf_none);
> +  return potential_constant_expression_1 (t, /*want_rval*/false,
> +					  /*strict*/false, /*now*/true,
> +					  /*fundef_p*/false, tf_none);
>   }
>   
>   /* Returns true if T is a potential constant expression that is not
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index bbc8be21900..7a74132e4d0 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -8452,6 +8452,7 @@ extern bool require_potential_constant_expression (tree);
>   extern bool require_constant_expression (tree);
>   extern bool require_rvalue_constant_expression (tree);
>   extern bool require_potential_rvalue_constant_expression (tree);
> +extern bool require_potential_rvalue_constant_expression_fncheck (tree);
>   extern tree cxx_constant_value			(tree, tree = NULL_TREE,
>   						 tsubst_flags_t = tf_error);
>   inline tree cxx_constant_value (tree t, tsubst_flags_t complain)
> diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> index c217d7e5aad..1e962b6e3b1 100644
> --- a/gcc/cp/method.cc
> +++ b/gcc/cp/method.cc
> @@ -1332,7 +1332,7 @@ struct comp_info
>   	&& !potential_rvalue_constant_expression (expr))
>         {
>   	if (was_constexp)
> -	  require_potential_rvalue_constant_expression (expr);
> +	  require_potential_rvalue_constant_expression_fncheck (expr);
>   	else
>   	  constexp = false;
>         }
> @@ -2670,13 +2670,17 @@ synthesized_method_walk (tree ctype, special_function_kind sfk, bool const_p,
>        requirements of a constexpr constructor (7.1.5), the
>        implicitly-defined default constructor is constexpr.
>   
> +     C++20:
>        The implicitly-defined copy/move assignment operator is constexpr if
>         - X is a literal type, and
>         - the assignment operator selected to copy/move each direct base class
>   	subobject is a constexpr function, and
>         - for each non-static data member of X that is of class type (or array
>   	thereof), the assignment operator selected to copy/move that
> -	member is a constexpr function.  */
> +	member is a constexpr function.
> +
> +      C++23:
> +      The implicitly-defined copy/move assignment operator is constexpr.  */
>     if (constexpr_p)
>       *constexpr_p = (SFK_CTOR_P (sfk)
>   		    || (SFK_ASSIGN_P (sfk) && cxx_dialect >= cxx14)
> diff --git a/gcc/doc/gcc/gcc-command-options/option-summary.rst b/gcc/doc/gcc/gcc-command-options/option-summary.rst
> index d068f98feac..01123f55069 100644
> --- a/gcc/doc/gcc/gcc-command-options/option-summary.rst
> +++ b/gcc/doc/gcc/gcc-command-options/option-summary.rst
> @@ -89,7 +89,7 @@ in the following sections.
>     :option:`-Wno-deprecated-enum-enum-conversion` :option:`-Wno-deprecated-enum-float-conversion` |gol|
>     :option:`-Weffc++`  :option:`-Wno-exceptions` :option:`-Wextra-semi`  :option:`-Wno-inaccessible-base` |gol|
>     :option:`-Wno-inherited-variadic-ctor`  :option:`-Wno-init-list-lifetime` |gol|
> -  :option:`-Winvalid-imported-macros` |gol|
> +  :option:`-Winvalid-constexpr`  :option:`-Winvalid-imported-macros` |gol|
>     :option:`-Wno-invalid-offsetof`  :option:`-Wno-literal-suffix` |gol|
>     :option:`-Wmismatched-new-delete` :option:`-Wmismatched-tags` |gol|
>     :option:`-Wmultiple-inheritance`  :option:`-Wnamespaces`  :option:`-Wnarrowing` |gol|
> diff --git a/gcc/doc/gcc/gcc-command-options/options-controlling-c++-dialect.rst b/gcc/doc/gcc/gcc-command-options/options-controlling-c++-dialect.rst
> index 5b05d31aae9..63517184c23 100644
> --- a/gcc/doc/gcc/gcc-command-options/options-controlling-c++-dialect.rst
> +++ b/gcc/doc/gcc/gcc-command-options/options-controlling-c++-dialect.rst
> @@ -989,6 +989,34 @@ In addition, these warning options have meanings only for C++ programs:
>   
>     Default setting; overrides :option:`-Wno-init-list-lifetime`.
>   
> +.. option:: -Winvalid-constexpr
> +
> +  .. note::
> +
> +    C++ and Objective-C++ only
> +
> +  Warn when a function never produces a constant expression.  In C++20
> +  and earlier, for every ``constexpr`` function and function template,
> +  there must be at least one set of function arguments in at least one
> +  instantiation such that an invocation of the function or constructor
> +  could be an evaluated subexpression of a core constant expression.
> +  C++23 removed this restriction, so it's possible to have a function
> +  or a function template marked ``constexpr`` for which no invocation
> +  satisfies the requirements of a core constant expression.
> +
> +  This warning is enabled as a pedantic warning by default in C++20 and
> +  earlier.  In C++23, :option:`-Winvalid-constexpr` can be turned on, in
> +  which case it will be an ordinary warning.  For example:
> +
> +  .. code-block:: c++
> +
> +    void f (int& i);
> +    constexpr void
> +    g (int& i)
> +    {
> +      f(i); // warns by default in C++20, in C++23 only with -Winvalid-constexpr
> +    }
> +
>   .. option:: -Winvalid-imported-macros
>   
>     Verify all imported macro definitions are valid at the end of
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor2.C
> index 30b01091fd1..eabc586385f 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor2.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ctor2.C
> @@ -7,5 +7,5 @@ struct A
>   
>   struct B : A
>   {
> -  constexpr B(): A() { }	// { dg-error "A::A" }
> +  constexpr B(): A() { }	// { dg-error "A::A" "" { target c++20_down } }
>   };
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-default-ctor.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-default-ctor.C
> index 8d352d0bb99..2f9fbfb596a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-default-ctor.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-default-ctor.C
> @@ -7,6 +7,6 @@ struct A {
>   struct B: A { };
>   constexpr int f(B b) { return b.i; }
>   
> -struct C { C(); };	       // { dg-message "" }
> -struct D: C { };	       // { dg-message "" }
> -constexpr int g(D d) { return 42; } // { dg-error "invalid type" }
> +struct C { C(); };	       // { dg-message "" "" { target c++20_down } }
> +struct D: C { };	       // { dg-message "" "" { target c++20_down } }
> +constexpr int g(D d) { return 42; } // { dg-error "invalid type" "" { target c++20_down } }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> index c167bb1d8bc..5eedf42ba36 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> @@ -37,7 +37,7 @@ struct base		       // { dg-message "no .constexpr. constructor" "" { target { !
>   
>   struct derived : public base	// { dg-message "base class" "" { target { ! implicit_constexpr } } }
>   {
> -  constexpr derived(): base() { } // { dg-error "non-.constexpr. function" "" { target { ! implicit_constexpr } } }
> +  constexpr derived(): base() { } // { dg-error "non-.constexpr. function" "" { target { { ! implicit_constexpr } && c++20_down } } }
>   };
>   
>   constexpr derived obj;		// { dg-error "not literal" "" { target { ! implicit_constexpr } } }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ex1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ex1.C
> index 1d5c58b4090..5d11822acb5 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ex1.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ex1.C
> @@ -87,7 +87,7 @@ struct resource {
>     }
>   };
>   constexpr resource f(resource d)
> -{ return d; }                  // { dg-error "non-.constexpr." "" { target { ! implicit_constexpr } } }
> +{ return d; }                  // { dg-error "non-.constexpr." "" { target { { ! implicit_constexpr } && c++20_down } } }
>   constexpr resource d = f(9);   // { dg-message ".constexpr." "" { target { ! implicit_constexpr } } }
>   
>   // 4.4 floating-point constant expressions
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-friend.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-friend.C
> index 85dfca4ff1d..3d171822855 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-friend.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-friend.C
> @@ -5,7 +5,7 @@ struct A { A(); };
>   
>   struct B {
>     friend constexpr int f(B) { return 0; } // OK
> -  friend constexpr int f(A) { return 0; } // { dg-error "constexpr" }
> +  friend constexpr int f(A) { return 0; } // { dg-error "constexpr" "" { target c++20_down } }
>   };
>   
>   template <class T>
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-generated1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-generated1.C
> index 4b0d68bf661..98235719546 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-generated1.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-generated1.C
> @@ -9,7 +9,7 @@ int g();
>   
>   // We should complain about this.
>   template<> constexpr int A<int>::f()
> -{ return g(); }			// { dg-error "non-.constexpr." }
> +{ return g(); }			// { dg-error "non-.constexpr." "" { target c++20_down } }
>   
>   // But not about this.
>   struct B
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice5.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice5.C
> index e934421c2f4..70327fc414a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice5.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice5.C
> @@ -9,5 +9,5 @@ struct A
>   struct B
>   {
>     A a[1];
> -  constexpr B() : a() {} // { dg-error "non-constant|non-.constexpr." "" { target { ! implicit_constexpr } } }
> +  constexpr B() : a() {} // { dg-error "non-constant|non-.constexpr." "" { target { { ! implicit_constexpr } && c++20_down } } }
>   };
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice6.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice6.C
> index bf95b2443c7..7eabd333758 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice6.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice6.C
> @@ -6,6 +6,6 @@ struct A
>     A(int);
>   };
>   
> -struct B : A {};                   // { dg-message "" }
> +struct B : A {};                   // { dg-message "" "" { target c++20_down } }
>   
> -constexpr int foo(B) { return 0; } // { dg-error "invalid type" }
> +constexpr int foo(B) { return 0; } // { dg-error "invalid type" "" { target c++20_down } }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-memfn1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-memfn1.C
> index 37255282ded..0c95961c730 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-memfn1.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-memfn1.C
> @@ -13,6 +13,6 @@ constexpr X X::g(X x) { return x; }
>   struct Y
>   {
>     Y() { }
> -  constexpr Y f(Y y) { return y; }  // { dg-error "constexpr" "" { target { ! implicit_constexpr } } }
> -  static constexpr Y g(Y y) { return y; } // { dg-error "constexpr" "" { target { ! implicit_constexpr } } }
> +  constexpr Y f(Y y) { return y; }  // { dg-error "constexpr" "" { target { { ! implicit_constexpr } && c++20_down } } }
> +  static constexpr Y g(Y y) { return y; } // { dg-error "constexpr" "" { target { { ! implicit_constexpr } && c++20_down } } }
>   };
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-neg2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-neg2.C
> index 793b4c3f5d3..4b27612d45c 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-neg2.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-neg2.C
> @@ -15,13 +15,13 @@ constexpr int three = one() ? 3 : nonconst_func(0);
>   //   such that the function invocation sub-stitution would produce a
>   //   constant expression (5.19), the program is ill-formed; no diagnostic
>   //   required.
> -constexpr int bogus() { return zero () ? 3 : nonconst_func(0); } // { dg-error "nonconst_func" }
> +constexpr int bogus() { return zero () ? 3 : nonconst_func(0); } // { dg-error "nonconst_func" "" { target c++20_down } }
>   
>   // Correctly rejected (not sure why).
> -constexpr int correct_error() { return nonconst_func(0); } // { dg-error "nonconst_func" }
> +constexpr int correct_error() { return nonconst_func(0); } // { dg-error "nonconst_func" "" { target c++20_down } }
>   
>   // Correctly rejected.
>   constexpr int z = bogus();	// { dg-error "" }
>   
>   // This is also correctly rejected.
> -constexpr int correct_failure() { return 0 ? 3 : nonconst_func(0); } // { dg-error "nonconst_func" }
> +constexpr int correct_failure() { return 0 ? 3 : nonconst_func(0); } // { dg-error "nonconst_func" "" { target c++20_down } }
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-non-const-arg.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-non-const-arg.C
> index 0f68643f145..abbc70368d4 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-non-const-arg.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-non-const-arg.C
> @@ -10,7 +10,7 @@ struct B {
>   int global;			// { dg-message "not const" }
>   
>   struct D : B {
> -  constexpr D() : B(global) { }   // { dg-error "global|argument" }
> +  constexpr D() : B(global) { }   // { dg-error "global|argument" "" { target c++20_down } }
>   };
>   
>   struct A2 {
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-reinterpret1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-reinterpret1.C
> index d7d244f752d..4e19cd36a9f 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-reinterpret1.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-reinterpret1.C
> @@ -17,7 +17,7 @@ public:
>     constexpr static Inner & getInner()
>     /* I am surprised this is considered a constexpr */
>     {
> -    return *((Inner *)4); // { dg-error "reinterpret_cast" }
> +    return *((Inner *)4); // { dg-error "reinterpret_cast" "" { target c++20_down } }
>     }
>   };
>   
> diff --git a/gcc/testsuite/g++.dg/cpp0x/pr65327.C b/gcc/testsuite/g++.dg/cpp0x/pr65327.C
> index e8149953ffd..b3ef57eec5f 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/pr65327.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/pr65327.C
> @@ -14,5 +14,5 @@ foo ()
>   constexpr volatile int // { dg-warning "deprecated" "" { target c++2a } }
>   bar ()
>   {
> -  return i;  // { dg-error "lvalue-to-rvalue conversion of a volatile lvalue" }
> +  return i;  // { dg-error "lvalue-to-rvalue conversion of a volatile lvalue" "" { target c++20_down } }
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-105050.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-105050.C
> index e0688fbd38e..e5d53c9817b 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-105050.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-105050.C
> @@ -5,7 +5,7 @@ void g();
>   void h();
>   
>   constexpr void f(int* p, int* q) {
> -  if (p != q && *p < 0) // { dg-error "neither branch of 'if' is a constant expression" }
> +  if (p != q && *p < 0) // { dg-error "neither branch of 'if' is a constant expression" "" { target c++20_down } }
>       g();
>     else
>       h();
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-89285-2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-89285-2.C
> index ea44daa849e..7b129fcee7a 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-89285-2.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-89285-2.C
> @@ -10,7 +10,7 @@ struct B {
>       int *c = &x->a;
>       while (*c)
>         c = reinterpret_cast<int *>((reinterpret_cast<char *>(c) + *c));
> -    *c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c); // { dg-error "reinterpret_cast" }
> +    *c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c); // { dg-error "reinterpret_cast" "" { target c++20_down } }
>     }
>   };
>   struct C : A {
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-89285.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-89285.C
> index 26aab9b6a50..fe0b8570ca2 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-89285.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-89285.C
> @@ -10,7 +10,7 @@ struct B {
>       int *c = &x->a;
>       while (*c)
>         c = reinterpret_cast<int *>((reinterpret_cast<char *>(c) + *c));
> -    *c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c);	// { dg-error "reinterpret_cast" }
> +    *c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c);	// { dg-error "reinterpret_cast" "" { target c++20_down } }
>     }
>   };
>   struct C : A {
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-89785-2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-89785-2.C
> index 5cd46c791af..7afd9d24e98 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-89785-2.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-89785-2.C
> @@ -11,7 +11,7 @@ foo (int x)
>       case 2:
>         break;
>       }
> -  throw 42;	// { dg-error "is not a constant expression" }
> +  throw 42;	// { dg-error "is not a constant expression" "" { target c++20_down } }
>     return 0;
>   }
>   
> @@ -29,7 +29,7 @@ bar (int x)
>   	    continue;
>   	  break;
>   	}
> -      throw -42;	// { dg-error "is not a constant expression" }
> +      throw -42;	// { dg-error "is not a constant expression" "" { target c++20_down } }
>       }
>     while (0);
>     return x;
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-local4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-local4.C
> index 647b5dcd7cd..180549e723a 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-local4.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-local4.C
> @@ -10,7 +10,7 @@ const A a = 42;
>   
>   constexpr int f()
>   {
> -  const int j = a.i;		// { dg-error "'a'" }
> +  const int j = a.i;		// { dg-error "'a'" "" { target c++20_down }  }
>     return j;
>   }
>   
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-neg1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-neg1.C
> index 8e9d1ea4943..53b5dd50f51 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-neg1.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-neg1.C
> @@ -3,7 +3,7 @@
>   struct A { A(); };
>   
>   constexpr int f(int i) {
> -  static int j = i;		// { dg-error "static" }
> +  static int j = i;		// { dg-error "static" "" { target c++20_down } }
>     thread_local int l = i;	// { dg-error "thread_local" "" { target c++20_down } }
>     goto foo;			// { dg-error "goto" "" { target c++20_down } }
>    foo:
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7b.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7b.C
> index ec10ddd2be8..a410e482664 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7b.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi7b.C
> @@ -21,7 +21,7 @@ bar()
>     A a = foo();
>     a.p->n = 5;
>     return a;
> -} // { dg-error "non-.constexpr." }
> +} // { dg-error "non-.constexpr." "" { target c++20_down } }
>   
>   constexpr int
>   baz()
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-throw.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-throw.C
> index 3bbc8ac1b88..35928744686 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-throw.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-throw.C
> @@ -7,18 +7,18 @@ constexpr void f1() {
>   
>   constexpr void f2() {
>     if (true)
> -    throw;	// { dg-error "not a constant expression" }
> +    throw;	// { dg-error "not a constant expression" "" { target c++20_down } }
>   }
>   
>   constexpr void f3() {
>     if (false)
>       ;
>     else
> -    throw;	// { dg-error "not a constant expression" }
> +    throw;	// { dg-error "not a constant expression" "" { target c++20_down } }
>   }
>   
>   constexpr void f4() {
> -  throw;	// { dg-error "not a constant expression" }
> +  throw;	// { dg-error "not a constant expression" "" { target c++20_down } }
>   }
>   
>   constexpr int fun(int n) {
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit10.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit10.C
> new file mode 100644
> index 00000000000..48706f7b66e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit10.C
> @@ -0,0 +1,96 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++23 } }
> +// { dg-options "-Winvalid-constexpr -pedantic-errors" }
> +
> +// No constexpr constructors = not a literal type.
> +struct NonLiteral {
> +  NonLiteral() {}
> +};
> +
> +// C++23: It is possible to write a constexpr function for which no
> +// invocation satisfies the requirements of a core constant expression.
> +constexpr NonLiteral
> +fn0 (int) // { dg-warning "invalid return type" }
> +{
> +  return NonLiteral{};
> +}
> +
> +constexpr int
> +fn1 (NonLiteral) // { dg-warning "invalid type" }
> +{
> +  return 42;
> +}
> +
> +// From P2448.
> +void f(int& i) {
> +    i = 0;
> +}
> +
> +constexpr void g(int& i) {
> +    f(i); // { dg-warning "call to" }
> +}
> +
> +// [dcl.constexpr] used to have this.
> +constexpr int f(bool b)
> +  { return b ? throw 0 : 0; }           // OK
> +constexpr int f() { return f(true); }   // ill-formed, no diagnostic required
> +
> +struct B {
> +  constexpr B(int) : i(0) { }
> +  int i;
> +};
> +
> +int global;
> +
> +struct D : B {
> +  constexpr D() : B(global) { } // { dg-warning "not usable" }
> +  // ill-formed, no diagnostic required
> +  // lvalue-to-rvalue conversion on non-constant global
> +};
> +
> +// If no specialization of the template would satisfy the requirements
> +// for a constexpr function when considered as a non-template function,
> +// the template is ill-formed, no diagnostic required.
> +template<typename>
> +constexpr void
> +fn2 ()
> +{
> +  int i = 42;
> +  f (i);
> +}
> +
> +void
> +fn3 ()
> +{
> +  fn2<int>();
> +}
> +
> +constexpr volatile int cvi = 10;
> +
> +constexpr int
> +fn4 ()
> +{
> +  return cvi;  // { dg-warning "lvalue-to-rvalue conversion" }
> +}
> +
> +constexpr unsigned int
> +fn5 (int *p)
> +{
> +  unsigned int *q = reinterpret_cast<unsigned int *>(p); // { dg-warning "reinterpret_cast" }
> +  return *q;
> +}
> +
> +constexpr int
> +fn6 (int i)
> +{
> +  void *p = (void *) 1LL; // { dg-warning ".reinterpret_cast. from integer to pointer" }
> +  return 42;
> +}
> +
> +constexpr int
> +fn7 (int i)
> +{
> +  static int s = i; // { dg-warning "static" }
> +  return s;
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit11.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit11.C
> new file mode 100644
> index 00000000000..a7114bc66cb
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit11.C
> @@ -0,0 +1,53 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++23 } }
> +// { dg-options "-Winvalid-constexpr -pedantic-errors" }
> +
> +// [dcl.constexpr]/4 used to say:
> +// The definition of a constexpr constructor whose function-body
> +// is not = delete shall additionally satisfy the following requirements:
> +// (4.1) for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
> +// (4.2) for a delegating constructor, the target constructor shall be a constexpr constructor.
> +
> +// This continues to be OK.
> +struct Length {
> +  constexpr explicit Length(int i = 0) : val(i) { }
> +private:
> +  int val;
> +};
> +
> +struct X {
> +  X() {}
> +  X(int i_) : i(i_) {}
> +  int i;
> +};
> +
> +struct S {
> +  X x;
> +  // Calls a non-constexpr constructor X::X(int).
> +  constexpr S(int i) : x(i) { } // { dg-warning "call to" }
> +  S(int, int) { }
> +  // Target constructor isn't constexpr.
> +  constexpr S() : S(42, 42) { } // { dg-warning "call to" }
> +};
> +
> +namespace N1 {
> +struct X {
> +  void x();
> +};
> +struct Y {
> +  X x;
> +  constexpr void y() { x.x(); } // { dg-warning "call to" }
> +};
> +}
> +
> +void g();
> +
> +struct A {
> +  constexpr A() { g(); } // { dg-warning "call to" }
> +};
> +
> +struct B {
> +  constexpr B& operator=(const B&) { g(); return *this; } // { dg-warning "call to" }
> +  constexpr B& operator=(B&&) { g(); return *this; } // { dg-warning "call to" }
> +};
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit12.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit12.C
> new file mode 100644
> index 00000000000..dd15e1552d7
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit12.C
> @@ -0,0 +1,32 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++23 } }
> +// Test that we get a diagnostic even in C++23 if you do call the function.
> +
> +constexpr unsigned int
> +fn0 (const int *p)
> +{
> +  return *reinterpret_cast<unsigned const int *>(p); // { dg-error ".reinterpret_cast. is not a constant expression" }
> +}
> +
> +constexpr void *
> +fn1 (int i)
> +{
> +  return (void *) 1LL;
> +}
> +
> +void
> +g ()
> +{
> +  constexpr int i = 42;
> +  /* The diagnostics differ.  fn0 is considered potentially-constant, but fn1
> +     isn't due to "reinterpret_cast from integer to pointer".  So for fn1,
> +     maybe_save_constexpr_fundef doesn't register_constexpr_fundef because
> +     'potential' is false.  Then cxx_eval_call_expression issues the "called
> +     in a constant expression" error, and explain_invalid_constexpr_fn doesn't
> +     explain what the problem is because it has "Only diagnose defaulted
> +     functions, lambdas, or instantiations."  (With -Winvalid-constexpr, we
> +     emit more information.)  */
> +  constexpr auto a1 = fn0 (&i);
> +  constexpr auto a2 = fn1 (i); // { dg-error "called in a constant expression" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit13.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit13.C
> new file mode 100644
> index 00000000000..7997e8e2c3c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit13.C
> @@ -0,0 +1,14 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++23 } }
> +// { dg-options "-Winvalid-constexpr" }
> +
> +constexpr volatile int i = 10;
> +
> +constexpr int
> +bar ()
> +{
> +  return i;  // { dg-warning "lvalue-to-rvalue conversion of a volatile lvalue" }
> +}
> +
> +constexpr int x = bar (); // { dg-error "called in a constant expression" }
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit14.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit14.C
> new file mode 100644
> index 00000000000..f79ff15cbe2
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit14.C
> @@ -0,0 +1,26 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++20 } }
> +// { dg-options "" }
> +// The definition of a constexpr destructor whose function-body is not
> +//  =delete shall additionally satisfy the following requirement:
> +//  (5.1) for every subobject of class type or (possibly multi-dimensional)
> +//  array thereof, that class type shall have a constexpr destructor.
> +
> +struct B {
> +  B() { }
> +  ~B() { }
> +};
> +
> +struct T : B {
> +  constexpr ~T() { }	// { dg-warning "call to" "" { target c++20_down } }
> +};
> +
> +struct S {
> +  constexpr S() = default;              // was error: implicit S() is not constexpr, now OK
> +  ~S() noexcept(false) = default;       // OK, despite mismatched exception specification
> +private:
> +  int i;
> +  S(S&);                                // OK: private copy constructor
> +};
> +S::S(S&) = default;                     // OK: defines copy constructor
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit15.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit15.C
> new file mode 100644
> index 00000000000..6441dabcc70
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit15.C
> @@ -0,0 +1,43 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++23 } }
> +// { dg-options "-Winvalid-constexpr" }
> +// A copy/move assignment operator for a class X that is defaulted and
> +// not defined as deleted is implicitly defined when it is odr-used,
> +// when it is needed for constant evaluation, or when it is explicitly
> +// defaulted after its first declaration.
> +// The implicitly-defined copy/move assignment operator is constexpr.
> +
> +struct S {
> +  constexpr S() {}
> +  S& operator=(const S&) = default; // #1
> +  S& operator=(S&&) = default; // #2
> +};
> +
> +struct U {
> +  constexpr U& operator=(const U&) = default;
> +  constexpr U& operator=(U&&) = default;
> +};
> +
> +/* FIXME: If we only declare #1 and #2, and default them here:
> +
> +   S& S::operator=(const S&) = default;
> +   S& S::operator=(S&&) = default;
> +
> +then they aren't constexpr.  This sounds like a bug:
> +<https://gcc.gnu.org/PR107598>.  */
> +
> +constexpr void
> +g ()
> +{
> +  S a;
> +  S b;
> +  b = a;
> +  b = S{};
> +
> +  U u, v;
> +  u = v;
> +  u = U{};
> +}
> +
> +static_assert ((g(), true), "");
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit16.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit16.C
> new file mode 100644
> index 00000000000..a6c4d19ffc6
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit16.C
> @@ -0,0 +1,23 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++20 } }
> +// { dg-options "" }
> +
> +template <typename T>
> +struct Wrapper {
> +    constexpr Wrapper() = default;
> +    constexpr Wrapper(Wrapper const&) = default;
> +    constexpr Wrapper(T const& t) : t(t) { }
> +
> +    constexpr T get() const { return t; }
> +    constexpr bool operator==(Wrapper const&) const = default; // { dg-warning "call to" "" { target c++20_down } }
> +private:
> +    T t;
> +};
> +
> +struct X {
> +    X();
> +    bool operator==(X const&) const;
> +};
> +
> +Wrapper<X> x;
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit3.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit3.C
> index 3b5585dcd84..2238db91157 100644
> --- a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit3.C
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit3.C
> @@ -5,6 +5,6 @@ constexpr int
>   foo ()
>   {
>     goto lab;	// { dg-error "'goto' in 'constexpr' function only available with" "" { target c++20_down } }
> -lab:		// { dg-error "'goto' is not a constant expression" "" { target { c++23 } } .-1 }
> +lab:
>     return 1;
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit6.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit6.C
> index fbeb83075b0..5a996cbc5c2 100644
> --- a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit6.C
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit6.C
> @@ -5,7 +5,7 @@
>   constexpr int
>   foo ()
>   {
> -  goto lab;		// { dg-error "'goto' is not a constant expression" }
> +  goto lab;		// { dg-error "'goto' is not a constant expression" "" { target c++20_down }  }
>   lab:
>     return 1;
>   }
> @@ -13,13 +13,24 @@ lab:
>   constexpr int
>   bar ()
>   {
> -  static int a;		// { dg-error "'a' defined 'static' in 'constexpr' context" }
> +  static int a;		// { dg-error "'a' defined 'static' in 'constexpr' context" "" { target c++20_down } }
>     return ++a;
>   }
>   
>   constexpr int
> -baz (int x)
> +baz ()
>   {
> -  thread_local int a;	// { dg-error "'a' defined 'thread_local' in 'constexpr' context" }
> +  thread_local int a;	// { dg-error "'a' defined 'thread_local' in 'constexpr' context" "" { target c++20_down } }
>     return ++a;
>   }
> +
> +// In C++23, we get errors about the non-constant expressions only if we
> +// actually call the functions in a constexpr context.
> +
> +void
> +test ()
> +{
> +  constexpr int a = foo (); // { dg-error "constant expression" }
> +  constexpr int b = bar (); // { dg-error "constant expression" }
> +  constexpr int c = baz (); // { dg-error "constant expression" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit8.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit8.C
> new file mode 100644
> index 00000000000..3fb1b93bd07
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit8.C
> @@ -0,0 +1,96 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++14 } }
> +// { dg-options "" }
> +
> +// No constexpr constructors = not a literal type.
> +struct NonLiteral {
> +  NonLiteral() {}
> +};
> +
> +// C++23: It is possible to write a constexpr function for which no
> +// invocation satisfies the requirements of a core constant expression.
> +constexpr NonLiteral
> +fn0 (int) // { dg-warning "invalid return type" "" { target c++20_down } }
> +{
> +  return NonLiteral{};
> +}
> +
> +constexpr int
> +fn1 (NonLiteral) // { dg-warning "invalid type" "" { target c++20_down } }
> +{
> +  return 42;
> +}
> +
> +// From P2448.
> +void f(int& i) {
> +    i = 0;
> +}
> +
> +constexpr void g(int& i) {
> +    f(i); // { dg-warning "call to" "" { target c++20_down } }
> +}
> +
> +// [dcl.constexpr] used to have this.
> +constexpr int f(bool b)
> +  { return b ? throw 0 : 0; }           // OK
> +constexpr int f() { return f(true); }   // ill-formed, no diagnostic required
> +
> +struct B {
> +  constexpr B(int) : i(0) { }
> +  int i;
> +};
> +
> +int global;
> +
> +struct D : B {
> +  constexpr D() : B(global) { } // { dg-warning "not usable" "" { target c++20_down } }
> +  // ill-formed, no diagnostic required
> +  // lvalue-to-rvalue conversion on non-constant global
> +};
> +
> +// If no specialization of the template would satisfy the requirements
> +// for a constexpr function when considered as a non-template function,
> +// the template is ill-formed, no diagnostic required.
> +template<typename>
> +constexpr void
> +fn2 ()
> +{
> +  int i = 42;
> +  f (i);
> +}
> +
> +void
> +fn3 ()
> +{
> +  fn2<int>();
> +}
> +
> +constexpr volatile int cvi = 10;
> +
> +constexpr int
> +fn4 ()
> +{
> +  return cvi;  // { dg-warning "lvalue-to-rvalue conversion" "" { target c++20_down } }
> +}
> +
> +constexpr unsigned int
> +fn5 (int *p)
> +{
> +  unsigned int *q = reinterpret_cast<unsigned int *>(p); // { dg-warning "reinterpret_cast" "" { target c++20_down } }
> +  return *q;
> +}
> +
> +constexpr int
> +fn6 (int i)
> +{
> +  void *p = (void *) 1LL; // { dg-warning ".reinterpret_cast. from integer to pointer" "" { target c++20_down } }
> +  return 42;
> +}
> +
> +constexpr int
> +fn7 (int i)
> +{
> +  static int s = i; // { dg-error "static" "" { target c++20_down } }
> +  return s;
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit9.C b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit9.C
> new file mode 100644
> index 00000000000..228e90f14c1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit9.C
> @@ -0,0 +1,53 @@
> +// PR c++/106649
> +// P2448 - Relaxing some constexpr restrictions
> +// { dg-do compile { target c++14 } }
> +// { dg-options "" }
> +
> +// [dcl.constexpr]/4 used to say:
> +// The definition of a constexpr constructor whose function-body
> +// is not = delete shall additionally satisfy the following requirements:
> +// (4.1) for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
> +// (4.2) for a delegating constructor, the target constructor shall be a constexpr constructor.
> +
> +// This continues to be OK.
> +struct Length {
> +  constexpr explicit Length(int i = 0) : val(i) { }
> +private:
> +  int val;
> +};
> +
> +struct X {
> +  X() {}
> +  X(int i_) : i(i_) {}
> +  int i;
> +};
> +
> +struct S {
> +  X x;
> +  // Calls a non-constexpr constructor X::X(int).
> +  constexpr S(int i) : x(i) { } // { dg-warning "call to" "" { target c++20_down } }
> +  S(int, int) { }
> +  // Target constructor isn't constexpr.
> +  constexpr S() : S(42, 42) { } // { dg-warning "call to" "" { target c++20_down } }
> +};
> +
> +namespace N1 {
> +struct X {
> +  void x();
> +};
> +struct Y {
> +  X x;
> +  constexpr void y() { x.x(); } // { dg-warning "call to" "" { target c++20_down } }
> +};
> +}
> +
> +void g();
> +
> +struct A {
> +  constexpr A() { g(); } // { dg-warning "call to" "" { target c++20_down } }
> +};
> +
> +struct B {
> +  constexpr B& operator=(const B&) { g(); return *this; } // { dg-warning "call to" "" { target c++20_down } }
> +  constexpr B& operator=(B&&) { g(); return *this; } // { dg-warning "call to" "" { target c++20_down } }
> +};
> diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> index efe97703a98..621c8dc21ea 100644
> --- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> +++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
> @@ -134,8 +134,8 @@
>   
>   #ifndef __cpp_constexpr
>   #  error "__cpp_constexpr"
> -#elif __cpp_constexpr != 202110
> -#  error "__cpp_constexpr != 202110"
> +#elif __cpp_constexpr != 202207
> +#  error "__cpp_constexpr != 202207"
>   #endif
>   
>   #ifndef __cpp_decltype_auto
> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
> index 83463868668..627ab142d5a 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
> @@ -57,7 +57,6 @@ consteval int
>   f13 (int x)
>   {
>     static int a = 5;		// { dg-error "'a' defined 'static' in 'consteval' function only available with" "" { target c++20_only } }
> -				// { dg-error "'a' defined 'static' in 'constexpr' context" "" { target c++23 } .-1 }
>     thread_local int b = 6;	// { dg-error "'b' defined 'thread_local' in 'consteval' function only available with" "" { target c++20_only } }
>     return x;
>   }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C
> index bb60a8ee91b..b2c98853882 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C
> @@ -13,7 +13,7 @@ void *operator new (std::size_t) noexcept;
>   constexpr bool
>   foo ()
>   {
> -  auto p = static_cast<int *> (::operator new (sizeof (int)));	// { dg-error "call to non-'constexpr' function" }
> +  auto p = static_cast<int *> (::operator new (sizeof (int)));	// { dg-error "call to non-'constexpr' function" "" { target c++20_down } }
>     *p = 1;
>     ::operator delete (p);
>     return false;
> @@ -24,7 +24,7 @@ struct S { constexpr S () : s (0) {} int s; };
>   constexpr bool
>   bar ()
>   {
> -  auto p = static_cast<S *> (::operator new (sizeof (S)));	// { dg-error "call to non-'constexpr' function" }
> +  auto p = static_cast<S *> (::operator new (sizeof (S)));	// { dg-error "call to non-'constexpr' function" "" { target c++20_down } }
>     auto q = new (p) S ();
>     q->s++;
>     q->~S ();
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-try5.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-try5.C
> index 216634dc56c..eb66105d7c4 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-try5.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-try5.C
> @@ -6,7 +6,6 @@ constexpr int foo ()
>   try {			// { dg-warning "function-try-block body of 'constexpr' function only available with" "" { target c++17_down } }
>     int a;		// { dg-error "uninitialized variable 'a' in 'constexpr' function" "" { target c++17_down } }
>     static double b = 1.0;// { dg-error "'b' defined 'static' in 'constexpr' function only available with" "" { target c++20_down } }
> -			// { dg-error "'b' defined 'static' in 'constexpr' context" "" { target c++23 } .-1 }
>     goto l;		// { dg-error "'goto' in 'constexpr' function only available with" "" { target c++20_down } }
>     l:;
>     return 0;
> @@ -22,7 +21,6 @@ constexpr int bar ()
>   {
>     int a;		// { dg-error "uninitialized variable 'a' in 'constexpr' function" "" { target c++17_down } }
>     static long double b = 3.0;// { dg-error "'b' defined 'static' in 'constexpr' function only available with" "" { target c++20_down } }
> -			// { dg-error "'b' defined 'static' in 'constexpr' context" "" { target c++23 } .-1 }
>     goto l;		// { dg-error "'goto' in 'constexpr' function only available with" "" { target c++20_down } }
>     l:;
>     try {			// { dg-warning "'try' in 'constexpr' function only available with" "" { target c++17_down } }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C
> index dff59271a1e..fb62ecbfdb5 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C
> @@ -9,7 +9,7 @@ struct A
>   struct B
>   {
>     A a;
> -  bool operator==(const B&) const = default; // { dg-error "A::operator==" "" { target { ! implicit_constexpr } } }
> +  bool operator==(const B&) const = default; // { dg-error "A::operator==" "" { target { { ! implicit_constexpr } && c++20_down } } }
>   };
>   
>   constexpr bool x = B() == B();	// { dg-error "non-.constexpr" "" { target { ! implicit_constexpr } } }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C
> index 7a517a8016c..69eaa7b9b20 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C
> @@ -7,8 +7,8 @@ struct A {
>   struct D
>   {
>     A i;
> -  bool operator==(const D& x) const = default; // { dg-error "A::operator==" }
> -  bool operator!=(const D& z) const = default; // { dg-error "D::operator==" }
> +  bool operator==(const D& x) const = default; // { dg-error "A::operator==" "" { target c++20_down } }
> +  bool operator!=(const D& z) const = default; // { dg-error "D::operator==" "" { target c++20_down } }
>   };
>   
>   constexpr D d{A()};
> diff --git a/gcc/testsuite/g++.dg/diagnostic/constexpr1.C b/gcc/testsuite/g++.dg/diagnostic/constexpr1.C
> index c962a60c847..19242d15ba8 100644
> --- a/gcc/testsuite/g++.dg/diagnostic/constexpr1.C
> +++ b/gcc/testsuite/g++.dg/diagnostic/constexpr1.C
> @@ -1,7 +1,5 @@
>   // { dg-do compile { target c++11 } }
>   
>   constexpr int foo() { thread_local int i __attribute__((unused)) {}; return 1; }  // { dg-error "40:.i. defined .thread_local." "" { target c++20_down } }
> -// { dg-error "40:.i. defined .thread_local. in .constexpr. context" "" { target c++23 } .-1 }
>   
>   constexpr int bar() { static int i __attribute__((unused)) {}; return 1; }  // { dg-error "34:.i. defined .static." "" { target c++20_down } }
> -// { dg-error "34:.i. defined .static. in .constexpr. context" "" { target c++23 } .-1 }
> diff --git a/gcc/testsuite/g++.dg/gomp/pr79664.C b/gcc/testsuite/g++.dg/gomp/pr79664.C
> index 582eedb6d6d..f4c30c0b3f4 100644
> --- a/gcc/testsuite/g++.dg/gomp/pr79664.C
> +++ b/gcc/testsuite/g++.dg/gomp/pr79664.C
> @@ -1,6 +1,6 @@
>   // PR c++/79664
>   // { dg-do compile }
> -// { dg-options "-std=c++14 -fopenmp" }
> +// { dg-options "-std=c++14 -fopenmp -Winvalid-constexpr -pedantic-errors" }
>   
>   constexpr int
>   f1 ()
> diff --git a/gcc/testsuite/g++.dg/ubsan/vptr-4.C b/gcc/testsuite/g++.dg/ubsan/vptr-4.C
> index a21d3d60a3a..1efd3f77a55 100644
> --- a/gcc/testsuite/g++.dg/ubsan/vptr-4.C
> +++ b/gcc/testsuite/g++.dg/ubsan/vptr-4.C
> @@ -1,7 +1,7 @@
>   // Verify that -fsanitize=vptr downcast instrumentation works properly
>   // inside of constexpr.
>   // { dg-do compile }
> -// { dg-options "-std=c++11 -fsanitize=vptr" }
> +// { dg-options "-std=c++11 -fsanitize=vptr -Winvalid-constexpr -pedantic-errors" }
>   
>   struct S {
>     constexpr S() : a(0) {}
> 
> base-commit: e505f7493bed1395d121d2f53137ec11706fa42e


  reply	other threads:[~2022-11-14 23:01 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-09 20:53 Marek Polacek
2022-11-14 23:00 ` Jason Merrill [this message]
2022-11-16  0:30   ` [PATCH v2] " Marek Polacek
2022-11-16 13:41     ` Jason Merrill
2022-11-16 16:06       ` [PATCH v3] " Marek Polacek
2022-11-16 20:27         ` Jason Merrill
2022-11-19  2:26           ` Jason Merrill
2022-12-02 19:48             ` Marek Polacek
2022-12-02 20:01               ` Jason Merrill

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5cfabd39-8afe-931c-7872-1b4360d2e5b5@redhat.com \
    --to=jason@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=polacek@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).