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

Une coroutine est une fonction qui peut suspendre son exécution pour être reprise ultérieurement. Les coroutines sont sans pile : elles suspendent l'exécution en retournant à l'appelant, et les données nécessaires à la reprise de l'exécution sont stockées séparément de la pile. Cela permet d'avoir un code séquentiel qui s'exécute de manière asynchrone (par exemple pour gérer des entrées/sorties non bloquantes sans rappels explicites), et prend également en charge des algorithmes sur des séquences infinies à calcul paresseux et d'autres utilisations.

Une fonction est une coroutine si sa définition contient l'un des éléments suivants :

  • l'expression co_await — pour suspendre l'exécution jusqu'à sa reprise
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));
    }
}
  • l'expression co_yield — pour suspendre l'exécution en renvoyant une valeur
generator<unsigned int> iota(unsigned int n = 0)
{
    while (true)
        co_yield n++;
}
  • l'instruction co_return — pour terminer l'exécution en renvoyant une valeur
lazy<int> f()
{
    co_return 7;
}

Chaque coroutine doit avoir un type de retour qui satisfait un certain nombre d'exigences, notées ci-dessous.

Table des matières

Restrictions

Les coroutines ne peuvent pas utiliser les arguments variadiques , les instructions return simples, ou les types de retour déductibles ( auto ou Concept ).

Fonctions consteval , fonctions constexpr , constructeurs , destructeurs , et la fonction main ne peuvent pas être des coroutines.

Exécution

Chaque coroutine est associée à

  • l' objet de promesse , manipulé depuis l'intérieur de la coroutine. La coroutine soumet son résultat ou son exception via cet objet. Les objets de promesse n'ont aucun lien avec std::promise .
  • le handle de coroutine , manipulé depuis l'extérieur de la coroutine. Il s'agit d'un handle non propriétaire utilisé pour reprendre l'exécution de la coroutine ou détruire le frame de coroutine.
  • l' état de coroutine , qui est un stockage interne alloué dynamiquement (sauf si l'allocation est optimisée), objet qui contient
  • l'objet promise
  • les paramètres (tous copiés par valeur)
  • une représentation du point de suspension actuel, afin qu'une reprise sache où continuer, et qu'une destruction sache quelles variables locales étaient dans la portée
  • les variables locales et temporaires dont la durée de vie couvre le point de suspension actuel.

Lorsqu'une coroutine commence son exécution, elle effectue les opérations suivantes :

  • alloue l'objet d'état de la coroutine en utilisant operator new .
  • copie tous les paramètres de fonction vers l'état de la coroutine : les paramètres par valeur sont déplacés ou copiés, les paramètres par référence restent des références (peuvent donc devenir pendantes si la coroutine est reprise après la fin de la durée de vie de l'objet référencé — voir les exemples ci-dessous).
  • appelle le constructeur pour l'objet promise. Si le type promise a un constructeur qui prend tous les paramètres de la coroutine, ce constructeur est appelé avec les arguments de la coroutine post-copie. Sinon, le constructeur par défaut est appelé.
  • appelle promise. get_return_object ( ) et conserve le résultat dans une variable locale. Le résultat de cet appel sera retourné à l'appelant lorsque la coroutine se suspend pour la première fois. Toutes les exceptions levées jusqu'à cette étape incluse se propagent vers l'appelant, et ne sont pas placées dans la promise.
  • appelle promise. initial_suspend ( ) et co_await e son résultat. Les types Promise typiques retournent soit std::suspend_always , pour les coroutines à démarrage différé, soit std::suspend_never , pour les coroutines à démarrage immédiat.
  • lorsque co_await promise. initial_suspend ( ) reprend, commence l'exécution du corps de la coroutine.

Quelques exemples de paramètre devenant pendants :

#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} détruit
    h.resume(); // la coroutine reprise exécute std::cout << i, utilise S::i après libération
    h.destroy();
}
coroutine bad2()
{
    S s{0};
    return s.f(); // la coroutine retournée ne peut pas être reprise sans commettre un use after free
}
void bad3()
{
    coroutine h = [i = 0]() -> coroutine // une lambda qui est aussi une coroutine
    {
        std::cout << i;
        co_return;
    }(); // immédiatement invoquée
    // lambda détruite
    h.resume(); // utilise (anonymous lambda type)::i après libération
    h.destroy();
}
void good()
{
    coroutine h = [](int i) -> coroutine // faire de i un paramètre de coroutine
    {
        std::cout << i;
        co_return;
    }(0);
    // lambda détruite
    h.resume(); // pas de problème, i a été copié dans le frame de coroutine
                // en tant que paramètre par valeur
    h.destroy();
}

