On 11/16/22 11:06, Marek Polacek wrote:
On Wed, Nov 16, 2022 at 08:41:53AM -0500, Jason Merrill wrote:
On 11/15/22 19:30, Marek Polacek wrote:
@@ -996,19 +1040,26 @@ register_constexpr_fundef (const constexpr_fundef &value)
     **slot = value;
   }
-/* FUN is a non-constexpr function called in a context that requires a
-   constant expression.  If it comes from a constexpr template, explain why
-   the instantiation isn't constexpr.  */
+/* FUN is a non-constexpr (or, with -Wno-invalid-constexpr, a constexpr
+   function called in a context that requires a constant expression).
+   If it comes from a constexpr template, explain why the instantiation
+   isn't constexpr.  */

The "if it comes from a constexpr template" wording has needed an update for
a while now.

Probably ever since r178519.  I've added "Otherwise, explain why the function
cannot be used in a constexpr context."  Is that acceptable?
--- /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>.  */

As I commented on the PR, I don't think this is actually a bug, so let's
omit this FIXME.

I'm glad I didn't really attempt to "fix" it (the inform message is flawed
and should be improved).  Thanks for taking a look.

Here's a version with the two comments updated.

Ok?

OK.

-- >8 --
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.

This patch includes changes to diagnose the problem if the user doesn't
use -Winvalid-constexpr and calls a constexpr function that in fact isn't
constexpr-ready yet: maybe_save_constexpr_fundef registers the function
if warn_invalid_constexpr is 0 and explain_invalid_constexpr_fn then
gives the diagnostic.

        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.  Register the
        function if -Wno-invalid-constexpr was specified.
        (explain_invalid_constexpr_fn): Don't return early if a function marked
        'constexpr' that isn't actually a constant expression was called.
        (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/invoke.texi: 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-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: 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                           | 299 ++++++++++++------
  gcc/cp/cp-tree.h                              |   1 +
  gcc/cp/method.cc                              |   8 +-
  gcc/doc/invoke.texi                           |  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    |   3 +-
  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   |   4 +-
  .../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-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         |  24 ++
  .../g++.dg/cpp23/constexpr-nonlit13.C         |  14 +
  .../g++.dg/cpp23/constexpr-nonlit14.C         |  26 ++
  .../g++.dg/cpp23/constexpr-nonlit15.C         |  35 ++
  .../g++.dg/cpp23/constexpr-nonlit16.C         |  23 ++
  .../g++.dg/cpp23/constexpr-nonlit3.C          |   2 +-
  .../g++.dg/cpp23/constexpr-nonlit6.C          |  13 +-
  .../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 +-
  47 files changed, 715 insertions(+), 146 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 f0be0598190..26447d02097 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=202211L");
          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 bc1f85e3b85..70745aa4e7c 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 e665839f5b1..a390cf921f3 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,11 +972,16 @@ 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);
        }
      }
- if (!potential && complain)
+  if (!potential && complain
+      /* If -Wno-invalid-constexpr was specified, we haven't complained
+        about non-constant expressions yet.  Register the function and
+        complain in explain_invalid_constexpr_fn if the function is
+        called.  */
+      && warn_invalid_constexpr != 0)
      return;
if (implicit)
@@ -996,19 +1040,27 @@ register_constexpr_fundef (const constexpr_fundef &value)
    **slot = value;
  }
-/* FUN is a non-constexpr function called in a context that requires a
-   constant expression.  If it comes from a constexpr template, explain why
-   the instantiation isn't constexpr.  */
+/* FUN is a non-constexpr (or, with -Wno-invalid-constexpr, a constexpr
+   function called in a context that requires a constant expression).
+   If it comes from a constexpr template, explain why the instantiation
+   isn't constexpr.  Otherwise, explain why the function cannot be used
+   in a constexpr context.  */
void
  explain_invalid_constexpr_fn (tree fun)
  {
    static hash_set<tree> *diagnosed;
    tree body;
+  /* In C++23, a function marked 'constexpr' may not actually be a constant
+     expression.  We haven't diagnosed the problem yet: -Winvalid-constexpr
+     wasn't enabled.  The function was called, so diagnose why it cannot be
+     used in a constant expression.  */
+  if (warn_invalid_constexpr == 0 && DECL_DECLARED_CONSTEXPR_P (fun))
+    /* Go on.  */;
    /* Only diagnose defaulted functions, lambdas, or instantiations.  */
-  if (!DECL_DEFAULTED_FN (fun)
-      && !LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun))
-      && !is_instantiation_of_constexpr (fun))
+  else if (!DECL_DEFAULTED_FN (fun)
+          && !LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun))
+          && !is_instantiation_of_constexpr (fun))
      {
        inform (DECL_SOURCE_LOCATION (fun), "%qD declared here", fun);
        return;
@@ -5612,11 +5664,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 +5678,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 +6751,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 +7039,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 +7930,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 +8815,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 +8833,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 +8860,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 +8921,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 +8971,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 +9002,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 +9023,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 +9058,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 +9097,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 +9123,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 +9133,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 +9156,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 +9230,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 +9379,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 +9391,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 +9441,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 +9455,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 +9471,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;
@@ -9465,9 +9533,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
@@ -9523,15 +9591,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
@@ -9554,9 +9624,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;
        }
