On 8/4/25 4:53 PM, Marek Polacek wrote:
On Wed, Jul 30, 2025 at 11:55:53PM -0400, Jason Merrill wrote:
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.

Yeah, probably not by much :/.
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?

I would like to remove it but if I'm using it here then I guess I can't,
yet.

Are there uses of LAMBDA_FUNCTION_P that can't be replaced with DECL_LAMBDA_FUNCTION_P?

In any case this new check needs a comment.

Added.

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?

Ah, yes.  That's because I started only adding the dummy op() if there
are no captures.  I've added lambda-scope9.C (reduced from lambda-uneval17.C)
to show the error we'd get without the push/pop.  Unfortunately it
captures a non-automatic variable.  I don't know if there is a better
way to test it.

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.

Sorry, I don't understand.  Yes, we need to make that distinction.
But outer_automatic_var_p only gets one decl, and otherwise only looks
at current_function_decl.  And for both 'f' and 'of_type_X'
DECL_CONTEXT (decl) == current_function_decl.  And for both there is
current_lambda_expr ().

It should be possible to distinguish them by looking at the binding levels/IDENTIFIER_BINDING if necessary.

I'm also suprised that of_type_X and f would have the same DECL_CONTEXT. They clearly shouldn't, but perhaps they do at this point because we haven't set the DECL_CONTEXT of of_type_X properly yet?

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

FIXMEs in finish_decltype_type?

Yes.

Jason

Reply via email to