On Fri, Oct 02, 2026 at 10:39:53PM +0200, Jason Merrill wrote:
> On 10/1/26 9:55 PM, Marek Polacek wrote:
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> >
> > -- >8 --
> > A couple of problems here:
> >
> > [meta.reflection.extract]/5.3 says "If r represents a variable, then
> > either that variable is usable in constant expressions or its lifetime
> > began within the core constant expression currently under evaluation."
> > but I was wrongly using is_constant_expression. That led to a crash
> > because we passed a type to build_address. For "is usable..." we want
> > decl_constant_var_p and for the "its lifetime began..." I'm adding a new
> > function. Using cxx_eval_constant_expression for non-reference
> > variables wouldn't work since evaluating a variable as a glvalue always
> > succeeds, and as a prvalue fails for e.g. an uninitialized variable
> > whose lifetime did begin.
> >
> > With this patch, a variable that isn't usable in constant expressions
> > throws. Thus the extract2.C and extract3.C changes; one can use
> > object_of to get the object instead.
> >
> > Also, DECL_INITIAL of a PARM_DECL is not an initializer but DECL_ARG_TYPE.
> > So we need cxx_eval_constant_expression to handle e.g.:
> >
> > int g;
> > consteval void h (const int &p)
> > {
> > std::meta::extract<const int &> (^^p);
> > }
> > consteval { h (g); }
> >
> > where we need to get "(const int &) &g" from PARM_DECL p.
> >
> > This adds a new XFAIL because we don't have P2686 yet.
> >
> > It seems that object_of will need similar tweaks but that's for another
> > patch.
> >
> > PR c++/127232
> >
> > gcc/cp/ChangeLog:
> >
> > * constexpr.cc (constexpr_global_ctx::within_lifetime_p): New.
> > (cxx_constexpr_in_eval_lifetime_p): New.
> > * cp-tree.h (cxx_constexpr_in_eval_lifetime_p): Declare.
> > * reflect.cc (extract_ref): Check decl_constant_var_p and
> > cxx_constexpr_in_eval_lifetime_p instead of
> > is_constant_expression. Use cxx_eval_constant_expression
> > instead of DECL_INITIAL for references. Adjust the checking
> > assert.
> >
> > gcc/testsuite/ChangeLog:
> >
> > * g++.dg/reflect/extract2.C: Adjust dg-error.
> > * g++.dg/reflect/extract3.C: Add dg-error. Add tests using
> > object_of.
> > * g++.dg/reflect/extract13.C: New test.
> > * g++.dg/reflect/extract14.C: New test.
> > ---
> > gcc/cp/constexpr.cc | 20 +++++++-
> > gcc/cp/cp-tree.h | 1 +
> > gcc/cp/reflect.cc | 15 ++++--
> > gcc/testsuite/g++.dg/reflect/extract13.C | 59 ++++++++++++++++++++++++
> > gcc/testsuite/g++.dg/reflect/extract14.C | 23 +++++++++
> > gcc/testsuite/g++.dg/reflect/extract2.C | 2 +-
> > gcc/testsuite/g++.dg/reflect/extract3.C | 21 ++++++---
> > 7 files changed, 129 insertions(+), 12 deletions(-)
> > create mode 100644 gcc/testsuite/g++.dg/reflect/extract13.C
> > create mode 100644 gcc/testsuite/g++.dg/reflect/extract14.C
> >
> > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > index 31ef5bf1111..cf31c803be6 100644
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -1247,7 +1247,16 @@ public:
> > return true;
> > return false;
> > }
> > - tree get_value (tree t)
> > + /* This differs from !is_outside_lifetime in that it returns false if
> > + T is not in the map. */
>
> The names are too similar for this subtle difference. Why not
> _eval_lifetime_ like the other function?
Renamed.
> > + bool within_lifetime_p (tree t)
> > + {
> > + if (tree *p = values.get (t))
> > + if (*p != void_node && *p != void_list_node)
> > + return true;
> > + return false;
> > + }
> > + tree get_value (tree t)
> > {
> > if (tree *p = values.get (t))
> > if (*p != void_node && *p != void_list_node)
> > @@ -1404,6 +1413,15 @@ cxx_constexpr_consteval_block (const constexpr_ctx
> > *ctx)
> > return ctx->global->consteval_block;
> > }
> > +/* Return true if T's lifetime began within the core constant expression
> > + currently under evaluation. */
> > +
> > +bool
> > +cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *ctx, tree t)
> > +{
> > + return ctx->global->within_lifetime_p (t);
> > +}
> > +
> > /* Predicates for the meaning of *jump_target. */
> > static bool
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 947bf62ff98..83a287471a5 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -9486,6 +9486,7 @@ extern bool cxx_constexpr_quiet_p (const
> > constexpr_ctx *);
> > extern mce_value cxx_constexpr_manifestly_const_eval (const constexpr_ctx
> > *);
> > extern tree cxx_constexpr_caller (const constexpr_ctx *);
> > extern tree cxx_constexpr_consteval_block (const constexpr_ctx *);
> > +extern bool cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *,
> > tree);
> > /* An RAII sentinel used to restrict constexpr evaluation so that it
> > doesn't do anything that causes extra DECL_UID generation. */
> > diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
> > index 8bf518df64f..8a4c5490bbf 100644
> > --- a/gcc/cp/reflect.cc
> > +++ b/gcc/cp/reflect.cc
> > @@ -7686,18 +7686,27 @@ extract_ref (location_t loc, const constexpr_ctx
> > *ctx, tree T, tree r,
> > if (adju != error_mark_node
> > && adjt != error_mark_node
> > && is_convertible (adju, adjt)
> > - && (!var_p || is_constant_expression (r)))
> > + && (!var_p
> > + || decl_constant_var_p (r)
> > + || cxx_constexpr_in_eval_lifetime_p (ctx, r)))
> > {
> > if (TYPE_REF_P (TREE_TYPE (r)))
> > {
> > - r = DECL_INITIAL (r);
> > + bool nc = false, ovf = false;
> > + tree jt = NULL_TREE;
> > + r = cxx_eval_constant_expression (ctx, r, vc_prvalue,
>
> vc_prvalue seems wrong if we're trying to get a reference to r; there's no
> need to fetch its value. Specifically, it should be OK if r has erroneous
> value as long as it's within lifetime.
Ah, copypaste from eval_object_of. Even vc_prvalue worked: it would produce
(int &) &x and then maybe_get_reference_referent would get the x. But I've
changed it to vc_glvalue and then cxx_eval_constant_expression gives us x,
and I don't need the maybe_ call. Which is nice.
I've added a test for "int u;" in a consteval function.
> > + &nc, &ovf, &jt);
> > + if (nc || jt)
> > + goto throw_;
> > r = maybe_get_reference_referent (r);
> > - gcc_checking_assert (!TYPE_REF_P (TREE_TYPE (r)));
> > + gcc_checking_assert (r && !TYPE_P (r)
> > + && !TYPE_REF_P (TREE_TYPE (r)));
> > }
> > return build_address (r);
>
> Incidentally, how is it OK that this returns a pointer rather than a
> reference?
I think it's wrong. I never noticed that. I suppose we can build up
a NOP_EXPR around the build_address. Where that makes a difference is
template <const int &> struct X { };
same_type<X<g>, X<extract<const int &> (object_of (^^g))>> s1;
which now compiles. Thanks,
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
-- >8 --
A couple of problems here:
[meta.reflection.extract]/5.3 says "If r represents a variable, then
either that variable is usable in constant expressions or its lifetime
began within the core constant expression currently under evaluation."
but I was wrongly using is_constant_expression. That led to a crash
because we passed a type to build_address. For "is usable..." we want
decl_constant_var_p and for the "its lifetime began..." I'm adding a new
function. Using cxx_eval_constant_expression for non-reference
variables wouldn't work since evaluating a variable as a glvalue always
succeeds, and as a prvalue fails for e.g. an uninitialized variable
whose lifetime did begin.
With this patch, a variable that isn't usable in constant expressions
throws. Thus the extract2.C and extract3.C changes; one can use
object_of to get the object instead.
Also, DECL_INITIAL of a PARM_DECL is not an initializer but DECL_ARG_TYPE.
So we need cxx_eval_constant_expression to handle e.g.:
int g;
consteval void h (const int &p)
{
std::meta::extract<const int &> (^^p);
}
consteval { h (g); }
where we need to get g from the PARM_DECL p, and return "(const int &) &g".
With cxx_eval_constant_expression with vc_glvalue, the
maybe_get_reference_referent call is no longer necessary.
The result should be wrapped in a NOP_EXPR otherwise e.g.
template <const int &> struct X { };
same_type<X<g>, X<extract<const int &> (object_of (^^g))>> s1;
fails: we returned a pointer rather than a value of reference type T.
This adds a new XFAIL because we don't have P2686 yet.
It seems that object_of will need similar tweaks but that's for another
patch.
PR c++/127232
gcc/cp/ChangeLog:
* constexpr.cc (constexpr_global_ctx::in_eval_lifetime_p): New.
(cxx_constexpr_in_eval_lifetime_p): New.
* cp-tree.h (cxx_constexpr_in_eval_lifetime_p): Declare.
* reflect.cc (extract_ref): Check decl_constant_var_p and
cxx_constexpr_in_eval_lifetime_p instead of
is_constant_expression. Use cxx_eval_constant_expression
instead of DECL_INITIAL for references. Adjust the checking
assert. Drop the maybe_get_reference_referent call. Build
a NOP_EXPR around the result.
gcc/testsuite/ChangeLog:
* g++.dg/reflect/extract2.C: Adjust dg-error.
* g++.dg/reflect/extract3.C: Add dg-error. Add tests using
object_of.
* g++.dg/reflect/extract13.C: New test.
* g++.dg/reflect/extract14.C: New test.
---
gcc/cp/constexpr.cc | 20 ++++++-
gcc/cp/cp-tree.h | 1 +
gcc/cp/reflect.cc | 17 ++++--
gcc/testsuite/g++.dg/reflect/extract13.C | 75 ++++++++++++++++++++++++
gcc/testsuite/g++.dg/reflect/extract14.C | 22 +++++++
gcc/testsuite/g++.dg/reflect/extract2.C | 2 +-
gcc/testsuite/g++.dg/reflect/extract3.C | 21 ++++---
7 files changed, 144 insertions(+), 14 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/reflect/extract13.C
create mode 100644 gcc/testsuite/g++.dg/reflect/extract14.C
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 31ef5bf1111..953f1d038ec 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -1247,7 +1247,16 @@ public:
return true;
return false;
}
- tree get_value (tree t)
+ /* This differs from !is_outside_lifetime in that it returns false if
+ T is not in the map. */
+ bool in_eval_lifetime_p (tree t)
+ {
+ if (tree *p = values.get (t))
+ if (*p != void_node && *p != void_list_node)
+ return true;
+ return false;
+ }
+ tree get_value (tree t)
{
if (tree *p = values.get (t))
if (*p != void_node && *p != void_list_node)
@@ -1404,6 +1413,15 @@ cxx_constexpr_consteval_block (const constexpr_ctx *ctx)
return ctx->global->consteval_block;
}
+/* Return true if T's lifetime began within the core constant expression
+ currently under evaluation. */
+
+bool
+cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *ctx, tree t)
+{
+ return ctx->global->in_eval_lifetime_p (t);
+}
+
/* Predicates for the meaning of *jump_target. */
static bool
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 947bf62ff98..83a287471a5 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -9486,6 +9486,7 @@ extern bool cxx_constexpr_quiet_p (const
constexpr_ctx *);
extern mce_value cxx_constexpr_manifestly_const_eval (const constexpr_ctx *);
extern tree cxx_constexpr_caller (const constexpr_ctx *);
extern tree cxx_constexpr_consteval_block (const constexpr_ctx *);
+extern bool cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *, tree);
/* An RAII sentinel used to restrict constexpr evaluation so that it
doesn't do anything that causes extra DECL_UID generation. */
diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
index 8bf518df64f..97f571c5554 100644
--- a/gcc/cp/reflect.cc
+++ b/gcc/cp/reflect.cc
@@ -7686,18 +7686,25 @@ extract_ref (location_t loc, const constexpr_ctx *ctx,
tree T, tree r,
if (adju != error_mark_node
&& adjt != error_mark_node
&& is_convertible (adju, adjt)
- && (!var_p || is_constant_expression (r)))
+ && (!var_p
+ || decl_constant_var_p (r)
+ || cxx_constexpr_in_eval_lifetime_p (ctx, r)))
{
if (TYPE_REF_P (TREE_TYPE (r)))
{
- r = DECL_INITIAL (r);
- r = maybe_get_reference_referent (r);
- gcc_checking_assert (!TYPE_REF_P (TREE_TYPE (r)));
+ bool nc = false, ovf = false;
+ tree jt = NULL_TREE;
+ r = cxx_eval_constant_expression (ctx, convert_from_reference (r),
+ vc_glvalue, &nc, &ovf, &jt);
+ if (nc || jt)
+ goto throw_;
+ gcc_checking_assert (!TYPE_P (r));
}
- return build_address (r);
+ return build_nop (T, build_address (r));
}
}
+throw_:
return throw_exception (loc, ctx, "value cannot be extracted", fun,
non_constant_p, jump_target);
}
diff --git a/gcc/testsuite/g++.dg/reflect/extract13.C
b/gcc/testsuite/g++.dg/reflect/extract13.C
new file mode 100644
index 00000000000..a771a6d3241
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/extract13.C
@@ -0,0 +1,75 @@
+// PR c++/127232
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+template<typename, typename> struct same_type;
+template<typename T> struct same_type<T, T> { };
+
+void
+foo (int const& x)
+{
+ constexpr std::meta::info y = ^^x;
+ constexpr auto e = extract<int const&>(y); // { dg-error "value cannot be
extracted" }
+}
+
+consteval bool
+throws_p (std::meta::info r)
+{
+ try { extract<const int &> (r); }
+ catch (std::meta::exception &) { return true; }
+ return false;
+}
+
+int g;
+constexpr const int &cr = g;
+
+void
+f (const int &x, int y)
+{
+ static_assert (throws_p (^^x));
+ static_assert (throws_p (^^y));
+ const int &lx = x;
+ static_assert (throws_p (^^lx));
+ int &lg = g;
+ static_assert (!throws_p (^^lg));
+ static_assert (throws_p (^^g));
+ int a = 42;
+ int &la = a;
+ static_assert (!throws_p (^^la)); // { dg-bogus "" "P2686" { xfail *-*-* } }
+}
+
+consteval bool
+lifetime ()
+{
+ int i = 0;
+ int &ri = i;
+ return &extract<int &> (^^ri) == &i;
+}
+static_assert (lifetime ());
+
+consteval void
+h (const int &p)
+{
+ int i = 42;
+ const int &ri = i;
+ extract<const int &> (^^i);
+ extract<const int &> (^^ri);
+ extract<const int &> (^^p);
+}
+
+consteval { h (g); }
+
+consteval bool
+erroneous ()
+{
+ int u;
+ int &ru = u;
+ return &extract<int &> (^^u) == &u && &extract<int &> (^^ru) == &u;
+}
+static_assert (erroneous ());
+
+template <const int &> struct X { };
+same_type<X<g>, X<extract<const int &> (object_of (^^g))>> s1;
+same_type<X<g>, X<extract<int &> (object_of (^^g))>> s2;
diff --git a/gcc/testsuite/g++.dg/reflect/extract14.C
b/gcc/testsuite/g++.dg/reflect/extract14.C
new file mode 100644
index 00000000000..614065117e9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/extract14.C
@@ -0,0 +1,22 @@
+// PR c++/127232
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+// Check that reflections of locals/parameters refer to the current frame in
+// recursive calls.
+consteval bool
+foo (int n, int *outer)
+{
+ int i = n;
+ if (&extract<int &> (^^i) != &i || &extract<int &> (^^n) != &n)
+ return false;
+ if (outer && &extract<int &> (^^i) == outer)
+ return false;
+ int &ri = i;
+ if (&extract<int &> (^^ri) != &i)
+ return false;
+ return n == 0 || foo (n - 1, &i);
+}
+static_assert (foo (2, nullptr));
diff --git a/gcc/testsuite/g++.dg/reflect/extract2.C
b/gcc/testsuite/g++.dg/reflect/extract2.C
index 9de23d1e931..2d5f758ea96 100644
--- a/gcc/testsuite/g++.dg/reflect/extract2.C
+++ b/gcc/testsuite/g++.dg/reflect/extract2.C
@@ -93,7 +93,7 @@ static_assert (!can_extract<const int &>(reflect_constant
(i)));
static_assert (!can_extract<int &>(^^i));
int j;
-static_assert (extract<int &>(^^j)); // { dg-error "non-constant|not usable" }
+constexpr auto e = extract<int &>(^^j); // { dg-error "cannot be extracted" }
consteval info
bar ()
diff --git a/gcc/testsuite/g++.dg/reflect/extract3.C
b/gcc/testsuite/g++.dg/reflect/extract3.C
index f8c173c0208..42e06fac342 100644
--- a/gcc/testsuite/g++.dg/reflect/extract3.C
+++ b/gcc/testsuite/g++.dg/reflect/extract3.C
@@ -33,12 +33,16 @@ static_assert (extract<const int &>(^^rj) == j);
static_assert (&extract<const int &>(^^rj) == &j);
int k = 1;
-static_assert (&extract<int &>(^^k) == &k);
-static_assert (&extract<const int &>(^^k) == &k);
+constexpr auto e1 = extract<int &>(^^k); // { dg-error "cannot be
extracted" }
+constexpr auto e2 = extract<const int &>(^^k); // { dg-error "cannot be
extracted" }
+static_assert (&extract<int &> (object_of (^^k)) == &k);
+static_assert (&extract<const int &> (object_of (^^k)) == &k);
int arr[5] {};
-static_assert (&extract<int (&)[5]>(^^arr) == &arr);
-static_assert (&extract<const int (&)[5]>(^^arr) == &arr);
+constexpr auto e3 = extract<int (&)[5]>(^^arr); // { dg-error
"cannot be extracted" }
+constexpr auto e4 = extract<const int (&)[5]>(^^arr); // { dg-error "cannot be
extracted" }
+static_assert (&extract<int (&)[5]>(object_of (^^arr)) == &arr);
+static_assert (&extract<const int (&)[5]>(object_of (^^arr)) == &arr);
struct S { int m; };
constexpr S s{42};
@@ -54,9 +58,12 @@ same_type<decltype(extract<const S &>(reflect_constant
(S{42}))), const S &> st7
same_type<decltype(&extract<const S &>(reflect_constant (S{42}))), const S *>
st8;
S sr{42};
-static_assert (&extract<S&>(^^sr) == &sr);
-static_assert (&extract<const S&>(^^sr) == &sr);
-static_assert (&extract<S&>(^^sr).m == &sr.m);
+constexpr auto e5 = extract<S&>(^^sr); // { dg-error "cannot be
extracted" }
+constexpr auto e6 = extract<const S&>(^^sr); // { dg-error "cannot be
extracted" }
+constexpr auto e7 = extract<S&>(^^sr).m; // { dg-error "cannot be
extracted" }
+static_assert (&extract<S&>(object_of (^^sr)) == &sr);
+static_assert (&extract<const S&>(object_of (^^sr)) == &sr);
+static_assert (&extract<S&>(object_of (^^sr)).m == &sr.m);
int &srm = sr.m;
static_assert (&extract<int&>(^^srm) == &sr.m);
base-commit: f9b60a8bd7569f59534a42d1383c1f66a37542cf
--
2.55.0