Lorsqu'une coroutine atteint un point de suspension

  • l'objet de retour obtenu précédemment est renvoyé à l'appelant/reprenant, après conversion implicite vers le type de retour de la coroutine, si nécessaire.

Lorsqu'une coroutine atteint l'instruction co_return , elle exécute les opérations suivantes :

  • appelle promise. return_void ( ) pour
  • co_return ;
  • co_return expr ; expr a le type void
  • ou appelle promise. return_value ( expr ) pour co_return expr ; expr a un type non-void
  • détruit toutes les variables avec durée de stockage automatique dans l'ordre inverse de leur création.
  • appelle promise. final_suspend ( ) et co_await le résultat.

Sortir de la fin de la coroutine équivaut à co_return ; , sauf que le comportement est indéfini si aucune déclaration de return_void n'est trouvée dans la portée de Promise . Une fonction ne contenant aucun mot-clé de définition dans son corps n'est pas une coroutine, quel que soit son type de retour, et sortir de la fin entraîne un comportement indéfini si le type de retour n'est pas (éventuellement qualifié cv) void .

// en supposant que task est un type de tâche de coroutine
task<void> f()
{
    // pas une coroutine, comportement indéfini
}
task<void> g()
{
    co_return;  // OK
}
task<void> h()
{
    co_await g();
    // OK, co_return implicite ;
}

Si la coroutine se termine par une exception non capturée, elle effectue les opérations suivantes :

  • capture l'exception et appelle promise. unhandled_exception ( ) depuis le bloc catch
  • appelle promise. final_suspend ( ) et co_await le résultat (par exemple pour reprendre une continuation ou publier un résultat). Il est comportement indéfini de reprendre une coroutine à partir de ce point.

Lorsque l'état de la coroutine est détruit, soit parce qu'elle s'est terminée via co_return ou une exception non attrapée, soit parce qu'elle a été détruite via son handle, il effectue les opérations suivantes :

  • appelle le destructeur de l'objet promise.
  • appelle les destructeurs des copies des paramètres de fonction.
  • appelle operator delete pour libérer la mémoire utilisée par l'état de la coroutine.
  • retourne l'exécution à l'appelant/reprenant.

Allocation dynamique

L'état de la coroutine est alloué dynamiquement via l'opérateur operator new non-tableau.

Si le type Promise définit un remplacement au niveau de la classe, il sera utilisé, sinon l' operator new global sera utilisé.

Si le type Promise définit une forme de placement de operator new qui prend des paramètres supplémentaires, et qu'ils correspondent à une liste d'arguments où le premier argument est la taille demandée (de type std::size_t ) et le reste sont les arguments de la fonction coroutine, ces arguments seront passés à operator new (cela permet d'utiliser la convention d'allocateur en tête pour les coroutines).

L'appel à operator new peut être optimisé (même si un allocateur personnalisé est utilisé) si

  • La durée de vie de l'état de la coroutine est strictement imbriquée dans la durée de vie de l'appelant, et
  • la taille du frame de coroutine est connue au site d'appel.

Dans ce cas, l'état de la coroutine est intégré dans la pile d'appel de l'appelant (si l'appelant est une fonction ordinaire) ou dans l'état de la coroutine (si l'appelant est une coroutine).

Si l'allocation échoue, la coroutine lance std::bad_alloc , sauf si le type Promise définit la fonction membre Promise :: get_return_object_on_allocation_failure ( ) . Si cette fonction membre est définie, l'allocation utilise la forme nothrow de operator new et en cas d'échec d'allocation, la coroutine retourne immédiatement l'objet obtenu à partir de Promise :: get_return_object_on_allocation_failure ( ) à l'appelant, par exemple :

