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.
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();
}
Maybe the early exit test should just be whether the context of the
outer variable is dependent.
> if (complain & tf_error)
> {
> auto_diagnostic_group d;
> diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C
> b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C
> new file mode 100644
> index 00000000000..eca53461c69
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C
> @@ -0,0 +1,48 @@
> +// PR c++/123536
> +// { dg-do compile { target c++11 } }
> +
> +int g;
> +
> +template<int = 1>
> +void
> +fn1 ()
> +{
> + int &c = g;
> + auto l = [] { c++; };
> + l();
> +}
> +
> +template<int N = 1>
> +void
> +fn2 ()
> +{
> + const int &c = N;
> + auto l = [] { int i = c; (void) i; }; // { dg-error ".c. is not captured" }
> + l();
> +}
> +
> +void
> +fn3 ()
> +{
> + int &c = g;
> + auto l = [] { c++; };
> + l();
> +}
> +
> +void
> +fn4 ()
> +{
> + int n = 42;
> + const int &c = n;
> + auto l = [] { int i = c; (void) i; }; // { dg-error ".c. is not captured" }
> + l();
> +}
> +
> +void
> +bar ()
> +{
> + fn1 ();
> + fn2 ();
> + fn3 ();
> + fn4 ();
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C
> b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C
> new file mode 100644
> index 00000000000..a6044c52e7e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const13.C
> @@ -0,0 +1,19 @@
> +// PR c++/123536
> +// { dg-do compile { target c++11 } }
> +
> +int g;
> +
> +template<class T>
> +void
> +fn1 ()
> +{
> + T c = g;
> + auto l = [] { c++; };
> + l();
> +}
> +
> +void
> +bar ()
> +{
> + fn1<int&> ();
> +}
>
> base-commit: 8c8f688fd8d936859dda5cd2eea88f0b22b6c189
> --
> 2.54.0
>
>