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.

       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.

Speaking of which, is anyone thinking to work on P2686 for GCC 17?

Jason

Reply via email to