struct Coroutine::promise_type
{
    /* ... */
    // garantir l'utilisation de l'opérateur new non-lançant
    static Coroutine get_return_object_on_allocation_failure()
    {
        std::cerr << __func__ << '\n';
        throw std::bad_alloc(); // ou, return Coroutine(nullptr);
    }
    // surcharge personnalisée non-lançante de new
    void* operator new(std::size_t n) noexcept
    {
        if (void* mem = std::malloc(n))
            return mem;
        return nullptr; // échec d'allocation
    }
};

Promesse

Le type Promise est déterminé par le compilateur à partir du type de retour de la coroutine en utilisant std::coroutine_traits .

Formellement, soit

  • R et Args... désignent respectivement le type de retour et la liste des types de paramètres d'une coroutine,
  • ClassT désigne le type de classe à laquelle appartient la coroutine si elle est définie comme fonction membre non statique,
  • cv désigne la qualification cv déclarée dans la déclaration de fonction si elle est définie comme fonction membre non statique,

son Promise type est déterminé par :

Par exemple :

Si la coroutine est définie comme ... alors son type Promise est ...
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

L'opérateur unaire co_await suspend une coroutine et retourne le contrôle à l'appelant.

co_await expr

Une expression co_await ne peut apparaître que dans une expression potentiellement évaluée au sein d'un corps de fonction régulier corps de fonction (y compris le corps de fonction d'une expression lambda ), et ne peut pas apparaître

Une expression co_await ne peut pas être une sous-expression potentiellement évaluée du prédicat d'une assertion de contrat .

(depuis C++26)

Premièrement, expr est converti en une expression awaitable comme suit :

  • si expr est produit par un point de suspension initial, un point de suspension final, ou une expression yield, l'awaitable est expr , tel quel.
  • sinon, si le type Promise de la coroutine actuelle possède la fonction membre await_transform , alors l'awaitable est promise. await_transform ( expr ) .
  • sinon, l'awaitable est expr , tel quel.

Ensuite, l'objet awaiter est obtenu, comme suit :

  • si la résolution de surcharge pour operator co_await donne une seule meilleure surcharge, l'awaiter est le résultat de cet appel :
  • awaitable. operator co_await ( ) pour la surcharge de membre,
  • operator co_await ( static_cast < Awaitable && > ( awaitable ) ) pour la surcharge non-membre.
  • sinon, si la résolution de surcharge ne trouve aucun opérateur co_await , l'awaitable est utilisable tel quel.
  • sinon, si la résolution de surcharge est ambiguë, le programme est mal formé.

Si l'expression ci-dessus est une prvalue , l'objet awaiter est un objet temporaire matérialisé à partir de celle-ci. Sinon, si l'expression ci-dessus est une glvalue , l'objet awaiter est l'objet auquel elle se réfère.

Ensuite, awaiter. await_ready ( ) est appelé (ceci est un raccourci pour éviter le coût de la suspension s'il est connu que le résultat est prêt ou peut être complété de manière synchrone). Si son résultat, converti contextuellement en bool est false alors

La coroutine est suspendue (son état de coroutine est peuplé avec les variables locales et le point de suspension actuel).
awaiter. await_suspend ( handle ) est appelée, où handle est le handle de coroutine représentant la coroutine actuelle. À l'intérieur de cette fonction, l'état suspendu de la coroutine est observable via ce handle, et c'est la responsabilité de cette fonction de la programmer pour reprendre sur un exécuteur, ou d'être détruite (retourner false compte comme une programmation)
  • si await_suspend retourne void , le contrôle est immédiatement retourné à l'appelant/reprenant de la coroutine actuelle (cette coroutine reste suspendue), sinon
  • si await_suspend retourne bool ,
  • la valeur true retourne le contrôle à l'appelant/reprenant de la coroutine actuelle
  • la valeur false reprend la coroutine actuelle.
  • si await_suspend retourne un handle de coroutine pour une autre coroutine, ce handle est repris (par un appel à handle. resume ( ) ) (notez que cela peut chaîner pour éventuellement causer la reprise de la coroutine actuelle).
  • si await_suspend lève une exception, l'exception est attrapée, la coroutine est reprise, et l'exception est immédiatement relancée.

Enfin, awaiter. await_resume ( ) est appelé (que la coroutine ait été suspendue ou non), et son résultat est le résultat de l'expression complète co_await expr .

Si la coroutine a été suspendue dans l'expression co_await , et est reprise ultérieurement, le point de reprise se situe immédiatement avant l'appel à awaiter. await_resume ( ) .

Notez que la coroutine est entièrement suspendue avant d'entrer dans awaiter. await_suspend ( ) . Son handle peut être partagé avec un autre thread et repris avant que la await_suspend ( ) ne retourne. (Notez que les règles de sécurité mémoire par défaut s'appliquent toujours, donc si un handle de coroutine est partagé entre threads sans verrou, l'awaiter doit utiliser au moins une sémantique release et le repreneur doit utiliser au moins une sémantique acquire .) Par exemple, le handle de coroutine peut être placé dans un callback, programmé pour s'exécuter sur un pool de threads lorsque l'opération d'I/O asynchrone se termine. Dans ce cas, puisque la coroutine actuelle peut avoir été reprise et ainsi exécuté le destructeur de l'objet awaiter, tout cela concurremment alors que await_suspend ( ) continue son exécution sur le thread actuel, await_suspend ( ) doit considérer * this comme détruit et ne pas y accéder après que le handle a été publié vers d'autres threads.

Exemple

#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(); });
            // Comportement indéfini potentiel : accès à *this potentiellement détruit
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // ceci est correct
        }
        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 détruit ici
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

