On 10/22/26 6:38 PM, Marek Polacek wrote:
Bootstrapped/regtested on x86_64-pc-linux-gnu and ppc64le-pc-linux-gnu,
ok for trunk?

-- >8 --
This patch implements the second half of P2686R5, references to constexpr
variables.  The first half (constexpr structured bindings) was implemented
by Jakub.  I used Jakub's WIP patch and (mostly) incorporated Jason's
comments in:
<https://gcc.gnu.org/pipermail/gcc-patches/2025-September/694469.html>

This proposal makes

   void f () {
     int i = 0;
     constexpr int &r = i;
     constexpr int *p = &i;
   }

valid by using the symbolic addressing approach: constexpr references
designate a specific object, rather than an address, and retain that
information across constant evaluation contexts.  Note that r and p
will be rejected if made static: the stack frames of r/i and p/i must
match.

What this paper means is that in C++26 static and constexpr
initialization is not the same thing as it used to be.  The former is
about "can this value be emitted as static data" and the latter is
about "is this value a valid result of a constant evaluation in the
C++26 sense".

Currently, we have reduced_constant_expression_p for both.  This
patch removes that and introduces two new predicates:
valid_static_initializer_p (~ static init) and constexpr_representable_p
(~new C++26 rules).  Sadly, the CONSTRUCTOR_NO_CLEARING flag is still
mutated in them; I couldn't find a reasonable way to fix that.

constexpr-referenceability is not a static property; an object is
(or isn't) constexpr-referenceable from a point P.  Thus,
constexpr_point_for_decl.

A word on temporaries.  We have temporaries whose lifetime has been
extended, and temporaries whose lifetime has not been extended, but
we have no way to tell them apart (except by looking into the constexpr
hash map as in this patch).  See CWG 1634 which proposes that
a non-extended temporary has its own distinct storage duration.
Temporaries whose lifetime has not been extended are never constexpr-
referenceable.  So:

   struct A { int a; const int &b; };
   void f() {
     constexpr A e1 = { 1, 2 }; // OK, temp extended to e1
     constexpr A e2(1, 2); // error: e2.b dangles
   }

This patch doesn't add a flag to distinguish them.  Instead, to fix
a crash in constexpr-ref5.C, I added the find_dangling_ref_r walk
and now we say for e2:

   error: constant evaluation refers to a temporary whose lifetime has ended

The decl_maybe_constant_var_p call in set_up_extended_ref_temp is there
solely because of tree-ssa/initlist-opt5.C.

This patch doesn't change mark_use because I didn't need to adjust
it for the ODR wording.

Two bugs that came up:
* we get a wrong "the value of '<temporary>' is not usable in a constant
expression" (PR101557),
* we still don't reject the following (PR70151):

   static constexpr int a = 4;
   static constexpr const int *b = &a + 2;

        PR c++/117784

@@ -5240,24 +5241,113 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, 
tree t,
    return result;
  }
