Date: Wed, 01 Oct 2025 12:16:59 +0300
The problem is that the lambda has the signature
return_type lambda::operator()(args...) const
This in turn translates to the function signature
return_type (const lambda&, args)
which leaves the capture group on the caller's stack. If the coroutine
outlives it, it is lost.
I proposed in the past to allow &&-qualified lambdas:
[captures...] (args...) && { body }
These would have the signature
return_type lambda::operator()(args...) &&
Translating to
return_type (lambda&&, args...)
If, in addition, we change the rule that coroutine member functions
that are rvalue-qualified have their rvalue qualification removed, we
end up with
return_type (lambda, args...)
and now the capture group lives in the coroutine frame.
This was previously discussed
in https://lists.isocpp.org/std-proposals/2020/05/1367.php. [3]
On Tue, 2025-09-30 at 19:20 +0200, Rhidian De Wit via Std-Proposals
wrote:
> 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 [4]:
> * 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 [2] the coroutine state object using operator new [1].
> * 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
[1] operator new
https://en.cppreference.com/w/cpp/memory/new/operator_new.html
[2] allocates
https://en.cppreference.com/w/cpp/language/coroutines.html#Dynamic_allocation
[3] https://lists.isocpp.org/std-proposals/2020/05/1367.php.
https://lists.isocpp.org/std-proposals/2020/05/1367.php
[4] cppreference
https://en.cppreference.com/w/cpp/language/coroutines.html
return_type lambda::operator()(args...) const
This in turn translates to the function signature
return_type (const lambda&, args)
which leaves the capture group on the caller's stack. If the coroutine
outlives it, it is lost.
I proposed in the past to allow &&-qualified lambdas:
[captures...] (args...) && { body }
These would have the signature
return_type lambda::operator()(args...) &&
Translating to
return_type (lambda&&, args...)
If, in addition, we change the rule that coroutine member functions
that are rvalue-qualified have their rvalue qualification removed, we
end up with
return_type (lambda, args...)
and now the capture group lives in the coroutine frame.
This was previously discussed
in https://lists.isocpp.org/std-proposals/2020/05/1367.php. [3]
On Tue, 2025-09-30 at 19:20 +0200, Rhidian De Wit via Std-Proposals
wrote:
> 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 [4]:
> * 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 [2] the coroutine state object using operator new [1].
> * 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
[1] operator new
https://en.cppreference.com/w/cpp/memory/new/operator_new.html
[2] allocates
https://en.cppreference.com/w/cpp/language/coroutines.html#Dynamic_allocation
[3] https://lists.isocpp.org/std-proposals/2020/05/1367.php.
https://lists.isocpp.org/std-proposals/2020/05/1367.php
[4] cppreference
https://en.cppreference.com/w/cpp/language/coroutines.html
Received on 2025-10-01 09:17:07