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.
> 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 ().
> 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?
Marek