diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 2eb54b5b6ed..e5e9c6c44f8 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9682,7 +9682,7 @@ build_trivial_dtor_call (tree instance, bool no_ptr_deref)
     }
 
   /* A trivial destructor should still clobber the object.  */
-  tree clobber = build_clobber (TREE_TYPE (instance));
+  tree clobber = build_clobber (TREE_TYPE (instance), CLOBBER_EOL);
   return build2 (MODIFY_EXPR, void_type_node,
 		 instance, clobber);
 }
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index c05760e6789..4f0f590c38a 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -1193,13 +1193,20 @@ public:
 	return *p;
     return NULL_TREE;
   }
-  tree *get_value_ptr (tree t)
+  tree *get_value_ptr (tree t, bool initializing)
   {
     if (modifiable && !modifiable->contains (t))
       return nullptr;
     if (tree *p = values.get (t))
-      if (*p != void_node)
-	return p;
+      {
+	if (*p != void_node)
+	  return p;
+	else if (initializing)
+	  {
+	    *p = NULL_TREE;
+	    return p;
+	  }
+      }
     return nullptr;
   }
   void put_value (tree t, tree v)
@@ -1208,13 +1215,20 @@ public:
     if (!already_in_map && modifiable)
       modifiable->add (t);
   }
-  void remove_value (tree t)
+  void destroy_value (tree t)
   {
-    if (DECL_P (t))
+    if ((TREE_CODE (t) == VAR_DECL
+	 || TREE_CODE (t) == PARM_DECL
+	 || TREE_CODE (t) == RESULT_DECL)
+	&& DECL_NAME (t) != in_charge_identifier)
       values.put (t, void_node);
     else
       values.remove (t);
   }
+  void clear_value (tree t)
+  {
+    values.remove (t);
+  }
 };
 
 /* Helper class for constexpr_global_ctx.  In some cases we want to avoid
@@ -1238,7 +1252,7 @@ public:
   ~modifiable_tracker ()
   {
     for (tree t: set)
-      global->remove_value (t);
+      global->clear_value (t);
     global->modifiable = nullptr;
   }
 };
@@ -1278,6 +1292,40 @@ struct constexpr_ctx {
   mce_value manifestly_const_eval;
 };
 
+/* Remove T from the global values map, checking for attempts to destroy
+   a value that has already finished its lifetime.  */
+
+static void
+destroy_value_checked (const constexpr_ctx* ctx, tree t, bool *non_constant_p)
+{
+  if (t == error_mark_node || TREE_TYPE (t) == error_mark_node)
+    return;
+
+  /* Don't error again here if we've already reported a problem.  */
+  if (!*non_constant_p
+      && DECL_P (t)
+      /* Non-trivial destructors have their lifetimes ended explicitly
+	 with a clobber, so don't worry about it here.  */
+      && (!TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (t))
+	  /* ...except parameters are remapped in cxx_eval_call_expression,
+	     and the destructor call during cleanup won't be able to tell that
+	     this value has already been destroyed, so complain now.  This is
+	     not quite unobservable, but is extremely unlikely to crop up in
+	     practice; see g++.dg/cpp2a/constexpr-lifetime2.C.  */
+	  || TREE_CODE (t) == PARM_DECL)
+      && ctx->global->is_outside_lifetime (t))
+    {
+      if (!ctx->quiet)
+	{
+	  auto_diagnostic_group d;
+	  error ("destroying %qE outside its lifetime", t);
+	  inform (DECL_SOURCE_LOCATION (t), "declared here");
+	}
+      *non_constant_p = true;
+    }
+  ctx->global->destroy_value (t);
+}
+
 /* This internal flag controls whether we should avoid doing anything during
    constexpr evaluation that would cause extra DECL_UID generation, such as
    template instantiation and function body copying.  */
@@ -2806,6 +2854,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	  && (CALL_FROM_NEW_OR_DELETE_P (t)
 	      || is_std_allocator_allocate (ctx->call)))
 	{
+	  const bool new_op_p = IDENTIFIER_NEW_OP_P (DECL_NAME (fun));
 	  const int nargs = call_expr_nargs (t);
 	  tree arg0 = NULL_TREE;
 	  for (int i = 0; i < nargs; ++i)
@@ -2813,12 +2862,15 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	      tree arg = CALL_EXPR_ARG (t, i);
 	      arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
 						  non_constant_p, overflow_p);
