8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++Advent Calendar 2021

Day 12

[コルーチン]operator co_await と await_transform

Last updated at Posted at 2021-12-12

はじめに

コルーチンの中断のカスタマイズポイントとして

  • operator co_await
  • await_transform

の二つがあります。
コルーチンライブラリを作る際には都合のいいほうを選んで使えばよいと思いますが、これら2つを改めて比べてみました。

ただの雑談:
MSVCがバグってて再開時に値を返すようなAwaiterをawait_transformで実装したら何故か致命的なコンパイルエラーでコンパイルできないケースがあったが試しにoperator co_awaitで実装しなおしたらコンパイルが通ったこともあった
コンパイラのバグは引きたくないですね

operator co_await

任意の型をAwaiterへ変換する

基本形

auto operator co_await(const Type&)
{
    return awaiter{};
} 

メンバ関数でもOK

struct Type
{
     auto operator co_await() const
     {
         return awaiter{};
     } 
};

await_transform

Promise側から指定する形で任意の型をAwaiterに変換する

基本形

struct promise_type
{
    // 他のメソッド省略…

    auto await_transform(const Type&) const
    {
        return awaiter{};
    }
};

なお、
operator co_awaitawait_transform両方ある場合は、
await_transformが優先される

どちらを使うのが良さそうか

  • AwaiterがPromiseに全く依存しない場合
    operator co_await
    (await_transformで同じ実装を何度もする必要がなくなるので)

  • Promiseごとに変換するAwaiterが型レベルで異なる
    await_transform
    (operator co_awaitの定義をPromiseごとに複数定義はできないので当然)

  • それ以外
    → 都合のいいほうを使う

個人的には、Promiseに依存するようなAwaiterは、
なんの問題もなければawait_transformを使っておくのがベターそうに思う

実装の切り替え

operator co_await から await_transform

Awaiterが依存するPromiseごとにawait_transformを実装するだけ

(この時await_suspendの仮引数がstd::coroutine_handle<>で済むようなケースはそもそもPromiseに依存していないのでawait_transformを使うべきではなさそう)

auto operator co_await(const Type&)
{
    // something
    
    struct awaiter {
        bool await_ready() const noexcept { return true;}
        
        void await_suspend(std::coroutine_handle<promise_type>)
        {
            // something with promise         
        }
        void await_suspend(std::coroutine_handle<promise_type2>)
        {
            // something with promise2         
        }

        void await_resume() {}        
    };
    return awaiter{}; 
}

変換後

struct awaiter
{
    bool await_ready() const noexcept { return true;}
    
    void await_suspend(std::coroutine_handle<promise> h)
    {
        // something with promise         
    }
    void await_suspend(std::coroutine_handle<promise2> h)
    {
        // something with promise2         
    }

    void await_resume() {}        
};
struct promise_type
{
    // 他のメソッド省略…

    auto await_transform(const Type&) const
    {
        // something
        return awaiter{};
    }
};
struct promise_type2
{
    // 他のメソッド省略…

    auto await_transform(const Type&) const
    {
        // something
        return awaiter{};
    }
};

await_transform から operator co_await

こちらは処理次第ではそのまま移植できないこともあるので注意がいる

たとえば、await_transformの動作やAwaiterの状態が、Promiseに依存してしまう以下のようなケース

struct promise_type
{
    auto await_transform(const Type& v)
    {
        // Promiseの状態に依存して、動作が変わる
        bool ready = this->something(v);
            
        struct awaiter {
            bool ready{};
            
		    bool await_ready() const noexcept
		    {
		    	return ready;
		    }

		    void await_suspend(std::coroutine_handle<>) {}
		    void await_resume() {}        
        };
        // Awaiterの状態がPromiseに依存
        return awaiter{ready}; 
    }
};

operator co_awaitでは任意のPromiseの状態にアクセスできないので、同じように実装しようにも上手くいかない。

この場合はもろもろの動作タイミングをawait_suspend時に遅延されるなどの工夫がいる

auto operator co_await(const Type& v)
{
    struct awaiter {
        Type v; // もしPromiseの状態と複合して絡むならフィールドにもつしかない

	    bool await_ready() const noexcept
	    {
            // await_suspendに流れるように中断させる(false)
	    	return false;
	    }

	    bool await_suspend(std::coroutine_handle<promise_type> h)
        {
            // もろもろの動作をawait_suspendに遅延させる
            bool ready = h.promise().something(v);
            return not ready;
        }
	    void await_resume() {}
    };
    return awaiter{v}; 
}

await_suspendはboolの返り値をもつこともできる
この際に、trueを返せば中断、falseを返せばこのコルーチンを再開させることになります。

上記の例のようにPromiseに依存してawait_readyの結果が変わるような場合は代わりにawait_suspend時点で判断する必要があります。
この時に、await_readyで返す値と反転することに注意がいります
(await_readyはfalseを返すと中断)

    return not ready;

まとめ

  • コルーチン中断のカスタマイズポイントは2つある
    • operator co_await
      • Promiseに依存することなく任意の型をAwaiterに変換
      • Promiseに依存しないケースならこちらを使うのが良さそう
    • await_transform
      • Promiseごとに指定する形で任意の型をAwaiterに変換
      • Promiseに依存するケースならこちらを使うのが良さそう
  • どちらも実装されている場合はawait_transformが優先
  • 相互変換は基本可能そうだが
    • operator co_awaitからawait_transformは、そのまま移植可能
    • await_transformからoperator co_awaitは、そのまま移植できないケースがありうるので工夫が必要
8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?