On 7/16/25 5:59 PM, Marek Polacek wrote:
On Mon, Jul 14, 2025 at 12:52:41PM -0400, Jason Merrill wrote:
On 7/11/25 5:49 PM, Marek Polacek wrote:
On Thu, Jul 10, 2025 at 02:13:06PM -0400, Jason Merrill wrote:
On 7/9/25 4:27 PM, Marek Polacek wrote:
On Tue, Jul 08, 2025 at 12:15:03PM -0400, Jason Merrill wrote:
On 7/7/25 4:52 PM, Marek Polacek wrote:
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch is an attempt to implement P2036R3 along with P2579R0, fixing
build breakages caused by P2036R3.

The simplest example is:

      auto counter1 = [j=0]() mutable -> decltype(j) {
          return j++;
      };

which currently doesn't compile because the 'j' in the capture isn't
visible in the trailing return type.  With these proposals, the 'j'
will be in a lambda scope which spans the trailing return type, so
this test will compile.

This oughtn't be difficult but decltype and other issues made this patch
much more challenging.

We have to push the explicit captures before going into _lambda_declarator_opt
because that is what parses the trailing return type.  Yet we can't build
any captures until after _lambda_body -> start_lambda_function which
creates the lambda's operator(), without which we can't build a proxy,
but _lambda_body happens only after parsing the declarator.  This patch
works around it by creating a fake operator() in make_dummy_lambda_op.

I was thinking that we could build the real operator() earlier, before the
trailing return type, so that it's there for the above uses, and then splice
in the trailing return type to the already-built function declaration,
perhaps with apply_deduced_return_type.

Ah, I see what you mean.  But it's not just the return type that we don't
have at the point where we have to have the operator(): it's also tx_qual,
exception_spec, std_attrs, and trailing_requires_clause.  Especially the
requires clause seems to be awkward to set post grokmethod; it seems I'd
have to replicate the flag_concepts block in grokfndecl?

Maybe I could add (by that I mean add it to the lambda via
finish_member_declaration) a bare bones operator() for the purposes of
parsing the return type/noexcept/requires, then after parsing them
construct a real operator(), then find a slot of the bare bones op(),
and replace it with the complete one.  I'm not sure if that makes sense
to do though.

I was hoping to avoid building more than one op().  But really, why do you
need an op() at all for building the proxies?  Could you use
build_dummy_object instead of DECL_ARGUMENTS of some fake op()?

The problem is that we need operator() to be the var's DECL_CONTEXT
for is_capture_proxy:

    && LAMBDA_FUNCTION_P (DECL_CONTEXT (decl)));

Maybe we could set their DECL_CONTEXT to the closure type and adjust
is_capture_proxy to handle that case as well?

Ah, now I recall why I had so much trouble when I tried that in GCC 15.
The problem is that the closure type is not complete at that point, and
that causes problems.  E.g., equal2 in lambda-scope3.C has

   return [t = t](const auto& obj) -> decltype(obj == t)

and the incomplete 't' in the decltype is problematic for cp_build_binary_op.

Hmm, problematic how?

In any case, I guess it's reasonable to build a dummy op(), but building a new one for each capture is not; they should all use the same op(). Adding it to the closure and then clobbering it later (perhaps by getting duplicate_decls to accept them as duplicates?) seems like a fine way to do that.

See also a few comments below.

So I still don't have anything better.
Another thing is that in "-> decltype(j)" we don't have the right
current_function_decl yet, so I've added the in_lambda_declarator_p flag
to be used in finish_decltype_type so that we know this decltype appertains
to a lambda -- then current_lambda_expr should give us the right lambda,
which has another new flag tracking whether mutable was seen.

The flag to finish_decltype_type seems unneeded; we should be able to tell
from the proxy that it belongs to a lambda.  And I would think that the new
handling in finish_decltype_type seems right in general; always refer to
current_lambda_expr instead of current_function_decl, etc.

