Hi Jason,

On Wed Mar 5, 2025 at 12:03 AM CET, Jason Merrill wrote:
> On 2/18/25 5:12 AM, Simon Martin wrote:
>> We've been rejecting this valid code since r8-4571:
>> 
>> === cut here ===
>> void foo (float);
>> int main () {
>>    constexpr float x = 0;
>>    (void) [&] () {
>>      foo (x);
>>      (void) [] () {
>>        foo (x);
>>      };
>>    };
>> }
>> === cut here ===
>> 
>> The problem is that when processing X in the inner lambda,
>> process_outer_var_ref errors out even though it does find the capture

>> from the enclosing lambda.
>> 
>> This patch changes process_outer_var_ref to accept and return the outer
>> proxy if it finds any.
>> 
>> Successfully tested on x86_64-pc-linux-gnu.
>> 
>>      PR c++/110584
>> 
>> gcc/cp/ChangeLog:
>> 
>>      * semantics.cc (process_outer_var_ref): Use capture from
>>      enclosing lambda, if any.
>> 
>> gcc/testsuite/ChangeLog:
>> 
>>      * g++.dg/cpp0x/lambda/lambda-nested10.C: New test.
>> 
>> ---
>>   gcc/cp/semantics.cc                           |  4 ++
>>   .../g++.dg/cpp0x/lambda/lambda-nested10.C     | 46 +++++++++++++++++++
>>   2 files changed, 50 insertions(+)
>>   create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested10.C
>> 
>> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
>> index 7c7d3e3c432..7bbc82f7dc1 100644
>> --- a/gcc/cp/semantics.cc
>> +++ b/gcc/cp/semantics.cc
>> @@ -4598,6 +4598,10 @@ process_outer_var_ref (tree decl, tsubst_flags_t 
>> complain, bool odr_use)
>>     if (!odr_use && context == containing_function)
>>       decl = add_default_capture (lambda_stack,
>>                              /*id=*/DECL_NAME (decl), initializer);
>> +  /* When doing lambda capture, if we found a capture in an enclosing 
>> lambda,
>> +     we can use it.  */
>> +  else if (!odr_use && is_capture_proxy (decl))
>> +    return decl;
>
> What makes this OK is not that it's a capture, but that the captured 
> variable is constant, so it should have satisfied this branch:
>
>>   /* Only an odr-use of an outer automatic variable causes an                
>>                                                         
>>      error, and a constant variable can decay to a prvalue                   
>>                                                         
>>      constant without odr-use.  So don't complain yet.  */
>>   else if (!odr_use && decl_constant_var_p (decl))
>>     return decl;
>
> is_constant_capture_proxy might be useful here.  And/or a new 
> strip_normal_capture function (that I'm surprised doesn't exist yet).
Yeah, that makes a lot of sense, thanks. This is what the attached updated
patch does, successfully tested on x86_64-pc-linux-gnu. OK for trunk?

Simon

From d9e83d2c9dc6fae4a15d47e694fbe689e8a413c1 Mon Sep 17 00:00:00 2001
From: Simon Martin <si...@nasilyan.com>
Date: Mon, 17 Feb 2025 18:53:22 +0100
Subject: [PATCH] c++: Look through capture proxy from outer lambda instead of
 erroring out [PR110584]

We've been rejecting this valid code since r8-4571:

=== cut here ===
void foo (float);
int main () {
  constexpr float x = 0;
  (void) [&] () {
    foo (x);
    (void) [] () {
      foo (x);
    };
  };
}
=== cut here ===

The problem is that when processing X in the inner lambda,
process_outer_var_ref errors out even though it does find the constant
capture from the enclosing lambda.

This patch makes sure that process_outer_var_ref properly looks through
normal capture proxies, if any.

Successfully tested on x86_64-pc-linux-gnu.

        PR c++/110584

gcc/cp/ChangeLog:

        * cp-tree.h (strip_normal_capture_proxy): Declare.
        * lambda.cc (strip_normal_capture_proxy): New function to look
        through normal capture proxies.
        (build_capture_proxy): Use it.
        * semantics.cc (process_outer_var_ref): Likewise.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp0x/lambda/lambda-nested10.C: New test.

