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> ();
    }

>    else if (lambda_expr)
>      {
>        if (complain & tf_error)
> @@ -4715,7 +4719,7 @@ process_outer_var_ref (tree decl, tsubst_flags_t 
> complain, bool odr_use)
>       }
>        return error_mark_node;
>      }
> -  else if (processing_contract_condition && (TREE_CODE (decl) == PARM_DECL))
> +  else if (processing_contract_condition && TREE_CODE (decl) == PARM_DECL)
>      /* Use of a parameter in a contract condition is fine.  */
>      return decl;
>    else
> 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..6dc69f64405
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const12.C
> @@ -0,0 +1,62 @@
> +// PR c++/123536
> +// { dg-do compile { target c++11 } }
> +
> +int g;
> +
> +struct S {
> +  static int static_data_member;
> +};
> +
> +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();
> +}
> +
> +template<class T>
> +void
> +fn5 ()
> +{
> +  int& c = T::static_data_member;
> +  auto l = [] { c++; };
> +  l();
> +}
> +
> +void
> +bar ()
> +{
> +  fn1 ();
> +  fn2 ();
> +  fn3 ();
> +  fn4 ();
> +  fn5<S>();
> +}
> 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&> ();
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C 
> b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C
> new file mode 100644
> index 00000000000..2299556a4e6
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const14.C
> @@ -0,0 +1,16 @@
> +// PR c++/123536
> +// { dg-do compile { target c++11 } }
> +
> +template<class T>
> +void
> +f ()
> +{
> +  int& c = T::x;     // { dg-error ".x. is not a member of .int." }
> +  auto l = [] { c++; };      // { dg-error ".c. is not captured" }
> +}
> +
> +void
> +g ()
> +{
> +  f<int>();
> +}
> diff --git a/gcc/testsuite/g++.dg/template/local11.C 
> b/gcc/testsuite/g++.dg/template/local11.C
> new file mode 100644
> index 00000000000..f75b748caa8
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/template/local11.C
> @@ -0,0 +1,18 @@
> +// PR c++/123536
> +
> +int g;
> +
> +template<class T>
> +void
> +fn1 ()
> +{
> +  T c = g;
> +  struct A { static void f() { c++; } };
> +  A::f();
> +}
> +
> +void
> +bar ()
> +{
> +  fn1<int&> ();
> +}
> 
> base-commit: 1af06a4bfbc04309f944ce50d2ce7a88f5dc150f
> -- 
> 2.54.0
> 
> 

Reply via email to