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

コルーチンは、実行を一時停止して後で再開できる関数です。コルーチンはスタックレスです:実行を一時停止するために呼び出し元に戻り、実行を再開するために必要なデータはスタックとは別に保存されます。これにより、(明示的なコールバックなしで非ブロッキングI/Oを処理するなど)非同期に実行される逐次的なコードが可能になり、遅延評価される無限シーケンスに対するアルゴリズムやその他の用途もサポートします。

関数は、その定義に以下のいずれかが含まれている場合、コルーチンとなります:

  • the co_await 式 — 再開されるまで実行を中断する
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));
    }
}
  • the co_yield expression — 値を返して実行を中断する
generator<unsigned int> iota(unsigned int n = 0)
{
    while (true)
        co_yield n++;
}
  • the co_return statement — 値を返して実行を完了する
lazy<int> f()
{
    co_return 7;
}

すべてのコルーチンは、以下に示すいくつかの要件を満たす戻り値の型を持たなければなりません。

目次

制限事項

コルーチンは 可変引数 、通常の return文 、または プレースホルダー戻り値型 ( auto または Concept ) を使用できません。

Consteval関数 constexpr関数 コンストラクタ デストラクタ 、および main関数 はコルーチンにできません。

実行

各コルーチンは関連付けられています

  • promise オブジェクト - コルーチン内部から操作されるオブジェクト。コルーチンはこのオブジェクトを通じて結果または例外を送出する。promise オブジェクトは std::promise とは一切関係ない。
  • コルーチンハンドル - コルーチン外部から操作される非所有ハンドル。コルーチンの実行再開やコルーチンフレームの破棄に使用される。
  • コルーチン状態 - 内部的な動的確保ストレージ(最適化により確保が省略される場合を除く)であり、以下のものを含むオブジェクト
  • promiseオブジェクト
  • パラメータ(すべて値でコピーされる)
  • 現在のサスペンションポイントの何らかの表現。これにより、resumeは継続する場所を認識し、destroyはスコープ内のローカル変数を把握できる
  • 現在のサスペンションポイントをまたいで生存期間が続くローカル変数と一時オブジェクト

コルーチンが実行を開始すると、以下の処理を実行します:

  • operator new を使用して コルーチン状態オブジェクトを動的に確保します。
  • すべての関数パラメータをコルーチン状態にコピーします:値渡しパラメータは移動またはコピーされ、参照渡しパラメータは参照のまま維持されます(したがって、参照先オブジェクトの寿命が終了した後にコルーチンが再開されると、ダングリング参照になる可能性があります — 例については後述)。
  • promiseオブジェクトのコンストラクタを呼び出します。promise型がすべてのコルーチンパラメータを受け取るコンストラクタを持つ場合、そのコンストラクタがコピー後のコルーチン引数で呼び出されます。それ以外の場合はデフォルトコンストラクタが呼び出されます。
  • promise. get_return_object ( ) を呼び出し、結果をローカル変数に保持します。この呼び出しの結果は、コルーチンが最初に停止したときに呼び出し元に返されます。このステップまでにスローされた例外はすべて、promiseに配置されず、呼び出し元に伝播します。
  • promise. initial_suspend ( ) を呼び出し、その結果を co_await します。一般的な Promise 型は、遅延開始コルーチンの場合は std::suspend_always を、即時開始コルーチンの場合は std::suspend_never を返します。
  • co_await promise. initial_suspend ( ) が再開されると、コルーチンの本体の実行を開始します。

パラメータがダングリング状態になる例のいくつか:

#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} 破棄済み
    h.resume(); // 再開されたコルーチンは std::cout << i を実行、解放後の S::i を使用
    h.destroy();
}
coroutine bad2()
{
    S s{0};
    return s.f(); // 返されたコルーチンは解放後の使用を犯さずに再開できない
}
void bad3()
{
    coroutine h = [i = 0]() -> coroutine // コルーチンでもあるラムダ
    {
        std::cout << i;
        co_return;
    }(); // 即時呼び出し
    // ラムダ破棄済み
    h.resume(); // 解放後の(匿名ラムダ型)::i を使用
    h.destroy();
}
void good()
{
    coroutine h = [](int i) -> coroutine // i をコルーチンパラメータにする
    {
        std::cout << i;
        co_return;
    }(0);
    // ラムダ破棄済み
    h.resume(); // 問題なし、i は値渡しパラメータとしてコルーチンフレームにコピー済み
                // フレームは値渡しパラメータとして
    h.destroy();
}