Sortie possible :

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

Note: l'objet awaiter fait partie de l'état de la coroutine (en tant qu'objet temporaire dont la durée de vie traverse un point de suspension) et est détruit avant que l'expression co_await ne se termine. Il peut être utilisé pour maintenir un état par opération comme requis par certaines APIs d'E/S asynchrones sans recourir à des allocations dynamiques supplémentaires.

La bibliothèque standard définit deux awaitables triviaux : std::suspend_always et std::suspend_never .

Démonstration de promise_type :: await_transform et d'un awaiter fourni par le programme

Exemple

#include <cassert>
#include <coroutine>
#include <iostream>
struct tunable_coro
{
    // Un awaiter dont la « disponibilité » est déterminée via le paramètre du constructeur.
    class tunable_awaiter
    {
        bool ready_;
    public:
        explicit(false) tunable_awaiter(bool ready) : ready_{ready} {}
        // Trois fonctions standard de l'interface 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(); }
        // Une fonction de transformation fournie par l'utilisateur qui retourne l'awaiter personnalisé :
        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); }
    // Pour simplifier, déclarez ces 4 fonctions spéciales comme supprimées :
    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_.fait())
            return;
        handle_.promise().disable_suspension();
        handle_();
    }
    bool operator()()
    {
        if (!handle_.fait())
            handle_();
        return !handle_.fait();
    }
private:
    promise_type::coro_handle handle_;
};
tunable_coro generate(int n)
{
    for (int i{}; i != n; ++i)
    {
        std::cout << i << ' ';
        // L'awaiter passé à co_await va vers promise_type::await_transform qui
        // émet un tunable_awaiter qui initialement provoque une suspension (retour à
        // main à chaque itération), mais après un appel à disable_suspension aucune suspension
        // se produit et la boucle s'exécute jusqu'à sa fin sans retourner à main().
        co_await std::suspend_always{};
    }
}
int main()
{
    auto coro = generate(8);
    coro(); // émet uniquement le premier élément == 0
    for (int k{}; k < 4; ++k)
    {
        coro(); // émet 1 2 3 4, un par chaque itération
        std::cout << ": ";
    }
    coro.disable_suspension();
    coro(); // émet les numéros de queue 5 6 7 tous en une seule fois
}

Sortie :

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

co_yield

co_yield expression retourne une valeur à l'appelant et suspend la coroutine actuelle : c'est l'élément fondamental commun des fonctions génératrices résumables.

co_yield expr
co_yield braced-init-list

C'est équivalent à

co_await promise.yield_value(expr)

Le yield_value typique d'un générateur stockerait (par copie/déplacement ou simplement en conservant l'adresse de, puisque la durée de vie de l'argument dépasse le point de suspension à l'intérieur du co_await ) son argument dans l'objet générateur et retournerait std::suspend_always , transférant le contrôle à l'appelant/reprenant.

