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;
+
       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

Reply via email to