-/* Return true if T is a valid constant initializer. If a CONSTRUCTOR
-   initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will be
-   cleared.  If called recursively on a FIELD_DECL's CONSTRUCTOR, SZ
-   is DECL_SIZE of the FIELD_DECL, otherwise NULL.
+/* For T, an ADDR_EXPR, peel the ADDR_EXPR and component references and return
+   the innermost tree.  */
+
+static tree
+addr_expr_base (tree t)
+{
+  gcc_checking_assert (TREE_CODE (t) == ADDR_EXPR);
+  t = TREE_OPERAND (t, 0);
+  while (TREE_CODE (t) == COMPONENT_REF
+        || (TREE_CODE (t) == ARRAY_REF
+            && TREE_CODE (TREE_OPERAND (t, 1)) == INTEGER_CST)
+        || TREE_CODE (t) == REALPART_EXPR
+        || TREE_CODE (t) == IMAGPART_EXPR)

This condition looks like handled_component_p.

I wondered if get_inner_reference would be useful as well, e.g. for bounds checking, but I guess not.

+    t = TREE_OPERAND (t, 0);
+  return t;
+}
+
+/* Figure out the point P for constexpr_representable_p given a DECL.  Here,
+   NULL_TREE means a point at namespace scope, from which only objects
+   with static storage duration are referenceable.  */
+
+tree
+constexpr_point_for_decl (tree decl)
+{
+  if (decl && VAR_P (decl))
+    return (decl_storage_duration (decl) == dk_auto
+           ? decl_function_context (decl)
+           : NULL_TREE);

What about PARM_DECL?

+  if (!call_stack.is_empty ())

We get here for any argument that isn't a VAR_DECL, which seems far too broad. It seems like it's intended specifically for an argument of NULL_TREE, which isn't mentioned in the function comment.

And maybe assert at this point that decl == NULL_TREE? Or is there another argument that wants this handling?

+    {
+      tree call = call_stack.last ();
+      return cp_get_callee_fndecl_nofold (call);
+    }
+
+  return NULL_TREE;
+}
+
+/* Return true if an object O is constexpr-referenceable from a point POINT
+   as per [expr.const.init]/2:
+   An object o is constexpr-referenceable from a point P if
+   -- o has static storage duration, or
+   -- o has automatic storage duration, and, letting v denote
+     -- the variable corresponding to o's complete object or
+     -- the variable to whose lifetime that of o is extended,
+     the smallest scope enclosing v and the smallest scope enclosing P
+     that are neither
+     -- block scopes nor
+     -- function parameter scopes associated with a requirement-parameter-list
+     are the same function parameter scope.  */
+
+static bool
+constexpr_referenceable_p (tree o, tree point)
+{
+  /* String-literals have static storage duration ([lex.string]).  */
+  if (TREE_CODE (o) == STRING_CST)
+    return true;
+
+  if (!(TREE_CODE (o) == PARM_DECL
+       || TREE_CODE (o) == FUNCTION_DECL
+       || VAR_P (o)))
+    return false;

What else could o be? I'd think gcc_unreachable rather than false. Or nothing here, and hit the assert in decl_storage_duration.

Even handling functions is outside the definition above, but it seems reasonable for simplicity.

+  switch (decl_storage_duration (o))
+    {
+    case dk_static:
+      return true;
+    case dk_thread:
+    case dk_dynamic:
+      return false;
+    case dk_auto:
+      return decl_function_context (o) == point;
+    }
+
+  gcc_unreachable ();
+}
+
+/* Return true if T is constexpr-representable at a point POINT:
+   [expr.const.init]/3: An object or reference x is constexpr-representable
+   at a point P if, for each constituent value of x that points to or past
+   an object o, and for each constituent reference of x that refers to an
+   object o, o is constexpr-referenceable from P.
+
+   If a CONSTRUCTOR initializes all the members, the CONSTRUCTOR_NO_CLEARING
+   flag will be cleared.  If called recursively on a FIELD_DECL's CONSTRUCTOR,
+   SZ is DECL_SIZE of the FIELD_DECL, otherwise NULL_TREE.
     UNION_ELEMENTAL_SUBOBJECT is true when recursing on union elemental
-   subobjects.
-   FIXME speed this up, it's taking 16% of compile time on sieve testcase.  */
+   subobjects.  STRICT_P is true if we are enforcing the language rules
+   about what is a constant expression; false if we are deciding whether
+   something can serve as a static initializer (before C++26 the rules are
+   equivalent, so STRICT_P is moot).  */

It seems odd to call this case "strict" when P2686 is adding to the set of constant expressions. How about calling this parm static_p instead?

But then, what is the difference between static_p == true and point == NULL_TREE? So do we need a separate parm at all?

-bool
-reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */,
-                              bool union_elemental_subobject /* = false */)
+static bool
+constexpr_representable_p (tree t, tree point, tree sz,
+                          bool union_elemental_subobject, bool strict_p)
  {
    if (t == NULL_TREE)
      return false;
+ if (CONSTANT_CLASS_P (t))
+    return true;
+
+  /* We should enforce the P2686 rules only in C++26.  */
+  strict_p &= cxx_dialect >= cxx26;
+
    switch (TREE_CODE (t))
      {
-    case PTRMEM_CST:
      case REFLECT_EXPR:
        /* Even if we can't lower this yet, it's constant.  */
        return true;
@@ -5376,13 +5469,68 @@ ok:
        CONSTRUCTOR_NO_CLEARING (t) = false;
        return true;
+ case ADDR_EXPR:
+      if (!strict_p)
+       break;
+      /* This is just an optimization: a TREE_CONSTANT address has a static
+        base, so it's referenceable from anywhere.  */
+      if (TREE_CONSTANT (t))
+       return true;
+      return constexpr_referenceable_p (addr_expr_base (t), point);
+
+    case POINTER_PLUS_EXPR:
+      if (!strict_p)
+       break;
+      if (TREE_CODE (TREE_OPERAND (t, 1)) != INTEGER_CST)
+       return false;
+      return constexpr_representable_p (TREE_OPERAND (t, 0), point,
+                                       /*sz=*/NULL_TREE,
+                                       union_elemental_subobject,
+                                       strict_p);
+
+    CASE_CONVERT:
+      if (!strict_p)
+       break;
+      if (!POINTER_TYPE_P (TREE_TYPE (t))
+         || !POINTER_TYPE_P (TREE_TYPE (TREE_OPERAND (t, 0))))
+       break;
+      return constexpr_representable_p (TREE_OPERAND (t, 0), point,
+                                       /*sz=*/NULL_TREE,
+                                       union_elemental_subobject,
+                                       strict_p);
+
      default:
-      /* FIXME are we calling this too much?  */

Was there a reason to drop this comment and the related FIXME in the function comment?

-      return initializer_constant_valid_p (t, TREE_TYPE (t)) != NULL_TREE;
+      break;
      }
+
+  return initializer_constant_valid_p (t, TREE_TYPE (t)) != NULL_TREE;
+}
+
@@ -5444,13 +5592,15 @@ verify_constant_explain_r (tree *tp, int 
*walk_subtrees, void *)
     We only call this in places that require an arithmetic constant, not in
     places where we might have a non-constant expression that can be a
     component of a constant expression, such as the address of a constexpr