コルーチンが中断ポイントに到達したとき

  • 必要に応じて、コルーチンの戻り値の型への暗黙的な変換が行われた後、以前に取得した戻り値オブジェクトが呼び出し元/再開元に返されます。

コルーチンが co_return 文に到達した場合、以下の処理を実行します:

  • calls promise. return_void ( ) を呼び出します
  • co_return ;
  • co_return expr ; ここで expr void 型を持つ
  • promise. return_value ( expr ) を呼び出す( co_return expr ; の場合。ここで expr は非void型を持つ)
  • 自動ストレージ期間を持つすべての変数を、作成された逆順で破棄する
  • promise. final_suspend ( ) を呼び出し、その結果を co_await する

コルーチンの末尾から抜け出すことは、 co_return ; と同等であるが、 Promise のスコープ内で return_void の宣言が見つからない場合、動作は未定義となる。関数本体に関連するキーワードが一切含まれない関数は、その戻り値の型に関わらずコルーチンではなく、末尾から抜け出した場合の動作は、戻り値の型が(CV修飾された可能性のある) void でない場合は未定義となる。

// taskが何らかのコルーチンタスク型であると仮定
task<void> f()
{
    // コルーチンではないため、未定義動作
}
task<void> g()
{
    co_return;  // OK
}
task<void> h()
{
    co_await g();
    // OK、暗黙的なco_return
}

コルーチンが捕捉されない例外で終了した場合、以下の処理を実行します:

  • 例外をキャッチし、catchブロック内から promise. unhandled_exception ( ) を呼び出す
  • promise. final_suspend ( ) を呼び出し、結果を co_await する(例:継続を再開する、または結果を公開する)。この時点からコルーチンを再開することは未定義動作である。

コルーチンの状態が破棄されるとき、それが co_return による終了、捕捉されなかった例外、またはハンドルを通じた破棄によって発生した場合、以下の処理を実行します:

  • promiseオブジェクトのデストラクタを呼び出します。
  • 関数パラメータのコピーのデストラクタを呼び出します。
  • coroutine状態で使用されていたメモリを解放するために operator delete を呼び出します。
  • 実行を呼び出し元/再開元に戻します。

動的割り当て

コルーチンの状態は非配列の operator new によって動的に割り当てられます。

Promise 型がクラスレベルの置換を定義している場合、それが使用されます。それ以外の場合はグローバルな operator new が使用されます。

Promise 型が追加のパラメータを受け取る配置形式の operator new を定義しており、それらが最初の引数が要求サイズ(型 std::size_t )で残りがコルーチン関数の引数である引数リストと一致する場合、それらの引数は operator new に渡されます(これによりコルーチンで 先頭アロケータ規約 を使用することが可能になります)。

operator new の呼び出しは、以下の場合に最適化されて除去される可能性があります(カスタムアロケータが使用されている場合でも):

  • コルーチン状態の生存期間は呼び出し元の生存期間に厳密にネストされており、
  • コルーチンフレームのサイズは呼び出し元で既知です。

その場合、コルーチンの状態は呼び出し元のスタックフレーム(呼び出し元が通常の関数の場合)またはコルーチン状態(呼び出し元がコルーチンの場合)に埋め込まれます。

メモリ確保が失敗した場合、コルーチンは std::bad_alloc を送出します。ただし、 Promise 型がメンバー関数 Promise :: get_return_object_on_allocation_failure ( ) を定義している場合は例外です。このメンバー関数が定義されている場合、メモリ確保は operator new のnothrow形式を使用し、メモリ確保が失敗した場合、コルーチンは直ちに Promise :: get_return_object_on_allocation_failure ( ) から取得したオブジェクトを呼び出し元に返します。例:

