Namespaces
Variants

Coroutines (C++20)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Una corrutina es una función que puede suspender su ejecución para ser reanudada posteriormente. Las corrutinas son sin pila: suspenden la ejecución al retornar al llamador, y los datos requeridos para reanudar la ejecución se almacenan separadamente de la pila. Esto permite código secuencial que se ejecuta de forma asíncrona (por ejemplo, para manejar E/S no bloqueante sin callbacks explícitos), y también soporta algoritmos sobre secuencias infinitas de cálculo diferido y otros usos.

Una función es una corrutina si su definición contiene cualquiera de los siguientes elementos:

  • la expresión co_await — para suspender la ejecución hasta que se reanude
task<> tcp_echo_server()
{
    char data[1024];
    while (true)
    {
        std::size_t n = co_await socket.async_read_some(buffer(data));
        co_await async_write(socket, buffer(data, n));
    }
}
  • la expresión co_yield — para suspender la ejecución devolviendo un valor
generator<unsigned int> iota(unsigned int n = 0)
{
    while (true)
        co_yield n++;
}
  • la co_return statement — para completar la ejecución devolviendo un valor
lazy<int> f()
{
    co_return 7;
}

Cada corrutina debe tener un tipo de retorno que cumpla con una serie de requisitos, señalados a continuación.

Contenidos

Restricciones

Las corrutinas no pueden usar argumentos variádicos , sentencias return simples, o tipos de retorno de marcador de posición ( auto o Concept ).

Funciones consteval , funciones constexpr , constructores , destructores , y la función main no pueden ser corrutinas.

Ejecución

Cada corrutina está asociada con

  • el objeto promise , manipulado desde dentro de la corrutina. La corrutina envía su resultado o excepción a través de este objeto. Los objetos promise no guardan relación alguna con std::promise .
  • el coroutine handle , manipulado desde fuera de la corrutina. Este es un manejador no propietario utilizado para reanudar la ejecución de la corrutina o para destruir el frame de la corrutina.
  • el coroutine state , que es un almacenamiento interno, asignado dinámicamente (a menos que la asignación sea optimizada), objeto que contiene
  • el objeto promise
  • los parámetros (todos copiados por valor)
  • alguna representación del punto de suspensión actual, para que un resume sepa dónde continuar, y un destroy sepa qué variables locales estaban en alcance
  • variables locales y temporales cuya duración abarca el punto de suspensión actual.

Cuando una corrutina comienza su ejecución, realiza lo siguiente:

  • asigna el objeto de estado de la corrutina usando operator new .
  • copia todos los parámetros de la función al estado de la corrutina: los parámetros por valor se mueven o copian, los parámetros por referencia permanecen como referencias (por lo tanto, pueden quedar colgadas si la corrutina se reanuda después de que finalice la vida útil del objeto referido — ver ejemplos más adelante).
  • llama al constructor para el objeto promise. Si el tipo promise tiene un constructor que toma todos los parámetros de la corrutina, se llama a ese constructor, con los argumentos de la corrutina posteriores a la copia. De lo contrario, se llama al constructor por defecto.
  • llama a promise. get_return_object ( ) y guarda el resultado en una variable local. El resultado de esa llamada se devolverá al llamador cuando la corrutina se suspenda por primera vez. Cualquier excepción lanzada hasta este paso inclusive se propaga de vuelta al llamador, no se coloca en el promise.
  • llama a promise. initial_suspend ( ) y co_await su resultado. Los tipos Promise típicos devuelven std::suspend_always , para corrutinas de inicio diferido, o std::suspend_never , para corrutinas de inicio inmediato.
  • cuando co_await promise. initial_suspend ( ) se reanuda, comienza a ejecutar el cuerpo de la corrutina.

Algunos ejemplos de un parámetro que se convierte en colgante:

