On Tue, May 19, 2026 at 12:34:26PM -0400, Jason Merrill wrote:
> On 5/19/26 11:26 AM, Patrick Palka wrote:
> > On Tue, 19 May 2026, Marek Polacek wrote:
> >
> > > On Tue, May 19, 2026 at 08:30:50AM -0400, Patrick Palka wrote:
> > > > On Mon, 18 May 2026, Marek Polacek wrote:
> > > >
> > > > > On Mon, May 18, 2026 at 09:34:23AM -0400, Patrick Palka wrote:
> > > > > > On Fri, 15 May 2026, Marek Polacek wrote:
> > > > > >
> > > > > > > On Thu, May 14, 2026 at 09:50:52PM -0400, Patrick Palka wrote:
> > > > > > > > On Thu, 14 May 2026, Marek Polacek wrote:
> > > > > > > >
> > > > > > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for
> > > > > > > > > trunk/16.2?
> > > > > > > > >
> > > > > > > > > -- >8 --
> > > > > > > > > Thanks to DR 696 (r253266), this works:
> > > > > > > > >
> > > > > > > > > int g;
> > > > > > > > > void fn ()
> > > > > > > > > {
> > > > > > > > > int &c = g;
> > > > > > > > > auto l = [] { c++; };
> > > > > > > > > l();
> > > > > > > > > }
> > > > > > > > >
> > > > > > > > > because `c` in the lambda body is not an odr-use because we
> > > > > > > > > can
> > > > > > > > > evaluate it to a constant and so there's no capture. But when
> > > > > > > > > fn is a template, we reject the code and crash. This patch
> > > > > > > > > fixes
> > > > > > > > > both.
> > > > > > > > >
> > > > > > > > > Outside a template, the call to maybe_constant_value in
> > > > > > > > > mark_use
> > > > > > > > > evaluates `c` to `(int&) &g` but in a template, it remains
> > > > > > > > > `c`.
> > > > > > > > > Then we emit an error, and crash on the error_mark_node from
> > > > > > > > > process_outer_var_ref. One of the reasons is
> > > > > > > > > else if (TYPE_REF_P (TREE_TYPE (expression)))
> > > > > > > > > /* FIXME cp_finish_decl doesn't fold reference
> > > > > > > > > initializers. */
> > > > > > > > > return true;
> > > > > > > > > in value_dependent_expression_p but even if that changed, we
> > > > > > > > > still
> > > > > > > > > wouldn't get the referent because decl_really_constant_value
> > > > > > > > > wouldn't
> > > > > > > > > give it to us; the DECL_INITIAL is not a TREE_CONSTANT yet.
> > > > > > > > >
> > > > > > > > > So I stopped trying to make this work in a template, and
> > > > > > > > > instead
> > > > > > > > > I'm delaying the processing to instantiating when we know that
> > > > > > > > > maybe_constant_value wouldn't even try to evaluate.
> > > > > > > >
> > > > > > > > It seems with this patch we still incorrectly reject the case
> > > > > > > > where the outer
> > > > > > > > local variable is initially type-dependent (not a regression):
> > > > > > > >
> > > > > > > > int g;
> > > > > > > >
> > > > > > > > template<class T>
> > > > > > > > void
> > > > > > > > fn1 ()
> > > > > > > > {
> > > > > > > > T c = g;
> > > > > > > > auto l = [] { c++; }; // bogus error: 'c' is not captured
> > > > > > > > l();
> > > > > > > > }
> > > > > > > >
> > > > > > > > void
> > > > > > > > bar ()
> > > > > > > > {
> > > > > > > > fn1<int&> ();
> > > > > > > > }
> > > > > > > >
> > > > > > > > via process_outer_var_ref, this time called from
> > > > > > > > finish_id_expression_1.
> > > > > > > > Maybe we can make both testcases work if we instead give
> > > > > > > > process_outer_var_ref
> > > > > > > > an early exit for when the outer variable is from a template?
> > > > > > >
> > > > > > > Thanks for that testcase; I hadn't considered it.
> > > > > > >
> > > > > > > What do y'all think about this approach, then? I think we want
> > > > > > > the new
> > > > > > > check only after checking !odr_use && decl_constant_var_p.
> > > > > > >
> > > > > > > dg.exp passed thus far.
> > > > > > >
> > > > > > > -- >8 --
> > > > > > > Thanks to DR 696 (r253266), this works:
> > > > > > >
> > > > > > > int g;
> > > > > > > void fn ()
> > > > > > > {
> > > > > > > int &c = g;
> > > > > > > auto l = [] { c++; };
> > > > > > > l();
> > > > > > > }
> > > > > > >
> > > > > > > because `c` in the lambda body is not an odr-use because we can
> > > > > > > evaluate it to a constant and so there's no capture. But when
> > > > > > > fn is a template, we reject the code and crash. This patch fixes
> > > > > > > both.
> > > > > > >
> > > > > > > Outside a template, the call to maybe_constant_value in mark_use
> > > > > > > evaluates `c` to `(int&) &g` but in a template, it remains `c`.
> > > > > > > Then we emit an error, and crash on the error_mark_node from
> > > > > > > process_outer_var_ref. One of the reasons is
> > > > > > > else if (TYPE_REF_P (TREE_TYPE (expression)))
> > > > > > > /* FIXME cp_finish_decl doesn't fold reference
> > > > > > > initializers. */
> > > > > > > return true;
> > > > > > > in value_dependent_expression_p but even if that changed, we still
> > > > > > > wouldn't get the referent because decl_really_constant_value
> > > > > > > wouldn't
> > > > > > > give it to us; the DECL_INITIAL is not a TREE_CONSTANT yet.
> > > > > > >
> > > > > > > So I stopped trying to make this work in a template, and instead
> > > > > > > I'm deferring the error in process_outer_var_ref to instantiation
> > > > > > > when it's instantiation-dependent.
> > > > > > >
> > > > > > > PR c++/123536
> > > > > > >
> > > > > > > gcc/cp/ChangeLog:
> > > > > > >
> > > > > > > * semantics.cc (process_outer_var_ref): Return decl when it is
> > > > > > > instantiation-dependent.
> > > > > > >
> > > > > > > gcc/testsuite/ChangeLog:
> > > > > > >
> > > > > > > * g++.dg/cpp0x/lambda/lambda-const12.C: New test.
> > > > > > > * g++.dg/cpp0x/lambda/lambda-const13.C: New test.
> > > > > > > ---
> > > > > > > gcc/cp/semantics.cc | 5 ++
> > > > > > > .../g++.dg/cpp0x/lambda/lambda-const12.C | 48
> > > > > > > +++++++++++++++++++
> > > > > > > .../g++.dg/cpp0x/lambda/lambda-const13.C | 19 ++++++++
> > > > > > > 3 files changed, 72 insertions(+)
> > > > > > > create mode 100644
> > > > > > > gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C
> > > > > > > create mode 100644
> > > > > > > gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C
> > > > > > >
> > > > > > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > > > > > > index 6564d9e37a6..201519ef411 100644
> > > > > > > --- a/gcc/cp/semantics.cc
> > > > > > > +++ b/gcc/cp/semantics.cc
> > > > > > > @@ -4699,6 +4699,11 @@ process_outer_var_ref (tree decl,
> > > > > > > tsubst_flags_t complain, bool odr_use)
> > > > > > > return var;
> > > > > > > else if (lambda_expr)
> > > > > > > {
> > > > > > > + /* Don't complain when DECL is dependent, because it can
> > > > > > > turn out to
> > > > > > > + be constant (and therefore needing no capture) when
> > > > > > > instantiating. */
> > > > > > > + if (instantiation_dependent_expression_p (decl))
> > > > > > > + return decl;
> > > > > > > +
> > > > > >
> > > > > > Presumably we also want to accept the nested class version of
> > > > > > lambda-const13.C:
> > > > > >
> > > > > > int g;
> > > > > >
> > > > > > template<class T>
> > > > > > void
> > > > > > fn1 ()
> > > > > > {
> > > > > > T c = g;
> > > > > > struct A { static void f() { c++; } };
> > > > > > A::f();
> > > > > > }
> > > > > >
> > > > > > void
> > > > > > bar ()
> > > > > > {
> > > > > > fn1<int&> ();
> > > > > > }
> > > > > >
> > > > > >
> > > > > > So the early exit should be independent of whether we're inside a
> > > > > > lambda.
> > > > >
> > > > > Oop, yes. I moved the check up and also added VAR_P because
> > > > > I *think* for PARM_DECLs we don't need/want that i_d_e_p check.
> > > > > Without it pr57416.C regresses its diagnostic a bit.
> > > > > > Does instantiation_dependent_expression_p work for:
> > > > > >
> > > > > > template<class T>
> > > > > > void
> > > > > > fn2 ()
> > > > > > {
> > > > > > int& c = T::static_data_member; // not marked constant at
> > > > > > parse time
> > > > > > auto l = [] { c++; };
> > > > > > l();
> > > > > > }
> > > > >
> > > > > It does seem to work. I discovered a crash on invalid when trying
> > > > > out this test, thus the mark_use hunk. Tested in lambda-const14.C.
> > > > >
> > > > > > Maybe the early exit test should just be whether the context of the
> > > > > > outer variable is dependent.
> > > > >
> > > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk/16.2?
> > > > >
> > > > > -- >8 --
> > > > > Thanks to DR 696 (r253266), this works:
> > > > >
> > > > > int g;
> > > > > void fn ()
> > > > > {
> > > > > int &c = g;
> > > > > auto l = [] { c++; };
> > > > > l();
> > > > > }
> > > > >
> > > > > because `c` in the lambda body is not an odr-use because we can
> > > > > evaluate it to a constant and so there's no capture. But when
> > > > > fn is a template, we reject the code and crash. This patch fixes
> > > > > both.
> > > > >
> > > > > Outside a template, the call to maybe_constant_value in mark_use
> > > > > evaluates `c` to `(int&) &g` but in a template, it remains `c`.
> > > > > Then we emit an error, and crash on the error_mark_node from
> > > > > process_outer_var_ref. One of the reasons is
> > > > > else if (TYPE_REF_P (TREE_TYPE (expression)))
> > > > > /* FIXME cp_finish_decl doesn't fold reference initializers.
> > > > > */
> > > > > return true;
> > > > > in value_dependent_expression_p but even if that changed, we still
> > > > > wouldn't get the referent because decl_really_constant_value wouldn't
> > > > > give it to us; the DECL_INITIAL is not a TREE_CONSTANT yet.
> > > > >
> > > > > So I stopped trying to make this work in a template, and instead
> > > > > I'm deferring the error in process_outer_var_ref to instantiation
> > > > > when it's instantiation-dependent. The VAR_P check there is not
> > > > > to regress the diagnostic in pr57416.C.
> > > > >
> > > > > The mark_use hunk is to fix a crash on invalid (lambda-const14.C).
> > > > >
> > > > > PR c++/123536
> > > > >
> > > > > gcc/cp/ChangeLog:
> > > > >
> > > > > * expr.cc (mark_use): Return if mark_rvalue_use returns
> > > > > error_mark_node.
> > > > > * semantics.cc (process_outer_var_ref): Return decl when it is
> > > > > instantiation-dependent.
> > > > >
> > > > > gcc/testsuite/ChangeLog:
> > > > >
> > > > > * g++.dg/cpp0x/lambda/lambda-const12.C: New test.
> > > > > * g++.dg/cpp0x/lambda/lambda-const13.C: New test.
> > > > > * g++.dg/cpp0x/lambda/lambda-const14.C: New test.
> > > > > * g++.dg/template/local11.C: New test.
> > > > > ---
> > > > > gcc/cp/expr.cc | 2 +
> > > > > gcc/cp/semantics.cc | 6 +-
> > > > > .../g++.dg/cpp0x/lambda/lambda-const12.C | 62
> > > > > +++++++++++++++++++
> > > > > .../g++.dg/cpp0x/lambda/lambda-const13.C | 19 ++++++
> > > > > .../g++.dg/cpp0x/lambda/lambda-const14.C | 16 +++++
> > > > > gcc/testsuite/g++.dg/template/local11.C | 18 ++++++
> > > > > 6 files changed, 122 insertions(+), 1 deletion(-)
> > > > > create mode 100644
> > > > > gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C
> > > > > create mode 100644
> > > > > gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C
> > > > > create mode 100644
> > > > > gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C
> > > > > create mode 100644 gcc/testsuite/g++.dg/template/local11.C
> > > > >
> > > > > diff --git a/gcc/cp/expr.cc b/gcc/cp/expr.cc
> > > > > index 4d017d530ef..516cea084a6 100644
> > > > > --- a/gcc/cp/expr.cc
> > > > > +++ b/gcc/cp/expr.cc
> > > > > @@ -182,6 +182,8 @@ mark_use (tree expr, bool rvalue_p, bool read_p,
> > > > > }
> > > > > }
> > > > > tree r = mark_rvalue_use (ref, loc, reject_builtin);
> > > > > + if (r == error_mark_node)
> > > > > + return error_mark_node;
> > > > > if (r != ref)
> > > > > {
> > > > > if (!rvalue_p)
> > > > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > > > > index 6564d9e37a6..5e64a220e4f 100644
> > > > > --- a/gcc/cp/semantics.cc
> > > > > +++ b/gcc/cp/semantics.cc
> > > > > @@ -4697,6 +4697,10 @@ process_outer_var_ref (tree decl,
> > > > > tsubst_flags_t complain, bool odr_use)
> > > > > constant without odr-use. So don't complain yet. */
> > > > > else if (!odr_use && decl_constant_var_p (var))
> > > > > return var;
> > > > > + /* Don't complain when DECL is dependent, because it can turn out
> > > > > to
> > > > > + be constant (and therefore needing no capture) when
> > > > > instantiating. */
> > > > > + else if (VAR_P (decl) && instantiation_dependent_expression_p
> > > > > (decl))
> > > > > + return decl;
> > > >
> > > > Think we need to look through capture proxies here by using 'var'
> > > > instead of 'decl' here for sake of:
> > > >
> > > > struct A { static int x; };
> > > >
> > > > template<class T>
> > > > void
> > > > fn1 ()
> > > > {
> > > > int& c = T::x;
> > > > [&c] {
> > > > [] { c++; }();
> > > > };
> > > > }
> > > >
> > > > void
> > > > bar ()
> > > > {
> > > > fn1<A> ();
> > > > }
> > >
> > > Fixed. I used var and then changed it back to decl, sigh.
> > > Thanks for catching that.
> > >
> > > Testing this, dg.exp passed.
> > >
> > > -- >8 --
> > > Thanks to DR 696 (r253266), this works:
> > >
> > > int g;
> > > void fn ()
> > > {
> > > int &c = g;
> > > auto l = [] { c++; };
> > > l();
> > > }
> > >
> > > because `c` in the lambda body is not an odr-use because we can
> > > evaluate it to a constant and so there's no capture. But when
> > > fn is a template, we reject the code and crash. This patch fixes
> > > both.
> > >
> > > Outside a template, the call to maybe_constant_value in mark_use
> > > evaluates `c` to `(int&) &g` but in a template, it remains `c`.
> > > Then we emit an error, and crash on the error_mark_node from
> > > process_outer_var_ref. One of the reasons is
> > > else if (TYPE_REF_P (TREE_TYPE (expression)))
> > > /* FIXME cp_finish_decl doesn't fold reference initializers. */
> > > return true;
> > > in value_dependent_expression_p but even if that changed, we still
> > > wouldn't get the referent because decl_really_constant_value wouldn't
> > > give it to us; the DECL_INITIAL is not a TREE_CONSTANT yet.
> > >
> > > So I stopped trying to make this work in a template, and instead
> > > I'm deferring the error in process_outer_var_ref to instantiation
> > > when it's instantiation-dependent. The VAR_P check there is not
> > > to regress the diagnostic in pr57416.C.
> > >
> > > The mark_use hunk is to fix a crash on invalid (lambda-const14.C).
> > >
> > > PR c++/123536
> > >
> > > gcc/cp/ChangeLog:
> > >
> > > * expr.cc (mark_use): Return if mark_rvalue_use returns
> > > error_mark_node.
> > > * semantics.cc (process_outer_var_ref): Return decl when it is
> > > instantiation-dependent.
> > >
> > > gcc/testsuite/ChangeLog:
> > >
> > > * g++.dg/cpp0x/lambda/lambda-const12.C: New test.
> > > * g++.dg/cpp0x/lambda/lambda-const13.C: New test.
> > > * g++.dg/cpp0x/lambda/lambda-const14.C: New test.
> > > * g++.dg/template/local11.C: New test.
> >
> > LGTM but I defer to Jason.
> >
> > > ---
> > > gcc/cp/expr.cc | 2 +
> > > gcc/cp/semantics.cc | 6 +-
> > > .../g++.dg/cpp0x/lambda/lambda-const12.C | 73 +++++++++++++++++++
> > > .../g++.dg/cpp0x/lambda/lambda-const13.C | 19 +++++
> > > .../g++.dg/cpp0x/lambda/lambda-const14.C | 16 ++++
> > > gcc/testsuite/g++.dg/template/local11.C | 18 +++++
> > > 6 files changed, 133 insertions(+), 1 deletion(-)
> > > create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C
> > > create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C
> > > create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C
> > > create mode 100644 gcc/testsuite/g++.dg/template/local11.C
> > >
> > > diff --git a/gcc/cp/expr.cc b/gcc/cp/expr.cc
> > > index 4d017d530ef..516cea084a6 100644
> > > --- a/gcc/cp/expr.cc
> > > +++ b/gcc/cp/expr.cc
> > > @@ -182,6 +182,8 @@ mark_use (tree expr, bool rvalue_p, bool read_p,
> > > }
> > > }
> > > tree r = mark_rvalue_use (ref, loc, reject_builtin);
> > > + if (r == error_mark_node)
> > > + return error_mark_node;
> > > if (r != ref)
> > > {
> > > if (!rvalue_p)
> > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > > index 490441dba6c..6dd46b14359 100644
> > > --- a/gcc/cp/semantics.cc
> > > +++ b/gcc/cp/semantics.cc
> > > @@ -4697,6 +4697,10 @@ process_outer_var_ref (tree decl, tsubst_flags_t
> > > complain, bool odr_use)
> >
> > While we're modifying process_outer_var_ref could you also document
> > odr_use's default argument with /* = false */?
>
> Sounds good.
Done.
> > > constant without odr-use. So don't complain yet. */
> > > else if (!odr_use && decl_constant_var_p (var))
> > > return var;
> > > + /* Don't complain when DECL is dependent, because it can turn out to
> > > + be constant (and therefore needing no capture) when instantiating.
> > > */
> > > + else if (VAR_P (var) && instantiation_dependent_expression_p (var))
>
> I still wonder why instantiation_dependent_expression_p is the test here
> rather than value_dependent_expression_p, but I guess the effect is the
> same. And I suppose it would need to be (decl_maybe_constant_var_p &&
> value_dependent_expression_p). So never mind.
>
> > +template<int N = 1>
> > +void
> > +fn2 ()
> > +{
> > + const int &c = N;
> > + auto l = [] { int i = c; (void) i; }; // { dg-error ".c. is not
> > captured" }
> > + l();
> > +}
>
> I expect this to be OK when we finish implementing P2686, as then we can
> evaluate 'c' to a constant value, as with
>
> void f() {
> const int &r = 42;
> constexpr int i = r;
> }
>
> OK with Patrick's suggestion and a comment about P2686.
Pushed, thanks. I'll let it sit on trunk for a while before backporting.
> Speaking of which, is anyone thinking to work on P2686 for GCC 17?
I think I'll take a stab at it.
Marek