struct Coroutine::promise_type
{
    /* ... */
    // 例外を投げないoperator-newの使用を保証
    static Coroutine get_return_object_on_allocation_failure()
    {
        std::cerr << __func__ << '\n';
        throw std::bad_alloc(); // または、return Coroutine(nullptr);
    }
    // カスタムの例外を投げないnewオーバーロード
    void* operator new(std::size_t n) noexcept
    {
        if (void* mem = std::malloc(n))
            return mem;
        return nullptr; // アロケーション失敗
    }
};

Promise

Promise 型は、コルーチンの戻り値型からコンパイラによって決定され、 std::coroutine_traits を使用します。

形式的には、以下を定義する

  • R および Args... はそれぞれコルーチンの戻り値型とパラメータ型リストを示します、
  • ClassT は非静的メンバ関数として定義されている場合のコルーチンが属するクラス型を示します、
  • cv は非静的メンバ関数として定義されている場合の 関数宣言 で宣言されたCV修飾を示します、

its Promise typeは以下によって決定されます:

例:

コルーチンが以下のように定義されている場合... その Promise 型は...
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

単項演算子 co_await はコルーチンを中断し、呼び出し元に制御を返します。

co_await

co_await 式は、通常の 評価される可能性のある式 内でのみ、通常の 関数本体 ラムダ式 の関数本体を含む)内に現れることができ、以下の場合には現れることはできません

co_await 式は、 潜在的に評価される 部分式として 契約表明 の述語内に現れることはできない。

(C++26以降)

まず最初に、 expr は以下のようにawaitableに変換されます:

  • expr が初期サスペンドポイント、最終サスペンドポイント、またはyield式によって生成された場合、awaitableは変更なしで expr となる。
  • それ以外の場合、現在のコルーチンの Promise 型がメンバ関数 await_transform を持つならば、awaitableは promise. await_transform ( expr ) となる。
  • それ以外の場合、awaitableは変更なしで expr となる。

次に、awaiterオブジェクトを以下のように取得します:

  • operator co_await に対するオーバーロード解決が単一の最適なオーバーロードを返す場合、awaiterはその呼び出しの結果となります:
  • awaitable. operator co_await ( ) メンバーオーバーロードの場合、
  • operator co_await ( static_cast < Awaitable && > ( awaitable ) ) 非メンバーオーバーロードの場合。
  • それ以外の場合、オーバーロード解決が演算子 co_await を見つけられない場合、待機可能オブジェクトはそのままアウェイターとなります。
  • それ以外の場合、オーバーロード解決が曖昧な場合、プログラムは不適格となります。

上記の式が prvalue の場合、awaiterオブジェクトはそこから materialized された一時オブジェクトです。それ以外の場合、上記の式が glvalue の場合、awaiterオブジェクトはそれが参照するオブジェクトです。

その後、 awaiter. await_ready ( ) が呼び出されます(これは、結果が準備完了しているか同期的に完了できることが既知の場合、サスペンションのコストを回避するためのショートカットです)。その結果が bool への文脈的変換で false の場合、

コルーチンは中断されます(そのコルーチン状態にはローカル変数と現在の中断ポイントが保存されます)。
awaiter. await_suspend ( handle ) が呼び出されます。ここでhandleは現在のコルーチンを表すコルーチンハンドルです。この関数内では、中断されたコルーチン状態はそのハンドルを通じて観察可能であり、この関数の責任は、何らかのエグゼキューターで再開するようにスケジュールするか、破棄することです(falseを返すことはスケジュールとしてカウントされます)
  • await_suspend void を返す場合、制御は直ちに現在のコルーチンの呼び出し元/再開元に戻ります(このコルーチンは中断されたままです)。それ以外の場合
  • await_suspend bool を返す場合、
  • true は制御を現在のコルーチンの呼び出し元/再開元に戻します
  • false は現在のコルーチンを再開します
  • await_suspend が他のコルーチンのコルーチンハンドルを返す場合、そのハンドルが再開されます( handle. resume ( ) の呼び出しによって)(これは連鎖的に最終的に現在のコルーチンが再開される原因となる可能性があります)
  • await_suspend が例外をスローした場合、例外は捕捉され、コルーチンは再開され、例外は直ちに再スローされます