#include <coroutine>
#include <iostream>
struct promise;
struct coroutine : std::coroutine_handle<promise>
{
    using promise_type = ::promise;
};
struct promise
{
    coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
};
struct S
{
    int i;
    coroutine f()
    {
        std::cout << i;
        co_return;
    }
};
void bad1()
{
    coroutine h = S{0}.f();
    // S{0} destruido
    h.resume(); // la corrutina reanudada ejecuta std::cout << i, usa S::i después de liberar
    h.destroy();
}
coroutine bad2()
{
    S s{0};
    return s.f(); // la corrutina devuelta no puede reanudarse sin cometer uso después de liberar
}
void bad3()
{
    coroutine h = [i = 0]() -> coroutine // una lambda que también es una corrutina
    {
        std::cout << i;
        co_return;
    }(); // invocada inmediatamente
    // lambda destruida
    h.resume(); // usa (tipo lambda anónimo)::i después de liberar
    h.destroy();
}
void good()
{
    coroutine h = [](int i) -> coroutine // hacer i un parámetro de la corrutina
    {
        std::cout << i;
        co_return;
    }(0);
    // lambda destruida
    h.resume(); // sin problema, i ha sido copiado al marco de la corrutina
                // como un parámetro por valor
    h.destroy();
}

Cuando una corrutina alcanza un punto de suspensión

  • el objeto de retorno obtenido anteriormente se devuelve al llamador/resumidor, después de la conversión implícita al tipo de retorno de la corrutina, si es necesario.

Cuando una corrutina alcanza la declaración co_return , realiza lo siguiente:

  • llama a promise. return_void ( ) para
  • co_return ;
  • co_return expr ; donde expr tiene tipo void
  • o llama promise. return_value ( expr ) para co_return expr ; donde expr tiene tipo no void
  • destruye todas las variables con duración de almacenamiento automático en orden inverso al que fueron creadas.
  • llama promise. final_suspend ( ) y co_await el resultado.

Caer al final de la corrutina es equivalente a co_return ; , excepto que el comportamiento es indefinido si no se pueden encontrar declaraciones de return_void en el ámbito de Promise . Una función sin ninguna de las palabras clave definitorias en su cuerpo de función no es una corrutina, independientemente de su tipo de retorno, y caer al final resulta en comportamiento indefinido si el tipo de retorno no es (posiblemente calificado cv) void .

// suponiendo que task es algún tipo de tarea de corrutina
task<void> f()
{
    // no es una corrutina, comportamiento indefinido
}
task<void> g()
{
    co_return;  // OK
}
task<void> h()
{
    co_await g();
    // OK, co_return implícito;
}

Si la corrutina termina con una excepción no capturada, realiza lo siguiente:

  • captura la excepción y llama promise. unhandled_exception ( ) desde dentro del bloque catch
  • llama promise. final_suspend ( ) y co_await al resultado (por ejemplo, para reanudar una continuación o publicar un resultado). Es comportamiento indefinido reanudar una corrutina desde este punto.

Cuando el estado de la corrutina es destruido, ya sea porque terminó mediante co_return o por una excepción no capturada, o porque fue destruido mediante su manejador, realiza lo siguiente:

  • llama al destructor del objeto promise.
  • llama a los destructores de las copias de los parámetros de la función.
  • llama a operator delete para liberar la memoria utilizada por el estado de la corrutina.
  • transfiere la ejecución de vuelta al llamador/resumidor.

Asignación dinámica

El estado de la corrutina se asigna dinámicamente mediante el operator new no-array.

Si el tipo Promise define un reemplazo a nivel de clase, será utilizado; de lo contrario, se utilizará el operator new global.

Si el tipo Promise define una forma de colocación de operator new que toma parámetros adicionales, y estos coinciden con una lista de argumentos donde el primer argumento es el tamaño solicitado (de tipo std::size_t ) y el resto son los argumentos de la función de corrutina, esos argumentos se pasarán a operator new (esto hace posible usar la convención de asignador principal para corrutinas).