Good point.  I've removed the flag and simplified the patch quite a bit.
However:
- to honor [expr.prim.id.unqual]/4, I have to know if the decltype is
    in the lambda's parameter-declaration-clause or not:

      [=]() -> decltype((x))  // float const&
      [=](decltype((x)) y)    // float&

    so I'm using LAMBDA_EXPR_CONST_QUAL_P for that.

Makes sense.

- if we want to handle nested lambdas correctly:

     [=](decltype((x)) y) {}  // float&

     [=] {
       [](decltype((x)) y) {};  // float const&
     }

    we probably will need a new flag for decltype.

Hmm?  Since the inner lambda has no capture-default, it doesn't qualify
under https://eel.is/c++draft/expr#prim.id.unqual-4.3 , so we look to the
outer lambda instead.

I would believe that we need to improve finish_decltype_type to handle this
properly (see also PR112926) but I don't see the need for a decltype flag.

I'll come back to this later.

@@ -3351,8 +3351,12 @@ check_local_shadow (tree decl)
        }
         /* Don't complain if it's from an enclosing function.  */
         else if (DECL_CONTEXT (old) == current_function_decl
-              && TREE_CODE (decl) != PARM_DECL
-              && TREE_CODE (old) == PARM_DECL)
+              && ((TREE_CODE (decl) != PARM_DECL
+                   && TREE_CODE (old) == PARM_DECL)
+                  || (is_capture_proxy (old)
+                      && current_lambda_expr ()
+                      && DECL_CONTEXT (old)
+                         == lambda_function (current_lambda_expr ()))))

What case is this handling?  Doesn't the previous if already deal with
parm/capture collision?

The proposal says that

    [x=1]{ int x; }

is invalid, so I wanted to give an error for it.  But since -Wshadow
warns for the case above, I've dropped that hunk and that simplifies
the patch even more.

It is good to give an error, not just a -Wshadow warning; now that I
understand the case you're trying to handle the above hunk makes more sense,
just please add a comment.  But for this case you shouldn't need to handle
proxies before the function body, so DECL_CONTEXT == current_function_decl
should be enough?

Ok, I've put it back.  And yes -- I don't have to check current_lambda_expr!
So it's simpler now.

Here's what I have now.

-- >8 --
This patch is an attempt to implement P2036R3 along with P2579R0, fixing
build breakages caused by P2036R3.

The simplest example is:

   auto counter1 = [j=0]() mutable -> decltype(j) {
       return j++;
   };

which currently doesn't compile because the 'j' in the capture isn't
visible in the trailing return type.  With these proposals, the 'j'
will be in a lambda scope which spans the trailing return type, so
this test will compile.

This oughtn't be difficult but decltype and other issues made this patch
much more challenging.

We have to push the explicit captures before going into _lambda_declarator_opt
because that is what parses the trailing return type.  Yet we can't build
any captures until after _lambda_body -> start_lambda_function which
creates the lambda's operator(), without which we can't build a proxy,
but _lambda_body happens only after parsing the declarator.  This patch
works around it by creating a fake operator() in make_dummy_lambda_op.

Another thing is that in "-> decltype(j)" we don't have the right
current_function_decl yet.  If current_lambda_expr gives us a lambda,
we know this decltype appertains to a lambda.  But we have to know if we
are in a parameter-declaration-clause: as per [expr.prim.id.unqual]/4.4,
if we are, we shouldn't be adding "const".  The new LAMBDA_EXPR_CONST_QUAL_P
flag tracks this.  But it doesn't handle nested lambdas yet, specifically,
[expr.prim.id.unqual]/14.

I don't think this patch changes behavior for the tests in
"capture-default with [=]" as the paper promises; clang++ behaves the
same as gcc with this patch.

diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 0d9ed2ea82b..d238ecc3432 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -11868,6 +11868,14 @@ cp_parser_lambda_expression (cp_parser* parser)
      if (cp_parser_start_tentative_firewall (parser))
        start = token;
+ /* A lambda scope starts immediately after the lambda-introducer of E
+       and extends to the end of the compound-statement of E.  */
+    begin_scope (sk_lambda, NULL_TREE);
+
+    /* Inject the captures for the sake of the possible
+       trailing-return-type -- we have to be able to look them up.  */

