On 7/25/25 4:55 PM, Marek Polacek wrote:
On Thu, Jul 17, 2025 at 05:20:31PM -0400, Jason Merrill wrote:
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?

[ Responded elsewhere. ]
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.

I've overhauled the patch to use one dummy op() per lambda.  But it was
pretty complicated.

I still have to do the whole make_call_declarator + grokmethod dance once
for the dummy op() and once for the real op() (and then when instantiating);
there could have been a requires clause/attributes on the real op() and
I can't simply clobber the old decl.

When finish_member_declaration -> add_method wants to add the real op(),
the dummy op() can't be in the closure or else we fail with "cannot be
overloaded".  So I have to remove the dummy op() prior to add_method,
this is done via remove_dummy_lambda_op.

Maybe we could special case add_method to combine them instead of giving an error? ...but perhaps that won't be significantly less hackish than this approach.

Now that even dummy lambdas have an operator(), I had to tweak
resolvable_dummy_lambda to give the right answer.  This I did by
checking DECL_LAMBDA_FUNCTION_P which isn't set for the dummy op().

Hmm, that's a new one to me. Perhaps we should remove LAMBDA_FUNCTION_P now?

In any case this new check needs a comment.

In tsubst_lambda_expr I have to add the dummy op() as well.  That meant
I had to play some push_nested_namespace/class games, kind of like in
instantiate_decl.

Hmm, when I comment out the push/pop, none of the tests break?

If there are no explicit captures, as it's often the case, we can skip
the dummy op() altogether.

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

Sorry, I had no luck with this.  First I think process_outer_var_ref
can't handle these vars.  Also, like in lambda-generic-ice5.C, we can't
tell that in:

   int f = 3;
   auto l = [](auto of_type_X)->
               Void<(decltype(of_type_X)::foo(f), 0)>
     {return;};

'f' is outer, but 'of_type_X' is not.

But we need to make that distinction. Which shouldn't be so hard, since they should have different DECL_CONTEXT and are in different binding levels.

And to address the FIXMEs there, the way process_outer_var_ref walks through intervening lambdas seems like exactly what we need to do.

Jason

Reply via email to