La llamada a operator new puede ser optimizada (incluso si se utiliza un asignador personalizado) si

  • La duración del estado de la corrutina está estrictamente anidada dentro de la duración del llamador, y
  • el tamaño del marco de corrutina se conoce en el sitio de llamada.

En ese caso, el estado de la corrutina está incrustado en el marco de pila del llamador (si el llamador es una función ordinaria) o estado de corrutina (si el llamador es una corrutina).

Si la asignación falla, la corrutina lanza std::bad_alloc , a menos que el tipo Promise defina la función miembro Promise :: get_return_object_on_allocation_failure ( ) . Si esa función miembro está definida, la asignación utiliza la forma nothrow de operator new y en caso de fallo de asignación, la corrutina retorna inmediatamente el objeto obtenido de Promise :: get_return_object_on_allocation_failure ( ) al llamador, por ejemplo:

struct Coroutine::promise_type
{
    /* ... */
    // asegurar el uso de operator-new que no lanza excepciones
    static Coroutine get_return_object_on_allocation_failure()
    {
        std::cerr << __func__ << '\n';
        throw std::bad_alloc(); // o, return Coroutine(nullptr);
    }
    // sobrecarga personalizada de new que no lanza excepciones
    void* operator new(std::size_t n) noexcept
    {
        if (void* mem = std::malloc(n))
            return mem;
        return nullptr; // fallo de asignación
    }
};

Promesa

El tipo Promise es determinado por el compilador a partir del tipo de retorno de la corrutina utilizando std::coroutine_traits .

Formalmente, sea

  • R y Args... denotan el tipo de retorno y la lista de tipos de parámetros de una corrutina respectivamente,
  • ClassT denota el tipo de clase a la que pertenece la corrutina si está definida como una función miembro no estática,
  • cv denota la calificación cv declarada en la declaración de función si está definida como una función miembro no estática,

su Promise tipo está determinado por:

Por ejemplo:

Si la corrutina se define como ... entonces su tipo Promise es ...
task < void > foo ( int x ) ; std:: coroutine_traits < task < void > , int > :: promise_type
task < void > Bar :: foo ( int x ) const ; std:: coroutine_traits < task < void > , const Bar & , int > :: promise_type
task < void > Bar :: foo ( int x ) && ; std:: coroutine_traits < task < void > , Bar && , int > :: promise_type

co_await

El operador unario co_await suspende una corrutina y devuelve el control al llamador.

co_await expr

Una expresión co_await solo puede aparecer en una expresión potencialmente evaluada dentro del cuerpo de una función regular cuerpo de función (incluyendo el cuerpo de función de una expresión lambda ), y no puede aparecer

Una expresión co_await no puede ser una subexpresión potencialmente evaluada del predicado de una aserción de contrato .

(since C++26)

Primero, expr se convierte en un awaitable de la siguiente manera:

  • si expr es producido por un punto de suspensión inicial, un punto de suspensión final, o una expresión yield, el awaitable es expr , tal cual.
  • de lo contrario, si el tipo Promise de la corrutina actual tiene la función miembro await_transform , entonces el awaitable es promise. await_transform ( expr ) .
  • de lo contrario, el awaitable es expr , tal cual.

Luego, se obtiene el objeto awaiter, de la siguiente manera:

  • si la resolución de sobrecarga para operator co_await da una única mejor sobrecarga, el awaiter es el resultado de esa llamada:
  • awaitable. operator co_await ( ) para la sobrecarga de miembro,
  • operator co_await ( static_cast < Awaitable && > ( awaitable ) ) para la sobrecarga no miembro.
  • de lo contrario, si la resolución de sobrecarga no encuentra ningún operador co_await , el awaiter es directamente esperable.
  • de lo contrario, si la resolución de sobrecarga es ambigua, el programa está mal formado.

Si la expresión anterior es un prvalue , el objeto awaiter es un temporal materializado a partir de él. De lo contrario, si la expresión anterior es un glvalue , el objeto awaiter es el objeto al cual se refiere.