-   variable that might be dereferenced later.  */
+   variable that might be dereferenced later.  POINT is the point used when
+   checking if T is constexpr-representable.  */
static bool
  verify_constant (tree t, bool allow_non_constant, bool *non_constant_p,
-                bool *overflow_p)
+                bool *overflow_p,
+                tree point/*=constexpr_point_for_decl (NULL_TREE)*/)
  {
-  if (!*non_constant_p && !reduced_constant_expression_p (t)
+  if (!*non_constant_p && !constexpr_representable_p (t, point)
        && t != void_node)
      {
        if (!allow_non_constant)
@@ -11510,6 +11660,34 @@ check_for_failed_contracts (constexpr_ctx *ctx)
    return error;
  }
+struct dangling_data {That seems like + constexpr_global_ctx *global;
+  tree object;
+};
+
+/* Helper to find &D.1234 where D.1234 is a temporary whose lifetime hasn't
+   been extended.  */
+
+static tree
+find_dangling_ref_r (tree *tp, int *walk_subtrees, void *data_)
+{
+  auto data = static_cast <dangling_data *> (data_);
+  tree t = *tp;
+
+  if (TYPE_P (t))
+    *walk_subtrees = 0;
+  else if (TREE_CODE (t) == ADDR_EXPR)
+    {
+      t = addr_expr_base (t);
+      if (t != data->object
+         && data->global->get_value_ptr (t, /*initializing=*/false))
+       return t;
+      *walk_subtrees = 0;
+    }
+
+  return NULL_TREE;
+}
+
  /* ALLOW_NON_CONSTANT is false if T is required to be a constant expression.
     STRICT has the same sense as for constant_value_1: true if we only allow
     conforming C++ constant expressions, or false if we want a constant value
@@ -11803,10 +11981,34 @@ cxx_eval_outermost_constant_expr (tree t, bool 
allow_non_constant,
        non_constant_p = true;
      }
+ /* If OBJECT is the slot of a TARGET_EXPR, it can be elided or copied to
+     the object being initialized, so it isn't itself that object.  */

Could TARGET_EXPR_ELIDING_P be useful?

Also, when does that make a difference? That is, in such cases why does passing NULL_TREE give a different result from passing object?

A related testcase:

struct X {
  const X *self;
  constexpr X () : self (this) {}
};

constexpr X make () { return X (); }

void
f ()
{
  constexpr X x = X ();         // ok with this patch
  static_assert (x.self == &x); // ok with this patch

  constexpr X y = make ();      // bogus error
  static_assert (y.self == &y); // bogus error
}

Maybe if TARGET_EXPR_ELIDING_P it's wrong to fall back to NULL_TREE, and we should instead say it's non-constant until we know the real object?

+  tree init_object = (TREE_CODE (t) == TARGET_EXPR
+                     && object == TARGET_EXPR_SLOT (t)
+                     ? NULL_TREE : object);
    if (!non_constant_p && !constexpr_dtor)
-    verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p);
+    verify_constant (r, allow_non_constant, &non_constant_p,
+                    &overflow_p, constexpr_point_for_decl (init_object));
>> + /* If we have created a temporary during this evaluation, its lifetime
+     will end at the end of the full-expression.  The result that refers to
+     such a temporary would dangle.  Lifetime-extended temporaries from
+     make_temporary_var_for_ref_to_temp were built outside this evaluation.  */
+  if (!non_constant_p && cxx_dialect >= cxx26 && !TREE_CONSTANT (r))
+    {
+      dangling_data dd = { &global_ctx, init_object };
+      if (cp_walk_tree_without_duplicates (&r, find_dangling_ref_r, &dd))

Let's move more of these details into a find_dangling_ref function so this function can just ask the question.

diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C 
b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C
index 398e079f724..49303f35ca6 100644
--- a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C
+++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C
@@ -6,7 +6,7 @@ struct A { int *const &r; };
 void
 foo (int x)
 {
-  constexpr A a = { &x };  // { dg-error "constant" }
-  static_assert (a.r == &x, ""); // { dg-error "non-constant" }
-  [&] { static_assert (a.r != nullptr, ""); } (); // { dg-error "non-constant" 
}
+  constexpr A a = { &x };  // { dg-error "constant" "" { target c++23_down } }
+  static_assert (a.r == &x, ""); // { dg-error "non-constant" "" { target 
c++23_down } }
+  [&] { static_assert (a.r != nullptr, ""); } (); // { dg-error "non-constant|not 
usable" }

The (remaining) error here includes

lambda-constexpr3a.C:9:15: note: ‘a’ used in its own initializer

which is inaccurate, can we improve that?

Jason

Reply via email to