---
 gcc/cp/cp-tree.h                              |  1 +
 gcc/cp/lambda.cc                              | 14 +++++-
 gcc/cp/semantics.cc                           |  6 +--
 .../g++.dg/cpp0x/lambda/lambda-nested10.C     | 46 +++++++++++++++++++
 4 files changed, 61 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested10.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 583d0496364..b1f1b21ade2 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8114,6 +8114,7 @@ extern void insert_capture_proxy          (tree);
 extern void insert_pending_capture_proxies     (void);
 extern bool is_capture_proxy                   (tree);
 extern bool is_normal_capture_proxy             (tree);
+extern tree strip_normal_capture_proxy         (tree);
 extern bool is_constant_capture_proxy           (tree);
 extern void register_capture_members           (tree);
 extern tree lambda_expr_this_capture            (tree, int);
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index da075b98805..ed70bb0ba8e 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -295,6 +295,17 @@ is_normal_capture_proxy (tree decl)
          && DECL_CAPTURED_VARIABLE (decl));
 }
 
+/* If DECL is a normal capture proxy, return the variable it captures.
+   Otherwise, just return DECL.  */
+
+tree
+strip_normal_capture_proxy (tree decl)
+{
+  while (is_normal_capture_proxy (decl))
+    decl = DECL_CAPTURED_VARIABLE (decl);
+  return decl;
+}
+
 /* Returns true iff DECL is a capture proxy for a normal capture
    of a constant variable.  */
 
@@ -469,8 +480,7 @@ build_capture_proxy (tree member, tree init)
       STRIP_NOPS (init);
 
       gcc_assert (VAR_P (init) || TREE_CODE (init) == PARM_DECL);
-      while (is_normal_capture_proxy (init))
-       init = DECL_CAPTURED_VARIABLE (init);
+      init = strip_normal_capture_proxy (init);
       retrofit_lang_decl (var);
       DECL_CAPTURED_VARIABLE (var) = init;
     }
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 7c7d3e3c432..1b7e7b35300 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4539,9 +4539,7 @@ process_outer_var_ref (tree decl, tsubst_flags_t 
complain, bool odr_use)
   if (containing_function && LAMBDA_FUNCTION_P (containing_function))
     {
       /* Check whether we've already built a proxy.  */
-      tree var = decl;
-      while (is_normal_capture_proxy (var))
-       var = DECL_CAPTURED_VARIABLE (var);
+      tree var = strip_normal_capture_proxy (decl);
       tree d = retrieve_local_specialization (var);
 
       if (d && d != decl && is_capture_proxy (d))
@@ -4601,7 +4599,7 @@ process_outer_var_ref (tree decl, tsubst_flags_t 
complain, bool odr_use)
   /* Only an odr-use of an outer automatic variable causes an
      error, and a constant variable can decay to a prvalue
      constant without odr-use.  So don't complain yet.  */
-  else if (!odr_use && decl_constant_var_p (decl))
+  else if (!odr_use && decl_constant_var_p (strip_normal_capture_proxy (decl)))
     return decl;
   else if (lambda_expr)
     {
diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested10.C 
b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested10.C
new file mode 100644
index 00000000000..2dd9dd4955e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-nested10.C
@@ -0,0 +1,46 @@
+// PR c++/110584
+// { dg-do "run" { target c++11 } }
+
+void foo (int i) {
+  if (i != 0)
+    __builtin_abort ();
+}
+
+int main () {
+  const int x = 0;
+
+  // We would error out on this.
+  (void) [&] () {
+    foo (x);
+    (void)[] () {
+      foo (x);
+    };
+  } ();
+  // As well as those.
+  (void) [&] () {
+    (void) [] () {
+      foo (x);
+    };
+  } ();
+  (void) [&x] () {
+    (void) [] () {
+      foo (x);
+    };
+  } ();
+  // But those would work already.
+  (void) [] () {
+    (void) [&] () {
+      foo (x);
+    };
+  } ();
+  (void) [&] () {
+    (void) [&] () {
+      foo (x);
+    };
+  } ();
+  (void) [=] () {
+    (void) [] () {
+      foo (x);
+    };
+  } ();
+}
-- 
2.44.0

Reply via email to