最後に、 awaiter. await_resume ( ) が呼び出され(コルーチンが中断されたかどうかに関わらず)、その結果が全体の co_await expr 式の結果となります。

コルーチンが co_await 式で中断され、後で再開された場合、再開ポイントは awaiter. await_resume ( ) の呼び出し直前に位置します。

コルーチンは awaiter. await_suspend ( ) に入る前に完全に中断されていることに注意してください。そのハンドルは他のスレッドと共有され、 await_suspend ( ) 関数が戻る前に再開される可能性があります。(デフォルトのメモリ安全性ルールが依然として適用されるため、ロックなしでコルーチンハンドルがスレッド間で共有される場合、awaiterは少なくとも release semantics を使用し、再開側は少なくとも acquire semantics を使用する必要があります。)例えば、コルーチンハンドルをコールバック内に配置し、非同期I/O操作が完了したときにスレッドプールで実行されるようにスケジュールすることができます。その場合、現在のコルーチンが既に再開され、それに伴ってawaiterオブジェクトのデストラクタが実行されている可能性があるため、 await_suspend ( ) が現在のスレッドで実行を継続している間の全ての並行処理において、 await_suspend ( ) * this が破棄されたものとして扱い、ハンドルが他のスレッドに公開された後はこれにアクセスすべきではありません。

#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(); });
            // 未定義動作の可能性: 破棄された可能性のある*thisへのアクセス
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // こちらは問題ありません
        }
        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はここで破棄されます
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

出力例:

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

注意: awaiterオブジェクトはコルーチンの状態の一部であり(サスペンションポイントを跨ぐ一時オブジェクトとして)、 co_await 式が完了する前に破棄されます。これは、追加的な動的メモリ確保に頼ることなく、一部の非同期I/O APIで必要とされる操作ごとの状態を維持するために使用できます。

標準ライブラリは2つの自明なアウェイタブルを定義しています: std::suspend_always std::suspend_never

promise_type promise_type :: await_transform およびプログラム提供awaiterのデモ

#include <cassert>
#include <coroutine>
#include <iostream>
struct tunable_coro
{
    // コンストラクタのパラメータによって「準備完了」状態が決定されるawaiter。
    class tunable_awaiter
    {
        bool ready_;
    public:
        explicit(false) tunable_awaiter(bool ready) : ready_{ready} {}
        // 3つの標準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(); }
        // ユーザーが提供する変換関数。カスタムawaiterを返す:
        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); }
    // 簡潔さのため、これら4つの特殊関数を削除済みとして宣言する:
    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 << ' ';
        // co_awaitに渡されたawaiterはpromise_type::await_transformに渡され、
        // 初期状態で中断を引き起こすtunable_awaiterの問題(呼び出し元に戻る)
        // 各イテレーションでmainが実行されますが、disable_suspension呼び出し後はサスペンションは発生しません
        // 発生し、ループが終了まで実行され、main() に戻らない。
        co_await std::suspend_always{};
    }
}
int main()
{
    auto coro = generate(8);
    coro(); // 最初の要素 == 0 のみを出力する
    for (int k{}; k < 4; ++k)
    {
        coro(); // 1 2 3 4 を各反復ごとに1つずつ出力する
        std::cout << ": ";
    }
    coro.disable_suspension();
    coro(); // 末尾の番号 5 6 7 を一度に出力する
}

出力:

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

co_yield

co_yield 式は呼び出し元に値を返し、現在のコルーチンを中断します:これは再開可能なジェネレータ関数の基本的な構成要素です。

co_yield
co_yield 波括弧初期化リスト

これは以下と同等です

co_await promise.yield_value(expr)

