C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Async Lambdas

From: Rhidian De Wit <rhidiandewit_at_[hidden]>
Date: Wed, 1 Oct 2025 08:43:52 +0200
Hi Arthur,

Thanks for the response, I'll take a look at making a PR in the Core
Guidelines to clarify the example a bit further.

I wasn't very familiar yet with the explicit parameter object, especially
not in lambdas, and after reading a bit about I did find that you can refer
to the captured variables in a lambda by using the `self` parameter you
declare: https://godbolt.org/z/9d1oPv9fG

Now, this is pretty painful syntax, as we'd constantly have to type
`std::forward_like<decltype(self)>(value)` to be able to use our values
safely.
My main problem with this is that it's super verbose and
`std::forward_like` is pretty confusing, especially as it's *kind of* like
`std::forward`, but quite different in its own right.

Now, I might be misusing this feature though, and maybe I'm not required to
use `std::forward_like` everywhere? If I understand it correctly, I might
indeed not have to, but it might be nice to have some confirmation from the
more knowledgeable members on this mailing list :)
Although from just a bit of experimentation, the `std::forward_like` seems
unnecessary: https://godbolt.org/z/bbc53shfj

So, I guess that my question of "Why does this not work in C++" is invalid,
as it does work in C++23 :)

Thanks for the very insightful response Arthur,

Best,


Op di 30 sep 2025 om 20:00 schreef Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]
>:

> (1) Maybe I'm missing something, but the "Example, Bad" from the Core
> Guidelines
> <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#cp51-do-not-use-capturing-lambdas-that-are-coroutines>
> looks like nonsense to me. In *your* example, you do `auto def =
> lambda(); co_await def;` which makes sense; but the Core Guidelines example
> just does `lambda();` — never awaiting the result — and so I think it's
> actually well-defined, if useless. I guess the //comments are in lieu of
> some actual code that could have gone wrong, but I can't guess from their
> phrasing what code they're supposed to be in lieu *of*. Would you
> consider making *a pull request against the Core Guidelines example*, to
> make it more meaningful and show what the problem is actually supposed to
> be? (Feel free to open an issue about it first too, and/or email me and
> I'll open an issue about it from my point of view.)
>
> (2) To your actual question: A lambda is just syntactic sugar for a struct
> with an operator(), and the captures of a lambda are just syntactic sugar
> for non-static data members of that struct. So any solution you can imagine
> for lambdas would have to work for plain old structs too. Here's a Godbolt
> <https://godbolt.org/z/5b5f9nxfM> of your same test case, lowered to a
> struct with an operator(). You see how the lambda is just a local variable,
> and so it goes out of scope at the curly-brace after `def = lambda();`.
> One thing we can do with the struct approach — but not with the lambda
> syntactic sugar — is to use an explicit object parameter (`this`
> parameter), like this <https://godbolt.org/z/E73s4E4PM>.
> Unfortunately I cannot think of any way to express that we want a
> *lambda's* operator() to take itself by value.
> We can write this:
> auto lambda = [x, y, z](this auto self) { ~~~ };
> lambda();
> but inside the `~~~` there is no way to get at `self.x`, `self.y`,
> `self.z`. A lambda's data members are (for some reason) not actually
> *named* `x`, `y`, `z`; those names are just syntactic sugar too. So even
> though I can mention `x` inside the lambda and get *my* own member `x`
> (because syntactic sugar), I can't get at the corresponding `x` member of *any
> other instance* of my own type (not even `self`), because that other
> instance (like me) doesn't actually have a member *named* `x`; it just
> has its own sugar that works only inside *it*.
>
> It might be productive to think about ways that we could make `this auto
> self` actually useful inside a lambda's body.
>
> Related but maybe orthogonal — maybe there could be a way to write, like,
> `this auto this`, with the semantics that I want to take myself by value
> but I also want to keep writing `myMember_` instead of `self.myMember_`
> throughout my function body. (Does this already exist and I'm just blanking
> on it? It seems super useful and obvious, and yet I don't *think* I've
> heard of it.)
>
> HTH,
> –Arthur
>
>
>
> On Tue, Sep 30, 2025 at 1:22 PM Rhidian De Wit via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> I forgot to mention, but I also reproduced the example from cppreference,
>> here: https://godbolt.org/z/TKMqM9r5W
>>
>> Op di 30 sep 2025 om 19:20 schreef Rhidian De Wit <rhidiandewit_at_[hidden]
>> >:
>>
>>> Hi all,
>>>
>>> A problem I regularly face at work is asynchronous lambdas, specifically
>>> the fact that the closure of an asynchronous lambda does not necessarily
>>> stay in scope after a suspension point, meaning that even if the captured
>>> variables remain in-scope, the capture group itself goes out-of-scope,
>>> causing all captured values in the lambda to become garbage.
>>>
>>> This is of course mentioned in the C++ core guidelines:
>>> https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#cp51-do-not-use-capturing-lambdas-that-are-coroutines
>>>
>>> However, I find myself wondering: why?
>>> Why does the capture group out-of-scope?
>>> From cppreference
>>> <https://en.cppreference.com/w/cpp/language/coroutines.html>:
>>>
>>> - *the coroutine state, which is internal, dynamically-allocated
>>> storage (unless the allocation is optimized out), object that contains *
>>>
>>>
>>> - * the promise object *
>>> - * the parameters (all copied by value) *
>>> - * some representation of the current suspension point, so that a
>>> resume knows where to continue, and a destroy knows what local variables
>>> were in scope *
>>> - * local variables and temporaries whose lifetime spans the current
>>> suspension point. *
>>>
>>> *When a coroutine begins execution, it performs the following: *
>>>
>>> - *allocates
>>> <https://en.cppreference.com/w/cpp/language/coroutines.html#Dynamic_allocation>
>>> the coroutine state object using operator new
>>> <https://en.cppreference.com/w/cpp/memory/new/operator_new.html>. *
>>> - * copies all function parameters to the coroutine state: by-value
>>> parameters are moved or copied, by-reference parameters remain references
>>> (thus, may become dangling, if the coroutine is resumed after the lifetime
>>> of referred object ends — see below for examples). *
>>>
>>> Could the lambda capture group not be allocated on the heap alongside
>>> all other heap allocated memory? This would make asynchronous lambdas
>>> possible and safe-to-use, which would be great, instead of having to always
>>> rely on actual functions.
>>>
>>> The reason that this is a problem for us is because of backwards
>>> compatability: We have a lot of old asynchronous code predating coroutines,
>>> and during conversions to coroutines, such details are easily forgotten,
>>> especially by people who aren't very used to coroutines and their quirks.
>>>
>>> Is there any ongoing proposal to address this problem?
>>>
>>> Thanks in advance for the replies and consideration,
>>>
>>> --
>>> Rhidian De Wit
>>> Software Engineer - Barco
>>>
>>
>>
>> --
>> Rhidian De Wit
>> Software Engineer - Barco
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>

-- 
Rhidian De Wit
Software Engineer - Barco

Received on 2025-10-01 06:44:07