-	      VERIFY_CONSTANT (arg);
+	      /* Deleting a non-constant pointer has a better error message
+		 below.  */
+	      if (new_op_p || i != 0)
+		VERIFY_CONSTANT (arg);
 	      if (i == 0)
 		arg0 = arg;
 	    }
 	  gcc_assert (arg0);
-	  if (IDENTIFIER_NEW_OP_P (DECL_NAME (fun)))
+	  if (new_op_p)
 	    {
 	      tree type = build_array_type_nelts (char_type_node,
 						  tree_to_uhwi (arg0));
@@ -2867,7 +2919,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 			  return t;
 			}
 		      DECL_NAME (var) = heap_deleted_identifier;
-		      ctx->global->remove_value (var);
+		      ctx->global->destroy_value (var);
 		      ctx->global->heap_dealloc_count++;
 		      return void_node;
 		    }
@@ -2890,7 +2942,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 			  return t;
 			}
 		      DECL_NAME (var) = heap_deleted_identifier;
-		      ctx->global->remove_value (var);
+		      ctx->global->destroy_value (var);
 		      ctx->global->heap_dealloc_count++;
 		      return void_node;
 		    }
@@ -3242,9 +3294,9 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 				      non_constant_p, overflow_p);
 
 	  /* Remove the parms/result from the values map.  */
-	  ctx->global->remove_value (res);
+	  destroy_value_checked (ctx, res, non_constant_p);
 	  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
-	    ctx->global->remove_value (parm);
+	    destroy_value_checked (ctx, parm, non_constant_p);
 
 	  /* Free any parameter CONSTRUCTORs we aren't returning directly.  */
 	  while (!ctors->is_empty ())
@@ -5644,6 +5696,10 @@ cxx_fold_indirect_ref_1 (const constexpr_ctx *ctx, location_t loc, tree type,
 	      }
 	  }
 
+      /* Handle conversion to "as base" type.  */
+      if (CLASSTYPE_AS_BASE (optype) == type)
+	return op;
+
       /* Handle conversion to an empty base class, which is represented with a
 	 NOP_EXPR.  Do this before spelunking into the non-empty subobjects,
 	 which is likely to be a waste of time (109678).  */
@@ -5895,7 +5951,7 @@ outside_lifetime_error (location_t loc, tree r)
     }
   else
     {
-      error_at (loc, "accessing object outside its lifetime");
+      error_at (loc, "accessing %qE outside its lifetime", r);
       inform (DECL_SOURCE_LOCATION (r), "declared here");
     }
 }
@@ -6112,8 +6168,10 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
   constexpr_ctx new_ctx = *ctx;
 
   tree init = TREE_OPERAND (t, 1);
-  if (TREE_CLOBBER_P (init))
-    /* Just ignore clobbers.  */
+
+  if (TREE_CLOBBER_P (init)
+      && CLOBBER_KIND (init) != CLOBBER_EOL)
+    /* Only handle clobbers ending the lifetime of storage.  */
     return void_node;
 
   /* First we figure out where we're storing to.  */
@@ -6123,7 +6181,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
 
   tree type = TREE_TYPE (target);
   bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR;
-  if (preeval)
+  if (preeval && !TREE_CLOBBER_P (init))
     {
       /* Evaluate the value to be stored without knowing what object it will be
 	 stored in, so that any side-effects happen first.  */
@@ -6231,11 +6289,18 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
       && const_object_being_modified == NULL_TREE)
     const_object_being_modified = object;
 
+  if (DECL_P (object)
+      && TREE_CLOBBER_P (init)
+      && DECL_NAME (object) == heap_deleted_identifier)
+    /* Ignore clobbers of deleted allocations for now; we'll get a better error
+       message later when operator delete is called.  */
+    return void_node;
+
   /* And then find/build up our initializer for the path to the subobject
      we're initializing.  */
   tree *valp;
   if (DECL_P (object))
-    valp = ctx->global->get_value_ptr (object);
+    valp = ctx->global->get_value_ptr (object, TREE_CODE (t) == INIT_EXPR);
   else
     valp = NULL;
   if (!valp)
@@ -6243,10 +6308,45 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
       /* A constant-expression cannot modify objects from outside the
 	 constant-expression.  */
       if (!ctx->quiet)