典型的なジェネレータの yield_value は、その引数を(引数の寿命が co_await 内部のサスペンションポイントを跨ぐため、コピー/ムーブするか単にアドレスを保存して)ジェネレータオブジェクトに格納し、 std::suspend_always を返して、呼び出し元/再開元に制御を移します。

#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>
template<typename T>
struct Generator
{
    // クラス名 'Generator' は任意の選択であり、コルーチンに必須ではありません
    // マジック。コンパイラは 'co_yield' キーワードの存在によってコルーチンを認識します。
    // 'MyGenerator'(または任意の他の名前)という名前を使用できます。ただし、含める限り
    // 'MyGenerator get_return_object()' メソッドを持つネストされた構造体 promise_type。
    // (注: コンストラクタとデストラクタの宣言を調整する必要があります
    // リネーム時に使用する。
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type // required
    {
        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(); } // 保存
                                                                              // 例外
        template<std::convertible_to<T> From> // C++20 concept
        std::suspend_always yield_value(From&& from)
        {
            value_ = std::forward<From>(from); // 結果をpromiseにキャッシュ
            return {};
        }
        void return_void() {}
    };
    handle_type h_;
    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool()
    {
        fill(); // コルーチンが完了したかどうかを確実に判断する唯一の方法は、
                // 次の値が生成されるかどうか (co_yield)
                // C++ゲッター(下記のoperator ())を介したコルーチンでの実行/再開
                // 次のco_yieldポイントまでコルーチンを継続(または終了まで実行)。
                // 次に、結果をpromiseに保存/キャッシュして、ゲッター(下記のoperator())でアクセスできるようにします
                // コルーチンを実行せずに取得するため。
        return !h_.done();
    }
    T operator()()
    {
        fill();
        full_ = false; // 以前にキャッシュされたものを移動します
                       // プロミスを再度空にするための結果
        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_);
            // 呼び出し元コンテキストでコルーチン例外を伝播
            full_ = true;
        }
    }
};
Generator<std::uint64_t>
fibonacci_sequence(unsigned n)
{
    if (n == 0)
        co_return;
    if (n > 94)
        throw std::runtime_error("フィボナッチ数列が大きすぎます。要素がオーバーフローします。");
    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); // uint64_t がオーバーフローする前に最大94
        for (int j = 0; gen; ++j)
            std::cout << "fib(" << j << ")=" << gen() << '\n';
    }
    catch (const std::exception& ex)
    {
        std::cerr << "例外: " << ex.what() << '\n';
    }
    catch (...)
    {
        std::cerr << "不明な例外。\n";
    }
}

出力:

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

注記

機能テスト マクロ 標準 機能
__cpp_impl_coroutine 201902L (C++20) コルーチン (コンパイラサポート)
__cpp_lib_coroutine 201902L (C++20) コルーチン (ライブラリサポート)
__cpp_lib_generator 202207L (C++23) std::generator : 範囲のための同期コルーチンジェネレータ

キーワード

co_await co_return co_yield

ライブラリサポート

コルーチンサポートライブラリ は、コルーチンに対するコンパイル時および実行時サポートを提供するいくつかの型を定義します。

不具合報告

以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。

DR 適用対象 公開時の動作 正しい動作
CWG 2556 C++20 無効な return_void によりコルーチンの終端からの
フォールスルー動作が未定義となっていた
この場合プログラムは
不適格となる
CWG 2668 C++20 co_await がラムダ式内で使用できなかった 許可される
CWG 2754 C++23 明示的オブジェクトメンバー関数のpromiseオブジェクト
構築時に * this が取得されていた
この場合 * this
取得されない

関連項目

(C++23)
同期 view を表す coroutine ジェネレータ
(クラステンプレート)

外部リンク

1. Lewis Baker, 2017-2022 - 非対称転送。
2. David Mazières, 2021 - C++20コルーチンに関するチュートリアル。
3. Chuanqi Xu & Yu Qi & Yao Han, 2021 - C++20コルーチンの原理と応用。(中国語)
4. Simon Tatham, 2023 - カスタムC++20コルーチンシステムの作成。