Entonces, awaiter. await_ready ( ) es llamado (esto es un atajo para evitar el costo de suspensión si se sabe que el resultado está listo o puede completarse sincrónicamente). Si su resultado, convertido contextualmente a bool es false entonces

La corrutina se suspende (su estado de corrutina se completa con variables locales y el punto de suspensión actual).
awaiter. await_suspend ( handle ) se llama, donde handle es el identificador de corrutina que representa la corrutina actual. Dentro de esa función, el estado suspendido de la corrutina es observable a través de ese identificador, y es responsabilidad de esta función programarlo para reanudar en algún ejecutor, o para ser destruido (devolver false cuenta como programación)
  • si await_suspend devuelve void , el control se devuelve inmediatamente al llamador/reanudador de la corrutina actual (esta corrutina permanece suspendida), de lo contrario
  • si await_suspend devuelve bool ,
  • el valor true devuelve el control al llamador/reanudador de la corrutina actual
  • el valor false reanuda la corrutina actual.
  • si await_suspend devuelve un identificador de corrutina para alguna otra corrutina, ese identificador se reanuda (mediante una llamada a handle. resume ( ) ) (nota: esto puede encadenarse para eventualmente causar que la corrutina actual se reanude).
  • si await_suspend lanza una excepción, la excepción se captura, la corrutina se reanuda y la excepción se vuelve a lanzar inmediatamente.

Finalmente, awaiter. await_resume ( ) es llamado (ya sea que la corrutina haya sido suspendida o no), y su resultado es el resultado de toda la expresión co_await expr .

Si la corrutina fue suspendida en la expresión co_await , y posteriormente es reanudada, el punto de reanudación es inmediatamente antes de la llamada a awaiter. await_resume ( ) .

Tenga en cuenta que la corrutina está completamente suspendida antes de entrar en awaiter. await_suspend ( ) . Su manejador puede compartirse con otro hilo y reanudarse antes de que la función await_suspend ( ) retorne. (Nótese que las reglas predeterminadas de seguridad de memoria aún se aplican, por lo que si un manejador de corrutina se comparte entre hilos sin un bloqueo, el awaiter debe usar al menos semánticas de liberación y el reanudador debe usar al menos semánticas de adquisición .) Por ejemplo, el manejador de corrutina puede colocarse dentro de una devolución de llamada, programada para ejecutarse en un grupo de hilos cuando se complete la operación de E/S asíncrona. En ese caso, dado que la corrutina actual pudo haber sido reanudada y por lo tanto ejecutado el destructor del objeto awaiter, todo concurrentemente mientras await_suspend ( ) continúa su ejecución en el hilo actual, await_suspend ( ) debe tratar * this como destruido y no acceder a él después de que el manejador fue publicado a otros hilos.