-	error ("modification of %qE is not a constant expression", object);
+	{
+	  auto_diagnostic_group d;
+	  if (DECL_P (object) && DECL_NAME (object) == heap_deleted_identifier)
+	    {
+	      error ("modification of allocated storage after deallocation "
+		     "is not a constant expression");
+	      inform (DECL_SOURCE_LOCATION (object), "allocated here");
+	    }
+	  else if (DECL_P (object) && ctx->global->is_outside_lifetime (object))
+	    {
+	      if (TREE_CLOBBER_P (init))
+		error ("destroying %qE outside its lifetime", object);
+	      else
+		error ("modification of %qE outside its lifetime "
+		       "is not a constant expression", object);
+	      inform (DECL_SOURCE_LOCATION (object), "declared here");
+	    }
+	  else
+	    {
+	      if (TREE_CLOBBER_P (init))
+		error ("destroying %qE from outside current evaluation "
+		       "is not a constant expression", object);
+	      else
+		error ("modification of %qE from outside current evaluation "
+		       "is not a constant expression", object);
+	    }
+	}
       *non_constant_p = true;
       return t;
     }
+
+  /* Handle explicit end-of-lifetime.  */
+  if (TREE_CLOBBER_P (init))
+    {
+      if (refs->is_empty ())
+	ctx->global->destroy_value (object);
+      return void_node;
+    }
+
   type = TREE_TYPE (object);
   bool no_zero_init = true;
 
@@ -6520,7 +6620,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
       /* The hash table might have moved since the get earlier, and the
 	 initializer might have mutated the underlying CONSTRUCTORs, so we must
 	 recompute VALP. */
-      valp = ctx->global->get_value_ptr (object);
+      valp = ctx->global->get_value_ptr (object, TREE_CODE (t) == INIT_EXPR);
       for (unsigned i = 0; i < vec_safe_length (indexes); i++)
 	{
 	  ctors[i] = valp;
@@ -7631,7 +7731,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
 	   full-expression.  */
 	for (tree save_expr : save_exprs)
-	  ctx->global->remove_value (save_expr);
+	  destroy_value_checked (ctx, save_expr, non_constant_p);
       }
       break;
 
@@ -8184,13 +8284,18 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 				      non_constant_p, overflow_p, jump_target);
 
     case BIND_EXPR:
+      /* Pre-emptively clear the vars declared by this BIND_EXPR from the value
+	 map, so that when checking whether they're already destroyed later we
+	 don't get confused by remnants of previous calls.  */
+      for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
+	ctx->global->clear_value (decl);
       r = cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
 					lval,
 					non_constant_p, overflow_p,
 					jump_target);
       for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
-	ctx->global->remove_value (decl);
-      return r;
+	destroy_value_checked (ctx, decl, non_constant_p);
+      break;
 
     case PREINCREMENT_EXPR:
     case POSTINCREMENT_EXPR:
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 16af59de696..43f6379befb 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -17313,7 +17313,7 @@ implicit_default_ctor_p (tree fn)
    storage is dead when we enter the constructor or leave the destructor.  */
 
 static tree
-build_clobber_this ()
+build_clobber_this (bool is_destructor)
 {
   /* Clobbering an empty base is pointless, and harmful if its one byte
      TYPE_SIZE overlays real data.  */
@@ -17329,7 +17329,8 @@ build_clobber_this ()
   if (!vbases)
     ctype = CLASSTYPE_AS_BASE (ctype);
 
-  tree clobber = build_clobber (ctype);
+  enum clobber_kind kind = is_destructor ? CLOBBER_EOL : CLOBBER_UNDEF;
+  tree clobber = build_clobber (ctype, kind);
 
   tree thisref = current_class_ref;
   if (ctype != current_class_type)
@@ -17750,7 +17751,7 @@ start_preparsed_function (tree decl1, tree attrs, int flags)
 	 because part of the initialization might happen before we enter the
 	 constructor, via AGGR_INIT_ZERO_FIRST (c++/68006).  */
       && !implicit_default_ctor_p (decl1))
-    finish_expr_stmt (build_clobber_this ());
+    finish_expr_stmt (build_clobber_this (/*is_destructor=*/false));
 
   if (!processing_template_decl
       && DECL_CONSTRUCTOR_P (decl1)
@@ -17973,7 +17974,8 @@ begin_destructor_body (void)
 	    finish_decl_cleanup (NULL_TREE, stmt);
 	  }
 	else