#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>
template<typename T>
struct Generator
{
    // Le nom de classe 'Generator' est notre choix et il n'est pas requis pour la coroutine
    // magique. Le compilateur reconnaît une coroutine par la présence du mot-clé 'co_yield'.
    // Vous pouvez utiliser le nom 'MyGenerator' (ou tout autre nom) à la place tant que vous incluez
    // struct imbriqué promise_type avec la méthode 'MyGenerator get_return_object()'.
    // (Note: Il est nécessaire d'ajuster les déclarations des constructeurs et destructeurs
    // lors du renommage.)
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type // requis
    {
        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(); } // sauvegarde
                                                                              // exception
        template<std::convertible_to<T> From> // Concept C++20
        std::suspend_always yield_value(From&& from)
        {
            value_ = std::forward<From>(from); // mise en cache du résultat dans la promesse
            return {};
        }
        void return_void() {}
    };
    handle_type h_;
    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool()
    {
        fill(); // La seule façon fiable de déterminer si nous avons terminé la coroutine ou non,
                // indique s'il y aura une valeur suivante générée (co_yield)
                // dans une coroutine via l'accesseur C++ (opérateur () ci-dessous) est pour exécuter/reprendre
                // coroutine jusqu'au prochain point co_yield (ou la laisser se terminer).
                // Ensuite nous stockons/mettons en cache le résultat dans la promesse pour permettre l'accesseur (opérateur() ci-dessous
                // pour le récupérer sans exécuter la coroutine).
        return !h_.fait();
    }
    T operator()()
    {
        fill();
        full_ = false; // nous allons déplacer notre cache précédent
                       // résultat pour rendre la promesse vide à nouveau
        return std::move(h_.promise().value_);
    }
private:
    bool full_ = false;
    void fill()
    {
        if (!full_)
        {
            h_();
            if (h_.promise().exception_)
                std::rethrow_exception(h_.promise().exception_);
            // propager l'exception de la coroutine dans le contexte appelé
            full_ = true;
        }
    }
};
Generator<std::uint64_t>
fibonacci_sequence(unsigned n)
{
    if (n == 0)
        co_return;
    if (n > 94)
        throw std::runtime_error("Séquence de Fibonacci trop grande. Les éléments déborderaient.");
    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); // max 94 avant que uint64_t ne déborde
        for (int j = 0; gen; ++j)
            std::cout << "fib(" << j << ")=" << gen() << '\n';
    }
    catch (const std::exception& ex)
    {
        std::cerr << "Exception: " << ex.what() << '\n';
    }
    catch (...)
    {
        std::cerr << "Exception inconnue.\n";
    }
}

Sortie :

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

Notes

Macro de test de fonctionnalité Valeur Norme Fonctionnalité
__cpp_impl_coroutine 201902L (C++20) Coroutines (support du compilateur)
__cpp_lib_coroutine 201902L (C++20) Coroutines (support de la bibliothèque)
__cpp_lib_generator 202207L (C++23) std::generator : générateur synchrone de coroutines pour les ranges

Mots-clés

co_await , co_return , co_yield

Support de la bibliothèque

Bibliothèque de support pour les coroutines définit plusieurs types fournissant un support à la compilation et à l'exécution pour les coroutines.

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement publié Comportement corrigé
CWG 2556 C++20 un return_void invalide rendait le comportement
de la fin de la coroutine indéfini
le programme est incorrect
dans ce cas
CWG 2668 C++20 co_await ne pouvait pas apparaître dans les expressions lambda autorisé
CWG 2754 C++23 * this était pris lors de la construction de l'objet
promise pour les fonctions membres à objet explicite
* this n'est pas
pris dans ce cas

Voir aussi

(C++23)
Une view qui représente un générateur synchrone de coroutine
(modèle de classe)

Liens externes

1. Lewis Baker, 2017-2022 - Transfert Asymétrique.
2. David Mazières, 2021 - Tutoriel sur les coroutines C++20.
3. Chuanqi Xu & Yu Qi & Yao Han, 2021 - Principes et Applications des Coroutines C++20. (Chinois)
4. Simon Tatham, 2023 - Écriture de systèmes de coroutines C++20 personnalisés.