Date: Tue, 30 Sep 2025 14:00:45 -0400
(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
>
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
>
Received on 2025-09-30 18:01:04