@@ -9603,7 +9674,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
@@ -9704,7 +9776,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.  */
@@ -9734,7 +9807,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))
@@ -9772,9 +9845,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;
@@ -9783,8 +9858,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;
@@ -9813,7 +9889,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;
        }
@@ -9822,8 +9899,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:
@@ -9861,7 +9939,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)
      {
@@ -9869,13 +9947,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);
  }
@@ -9884,7 +9963,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. */
@@ -9892,7 +9973,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. */
@@ -9900,7 +9983,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);
  }
@@ -9909,7 +9993,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);
  }
@@ -9918,7 +10013,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);
  }
@@ -9932,7 +10028,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. */
@@ -9940,7 +10038,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. */
@@ -9948,7 +10048,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);
  }
@@ -9958,7 +10059,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 07f96ea861f..811a8345712 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8450,6 +8450,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/invoke.texi b/gcc/doc/invoke.texi
index 96f0b6d08bd..31d031cd25c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -256,7 +256,7 @@ in the following sections.
  -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion 
@gol
  -Weffc++  -Wno-exceptions -Wextra-semi  -Wno-inaccessible-base @gol
  -Wno-inherited-variadic-ctor  -Wno-init-list-lifetime @gol
--Winvalid-imported-macros @gol
+-Winvalid-constexpr -Winvalid-imported-macros @gol
  -Wno-invalid-offsetof  -Wno-literal-suffix @gol
  -Wmismatched-new-delete -Wmismatched-tags @gol
  -Wmultiple-inheritance  -Wnamespaces  -Wnarrowing @gol
@@ -3766,6 +3766,32 @@ the variable declaration statement.
@end itemize +@item -Winvalid-constexpr
+@opindex Winvalid-constexpr
+@opindex Wno-invalid-constexpr
+
+Warn when a function never produces a constant expression.  In C++20
+and earlier, for every @code{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 @code{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:
+
+@smallexample
+void f (int& i);
+constexpr void
+g (int& i)
+@{
+  f(i); // warns by default in C++20, in C++23 only with -Winvalid-constexpr
+@}
+@end smallexample
+
  @item -Winvalid-imported-macros
  @opindex Winvalid-imported-macros
  @opindex Wno-invalid-imported-macros
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..48281a47784 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ex1.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ex1.C
@@ -87,7 +87,8 @@ 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 } } }
+// { dg-error "non-.constexpr." "" { target c++23 } .-2 }
  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..47f7fb05e29 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-neg2.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-neg2.C
@@ -18,10 +18,10 @@ constexpr int three = one() ? 3 : nonconst_func(0);
  constexpr int bogus() { return zero () ? 3 : nonconst_func(0); } // { dg-error 
"nonconst_func" }
// 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-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..8f003b80190
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit12.C
@@ -0,0 +1,24 @@
+// 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; // { dg-error ".reinterpret_cast." }
+}
+
+void
+g ()
+{
+  constexpr int i = 42;
+  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..aa35fa88189
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit15.C
@@ -0,0 +1,35 @@
+// 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;
+  S& operator=(S&&) = default;
+};
+
+struct U {
+  constexpr U& operator=(const U&) = default;
+  constexpr U& operator=(U&&) = default;
+};
+
+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..a1436938318 100644
--- a/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit6.C
+++ b/gcc/testsuite/g++.dg/cpp23/constexpr-nonlit6.C
@@ -18,8 +18,19 @@ bar ()
  }
constexpr int
-baz (int x)
+baz ()
  {
    thread_local int a; // { dg-error "'a' defined 'thread_local' in 'constexpr' 
context" }
    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 85e66871fd1..3ce36e45cb7 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: 7026d0455dce1092975d4884f450a12a6ed205c7


Reply via email to