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?

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.

@@ -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?

Jason

Reply via email to