-	  finish_decl_cleanup (NULL_TREE, build_clobber_this ());
+	  finish_decl_cleanup (NULL_TREE,
+			       build_clobber_this (/*is_destructor=*/true));
       }
 
       /* And insert cleanups for our bases and members so that they
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
index 43aa7c974c1..3fda29e0cc2 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
@@ -10,4 +10,4 @@ constexpr const int& test() {
   auto local = S{};  // { dg-message "note: declared here" }
   return local.get();
 }
-constexpr int x = test();  // { dg-error "accessing object outside its lifetime" }
+constexpr int x = test();  // { dg-error "accessing .local. outside its lifetime" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
index 2f5ae8db6d5..d82ba5c8b73 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
@@ -8,7 +8,7 @@ struct S {
 
 constexpr int error() {
   const auto& local = S{}.get();  // { dg-message "note: declared here" }
-  return local;  // { dg-error "accessing object outside its lifetime" }
+  return local;  // { dg-error "accessing '\[^'\]+' outside its lifetime" }
 }
 constexpr int x = error();  // { dg-message "in .constexpr. expansion" }
 
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
index 53785521d05..67e9b91c723 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
@@ -7,7 +7,7 @@ constexpr int f(int i) {
     int j = 123;  // { dg-message "note: declared here" }
     p = &j;
   }
-  return *p;  // { dg-error "accessing object outside its lifetime" }
+  return *p;  // { dg-error "accessing 'j' outside its lifetime" }
 }
 
 constexpr int i = f(0);  // { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
index 181a1201663..6f0d749dcf2 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
@@ -5,7 +5,7 @@ constexpr const double& test() {
   return local;
 }
 
-static_assert(test() == 3.0, "");  // { dg-error "constant|accessing object outside its lifetime" }
+static_assert(test() == 3.0, "");  // { dg-error "constant|accessing '\[^'\]+' outside its lifetime" }
 
 // no deference, shouldn't error
 static_assert((test(), true), "");
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime7.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime7.C
new file mode 100644
index 00000000000..4148f42f7be
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime7.C
@@ -0,0 +1,93 @@
+// PR c++/71093
+// { dg-do compile { target c++14 } }
+
+constexpr int f (const int *p)
+{
+  typedef int T;
+  p->~T ();   // { dg-error "destroying" }
+  return *p;
+}
+
+constexpr int i = 0;
+constexpr int j = f (&i);
+
+
+template <typename T>
+constexpr bool test_access() {
+  T x {};
+  x.~T();
+  T y = x;  // { dg-error "lifetime" }
+  return true;
+}
+
+template <typename T>
+constexpr bool test_modification() {
+  T x {};
+  x.~T();
+  x = T();  // { dg-error "lifetime" }
+  return true;
+}
+
+template <typename T>
+constexpr bool test_scope() {
+  {
+    T x {};
+    x.~T();
+  }  // { dg-error "destroying" }
+  return true;
+}
+
+template <typename T>
+constexpr bool test_destroy_temp() {
+  T{}.~T();  // { dg-error "destroying" }
+  return true;
+}
+
+template <typename T>
+constexpr bool test_parameter(T t) {
+  // note: error message occurs at point of call
+  t.~T();
+  return true;
+}
+
+template <typename T>
+constexpr void test_bindings_impl(int n) {
+  if (n == 0) return;
+  T a {};
+  if (n == 1) return;
+  T b {};
+}
+
+template <typename T>
+constexpr bool test_bindings() {
+  test_bindings_impl<T>(1);
+  test_bindings_impl<T>(0);
+  test_bindings_impl<T>(2);
+  return true;
+}
+
+constexpr bool i1 = test_access<int>();        // { dg-message "in .constexpr." }
+constexpr bool i2 = test_modification<int>();  // { dg-message "in .constexpr." }
+constexpr bool i3 = test_scope<int>();         // { dg-message "in .constexpr." }
+constexpr bool i4 = test_destroy_temp<int>();  // { dg-message "in .constexpr." "" { xfail *-*-* } }
+constexpr bool i5 = test_parameter(int{});     // { dg-error "destroying" }
+constexpr bool i6 = test_bindings<int>();
+
+struct Trivial { int x; };
+constexpr bool t1 = test_access<Trivial>();        // { dg-message "in .constexpr." }
+constexpr bool t2 = test_modification<Trivial>();  // { dg-message "in .constexpr." }
+constexpr bool t3 = test_scope<Trivial>();         // { dg-message "in .constexpr." }
+constexpr bool t4 = test_destroy_temp<Trivial>();  // { dg-message "in .constexpr." }
+constexpr bool t5 = test_parameter(Trivial{});     // { dg-error "destroying" }
+constexpr bool t6 = test_bindings<Trivial>();
+
+#if __cplusplus >= 202002L
+struct NonTrivial { int x; constexpr ~NonTrivial() {} };  // { dg-error "destroying" "" { target c++20 } }
+constexpr bool n1 = test_access<NonTrivial>();        // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n2 = test_modification<NonTrivial>();  // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n3 = test_scope<NonTrivial>();         // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n4 = test_destroy_temp<NonTrivial>();  // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n5 = test_parameter(NonTrivial{});     // { dg-error "destroying" "" { target c++20 } }
+constexpr bool n6 = test_bindings<NonTrivial>();
+#endif
+
diff --git a/gcc/testsuite/g++.dg/cpp2a/bitfield2.C b/gcc/testsuite/g++.dg/cpp2a/bitfield2.C
index dcb424fc8f6..885d4f0e26d 100644
--- a/gcc/testsuite/g++.dg/cpp2a/bitfield2.C
+++ b/gcc/testsuite/g++.dg/cpp2a/bitfield2.C
@@ -13,7 +13,7 @@ template <bool V, int W>
 struct U {
   int j : W = 7;		// { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
   int k : W { 8 };		// { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
-  int l : V ? 7 : a = 3;	// { dg-error "modification of .a. is not a constant expression" }
+  int l : V ? 7 : a = 3;	// { dg-error "modification of .a. from outside current evaluation is not a constant expression" }
 				// { dg-error "width not an integer constant" "" { target *-*-* } .-1 }
   int m : (V ? W : b) = 9;	// { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
 				// { dg-error "zero width for bit-field" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime1.C
new file mode 100644
index 00000000000..36163844eca
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime1.C
@@ -0,0 +1,21 @@
+// { dg-do compile { target c++20 } }
+
+#include "construct_at.h"
+
+struct S { int x; };
+constexpr int f() {
+  S s;
+  s.~S();
+  std::construct_at(&s, 5);
+  return s.x;
+}
+static_assert(f() == 5);
+
+struct T { int x; constexpr ~T() {} };
+constexpr int g() {
+  T t;
+  t.~T();
+  std::construct_at(&t, 12);
+  return t.x;
+}
+static_assert(g() == 12);
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime2.C
new file mode 100644
index 00000000000..56cc9e3c1c8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime2.C
@@ -0,0 +1,23 @@
+// { dg-do compile { target c++20 } }
+
+#include "construct_at.h"
+
+struct S { int x; };
+
+constexpr bool foo(S s, S*& p) {
+  p = &s;
+  s.~S();
+  return true;
+}
+
+constexpr bool bar() {
+  // This is, strictly speaking, implementation-defined behaviour;
+  // see [expr.call] p6.  However, in all other cases we destroy
+  // at the end of the full-expression, so the below should be fixed.
+  S* p;
+  foo(S{}, p), std::construct_at(p);  // { dg-bogus "destroying" "" { xfail *-*-* } }
+
+  return true;
+}
+
+constexpr bool x = bar();
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
index 3ba440fec53..5d9f192507b 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
@@ -34,7 +34,7 @@ constexpr auto v3 = f3 ();	// { dg-message "in 'constexpr' expansion of" }
 constexpr bool
 f4 (int *p)
 {
-  delete p;			// { dg-error "deallocation of storage that was not previously allocated" }
+  delete p;			// { dg-error "destroying 'q' from outside current evaluation" }
   return false;
 }
 
@@ -70,3 +70,18 @@ f7 ()
 }
 
 constexpr auto v7 = f7 ();
+
+constexpr bool
+f8_impl (int *p)
+{
+  delete p;			// { dg-error "deallocation of storage that was not previously allocated" }
+  return false;
+}
+
+constexpr bool
+f8 ()
+{
+  int q = 0;
+  return f8_impl (&q);
+}
+constexpr auto v8 = f8 ();	// { dg-message "in 'constexpr' expansion of" }