Ejemplo

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
auto switch_to_new_thread(std::jthread& out)
{
    struct awaitable
    {
        std::jthread* p_out;
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<> h)
        {
            std::jthread& out = *p_out;
            if (out.joinable())
                throw std::runtime_error("Output jthread parameter not empty");
            out = std::jthread([h] { h.resume(); });
            // Potential undefined behavior: accessing potentially destroyed *this
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
        }
        void await_resume() {}
    };
    return awaitable{&out};
}
struct task
{
    struct promise_type
    {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};
task resuming_on_new_thread(std::jthread& out)
{
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

Salida posible:

Coroutine started on thread: 139972277602112
New thread ID: 139972267284224
Coroutine resumed on thread: 139972267284224

Nota: el objeto awaiter es parte del estado de la corrutina (como un temporal cuya vida cruza un punto de suspensión) y se destruye antes de que la expresión co_await finalice. Puede utilizarse para mantener el estado por operación según lo requieren algunas APIs de E/S asíncronas sin recurrir a asignaciones dinámicas adicionales.

La biblioteca estándar define dos awaitables triviales: std::suspend_always y std::suspend_never .

Demostración de promise_type :: await_transform y un awaiter proporcionado por el programa

Ejemplo

#include <cassert>
#include <coroutine>
#include <iostream>
struct tunable_coro
{
    // Un awaiter cuya "disponibilidad" se determina mediante el parámetro del constructor.
    class tunable_awaiter
    {
        bool ready_;
    public:
        explicit(false) tunable_awaiter(bool ready) : ready_{ready} {}
        // Tres funciones estándar de la interfaz awaiter:
        bool await_ready() const noexcept { return ready_; }
        static void await_suspend(std::coroutine_handle<>) noexcept {}
        static void await_resume() noexcept {}
    };
    struct promise_type
    {
        using coro_handle = std::coroutine_handle<promise_type>;
        auto get_return_object() { return coro_handle::from_promise(*this); }
        static auto initial_suspend() { return std::suspend_always(); }
        static auto final_suspend() noexcept { return std::suspend_always(); }
        static void return_void() {}
        static void unhandled_exception() { std::terminate(); }
        // Una función de transformación proporcionada por el usuario que devuelve el awaiter personalizado:
        auto await_transform(std::suspend_always) { return tunable_awaiter(!ready_); }
        void disable_suspension() { ready_ = false; }
    private:
        bool ready_{true};
    };
    tunable_coro(promise_type::coro_handle h) : handle_(h) { assert(h); }
    // Por simplicidad, declare estas 4 funciones especiales como eliminadas:
    tunable_coro(tunable_coro const&) = delete;
    tunable_coro(tunable_coro&&) = delete;
    tunable_coro& operator=(tunable_coro const&) = delete;
    tunable_coro& operator=(tunable_coro&&) = delete;
    ~tunable_coro()
    {
        if (handle_)
            handle_.destroy();
    }
    void disable_suspension() const
    {
        if (handle_.done())
            return;
        handle_.promise().disable_suspension();
        handle_();
    }
    bool operator()()
    {
        if (!handle_.done())
            handle_();
        return !handle_.done();
    }
private:
    promise_type::coro_handle handle_;
};
tunable_coro generate(int n)
{
    for (int i{}; i != n; ++i)
    {
        std::cout << i << ' ';
        // The awaiter passed to co_await goes to promise_type::await_transform which
        // issues tunable_awaiter que inicialmente causa suspensión (regresando a
        // main en cada iteración), pero después de una llamada a disable_suspension no hay suspensión
        // ocurre y el bucle se ejecuta hasta su finalización sin retornar a main().
        co_await std::suspend_always{};
    }
}
int main()
{
    auto coro = generate(8);
    coro(); // emite solo el primer elemento == 0
    for (int k{}; k < 4; ++k)
    {
        coro(); // emite 1 2 3 4, uno por cada iteración
        std::cout << ": ";
    }
    coro.disable_suspension();
    coro(); // emite los números de cola 5 6 7 todos a la vez
}

Salida:

0 1 : 2 : 3 : 4 : 5 6 7

co_yield

co_yield expression devuelve un valor al llamador y suspende la corrutina actual: es el bloque de construcción común de las funciones generadoras reanudables.

co_yield expr
co_yield braced-init-list

Es equivalente a

co_await promise.yield_value(expr)

Un generador típico yield_value almacenaría (copiar/mover o simplemente almacenar la dirección de, ya que el tiempo de vida del argumento cruza el punto de suspensión dentro del co_await ) su argumento en el objeto generador y retornaría std::suspend_always , transfiriendo control al llamador/resumidor.

#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>
template<typename T>
struct Generator
{
    // El nombre de la clase 'Generator' es nuestra elección y no es requerido para coroutine
    // magia. El compilador reconoce la corrutina por la presencia de la palabra clave 'co_yield'.
    // Puedes usar el nombre 'MyGenerator' (o cualquier otro nombre) en su lugar siempre que incluyas
    // estructura anidada promise_type con el método 'MyGenerator get_return_object()'.
    // (Nota: Es necesario ajustar las declaraciones de constructores y destructores
    //  cuando se renombrar.)
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type // requerido
    {
        T value_;
        std::exception_ptr exception_;
        Generator get_return_object()
        {
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { exception_ = std::current_exception(); } // guardando
                                                                              // exception
        template<std::convertible_to<T> From> // C++20 concept
        std::suspend_always yield_value(From&& from)
        {
            value_ = std::forward<From>(from); // almacenando en caché el resultado en promise
            return {};
        }
        void return_void() {}
    };
    handle_type h_;
    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool()
    {
        fill(); // La única manera confiable de averiguar si hemos finalizado la corrutina o no,
                // si habrá o no un siguiente valor generado (co_yield)
                // en corrutina mediante getter de C++ (operator () abajo) es para ejecutar/reanudar
                // coroutine hasta el siguiente punto co_yield (o dejarla terminar al final).
                // Luego almacenamos/en cache el resultado en la promesa para permitir el getter (operator() a continuación
                // para capturarlo sin ejecutar la corrutina).
        return !h_.done();
    }
    T operator()()
    {
        fill();
        full_ = false; // vamos a mover nuestro previamente almacenado en caché
                       // resultado para hacer la promesa vacía de nuevo
        return std::mover(h_.promise().value_);
    }
private:
    bool full_ = false;
    void fill()
    {
        if (!full_)
        {
            h_();
            if (h_.promise().exception_)
                std::rethrow_exception(h_.promise().exception_);
            // propagar excepción de corrutina en contexto llamado
            full_ = true;
        }
    }
};
Generator<std::uint64_t>
fibonacci_sequence(unsigned n)
{
    if (n == 0)
        co_return;
    if (n > 94)
        throw std::runtime_error("Secuencia de Fibonacci demasiado grande. Los elementos desbordarían.");
    co_yield 0;
    if (n == 1)
        co_return;
    co_yield 1;
    if (n == 2)
        co_return;
    std::uint64_t a = 0;
    std::uint64_t b = 1;
    for (unsigned i = 2; i < n; ++i)
    {
        std::uint64_t s = a + b;
        co_yield s;
        a = b;
        b = s;
    }
}
int main()
{
    try
    {
        auto gen = fibonacci_sequence(10); // máximo 94 antes de que uint64_t desborde
        for (int j = 0; gen; ++j)
            std::cout << "fib(" << j << ")=" << gen() << '\n';
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Excepción: " << ex.qué() << '\n';
    }
    catch (...)
    {
        std::cerr << "Excepción desconocida.\n";
    }
}

Salida:

fib(0)=0
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
fib(9)=34

Notas

Macro de prueba de características Valor Estándar Característica
__cpp_impl_coroutine 201902L (C++20) Corrutinas (soporte del compilador)
__cpp_lib_coroutine 201902L (C++20) Corrutinas (soporte de biblioteca)
__cpp_lib_generator 202207L (C++23) std::generator : generador de corrutina síncrona para rangos

Palabras clave

co_await , co_return , co_yield

Soporte de biblioteca

Biblioteca de soporte para corrutinas define varios tipos que proporcionan soporte en tiempo de compilación y ejecución para corrutinas.

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 2556 C++20 un return_void inválido hacía que el comportamiento
de caer al final de la corrutina fuera indefinido
el programa está mal
formado en este caso
CWG 2668 C++20 co_await no podía aparecer en expresiones lambda permitido
CWG 2754 C++23 * this se tomaba al construir el objeto promise
para funciones miembro de objeto explícito
* this no se
toma en este caso

Véase también

(C++23)
Un view que representa un generador síncrono de corrutina
(plantilla de clase)

Enlaces externos

1. Lewis Baker, 2017-2022 - Asymmetric Transfer.
2. David Mazières, 2021 - Tutorial on C++20 coroutines.
3. Chuanqi Xu & Yu Qi & Yao Han, 2021 - C++20 Principles and Applications of Coroutine. (Chino)
4. Simon Tatham, 2023 - Writing custom C++20 coroutine systems.