C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Async Lambdas

From: Avi Kivity <avi_at_[hidden]>
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

Received on 2025-10-01 09:17:07