yohhoyの日記

技術的メモをしていきたい日記

yield式を使わないジェネレータ

C++2a(C++20)コルーチンにはジェネレータ実装を容易にするco_yield式が導入されるが、動作仕様的にはco_await式のシンタックスシュガーとなっている。

#include <coroutine>
#include <iostream>
#include <utility>

#define MIMIC_CO_YIELD 1

#if MIMIC_CO_YIELD
// yield式相当を表現する値保持クラス
template <typename T> struct yield { T value; };
#endif

template <typename T>
struct generator {
  struct promise_type {
    T value_;
    auto get_return_object() { return generator(*this); }
    auto initial_suspend() noexcept { return std::suspend_always{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
#if MIMIC_CO_YIELD
    // 式co_await yield{v}に対応するカスタイマイズポイント
    auto await_transform(yield<T>&& bag) {
      value_ = std::move(bag.value);
      return std::suspend_always{};
    }
#else
    auto yield_value(T x) {
      value_ = std::move(x);
      return std::suspend_always{};
    }
#endif
    void return_void() {}
    void unhandled_exception() { throw; }
  };
  using coro_handle = std::coroutine_handle<promise_type>;

  generator(generator const&) = delete;
  generator(generator&& rhs) : coro_(rhs.coro_) { rhs.coro_ = nullptr; }
  ~generator() { if (coro_) coro_.destroy(); }
 
  bool next() { return coro_ ? (coro_.resume(), !coro_.done()) : false; }
  T& value() { return coro_.promise().value_; }

private:
  generator(promise_type& p)
    : coro_(coro_handle::from_promise(p)) {}
  coro_handle coro_;
};

generator<int> f()
{
#if MIMIC_CO_YIELD
  // co_await式を利用
  co_await yield{1};
  co_await yield{2};
#else
  // 通常のco_yield式
  co_yield 1;
  co_yield 2;
#endif
}

int main() {
  auto g = f();
  while (g.next()) {
    std::cout << g.value() << std::endl;
  }
}

2022-01-13追記:co_awaitco_yieldでは演算子の優先順位が異なるため*1、いつでも両者を相互変換できるわけではない。co_awaitは単項演算子と同じく優先順位が高く、co_yieldは代入演算子と同じく優先順位が低い。*2

C++2a WD(n4861) 7.6.17/p1より一部引用。

A yield-expression shall appear only within a suspension context of a function (7.6.2.3). Let e be the operand of the yield-expression and p be an lvalue naming the promise object of the enclosing coroutine (9.5.4), then the yield-expression is equivalent to the expression co_await p.yield_value(e).
(snip)

関連URL