This comment seems misleading since they now are in scope throughout the lambda declarator.

+    push_capture_proxies (lambda_expr);
+
      ok &= cp_parser_lambda_declarator_opt (parser, lambda_expr);
if (ok && cp_parser_error_occurred (parser))
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 86b09049677..4d07a00f89a 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4514,6 +4514,14 @@ outer_automatic_var_p (tree decl)
          && !TREE_STATIC (decl));
  }
+/* Returns true iff DECL is an automatic variable. */
+
+static bool
+automatic_var_p (tree decl)
+{
+  return VAR_P (decl) && !TREE_STATIC (decl);
+}
+
  /* DECL satisfies outer_automatic_var_p.  Possibly complain about it or
     rewrite it for lambda capture.
@@ -12888,9 +12896,12 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p,
      }
    else
      {
-      if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr))
-         && current_function_decl
-         && LAMBDA_FUNCTION_P (current_function_decl))
+      const bool cfun_is_lambda_p
+       = (current_function_decl && LAMBDA_FUNCTION_P (current_function_decl));
+      if ((outer_automatic_var_p (STRIP_REFERENCE_REF (expr))
+          && cfun_is_lambda_p)
+         || (automatic_var_p (STRIP_REFERENCE_REF (expr))
+             && current_lambda_expr ()))

Can we make outer_automatic_var_p give the right answer in this context instead of adding a separate automatic_var_p, and then just change the current_function_decl test to current_lambda_expr ()?

What value does cfun_is_lambda_p get if we're in a lambda-declarator inside another lambda?

        {
          /* [expr.prim.id.unqual]/3: If naming the entity from outside of an
             unevaluated operand within S would refer to an entity captured by
@@ -12908,7 +12919,7 @@ finish_decltype_type (tree expr, bool 
id_expression_or_member_access_p,
             And we don't handle nested lambdas properly, where we need to
             consider the outer lambdas as well (PR112926). */
          tree decl = STRIP_REFERENCE_REF (expr);
-         tree lam = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT 
(current_function_decl));
+         tree lam = current_lambda_expr ();
          tree cap = lookup_name (DECL_NAME (decl), LOOK_where::BLOCK,
                                  LOOK_want::HIDDEN_LAMBDA);
@@ -12924,17 +12935,26 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, if (type && !TYPE_REF_P (type))
            {
-             tree obtype = TREE_TYPE (DECL_ARGUMENTS (current_function_decl));
-             if (WILDCARD_TYPE_P (non_reference (obtype)))
-               /* We don't know what the eventual obtype quals will be.  */
-               goto dependent;
-             auto direct_type = [](tree t){
-                 if (INDIRECT_TYPE_P (t))
-                   return TREE_TYPE (t);
-                 return t;
-              };
-             int const quals = cp_type_quals (type)
-                             | cp_type_quals (direct_type (obtype));
+             int quals;
+             if (cfun_is_lambda_p)
+               {
+                 tree obtype = TREE_TYPE (DECL_ARGUMENTS 
(current_function_decl));
+                 if (WILDCARD_TYPE_P (non_reference (obtype)))
+                   /* We don't know what the eventual obtype quals will be.  */
+                   goto dependent;
+                 auto direct_type = [](tree t){
+                     if (INDIRECT_TYPE_P (t))
+                       return TREE_TYPE (t);
+                     return t;
+                  };
+                 quals = (cp_type_quals (type)
+                          | cp_type_quals (direct_type (obtype)));

We might change this block to just be for the explicit object parm case, and handle all other lambdas with LAMBDA_EXPR_CONST_QUAL_P?

+               }
+             else
+               /* We are in the parameter clause, trailing return type, or
+                  the requires clause and have no relevant c_f_decl yet.  */
+               quals = (LAMBDA_EXPR_CONST_QUAL_P (lam)
+                        ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED);
              type = cp_build_qualified_type (type, quals);
              type = build_reference_type (type);
            }

Reply via email to