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.



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:
  • 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 the coroutine state object using operator new.
  • 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