๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

C++ A Generator Coroutine

A coroutine is a function that can pause in the middle and resume later. A generator is a coroutine that hands out one value each time it pauses.

A generator of squares

The squares function pauses at each co_yield and hands the value out. final_suspend is the pause the coroutine takes when its body ends. Its word noexcept promises this step never throws, and the promise gets its own lesson later.

#include <coroutine>
#include <iostream>

class Generator {
public:
  struct promise_type {
    int current;
    Generator get_return_object() {
      return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_always initial_suspend() {
      return {};
    }
    std::suspend_always final_suspend() noexcept {
      return {};
    }
    std::suspend_always yield_value(int value) {
      current = value;
      return {};
    }
    void return_void() {
    }
    void unhandled_exception() {
    }
  };
  explicit Generator(std::coroutine_handle<promise_type> handle) {
    h = handle;
  }
  ~Generator() {
    h.destroy();
  }
  bool next() {
    h.resume();
    return !h.done();
  }
  int value() {
    return h.promise().current;
  }

private:
  std::coroutine_handle<promise_type> h;
};

Generator squares() {
  for (int i = 1; i <= 10; i++) {
    co_yield i * i;
  }
}

int main() {
  Generator gen = squares();
  for (int k = 0; k < 4; k++) {
    gen.next();
    std::cout << gen.value() << std::endl;
  }
  return 0;
}

Nothing runs until asked

Making the generator runs none of its body. The body starts only when main calls next(), so the Created line prints first.

#include <coroutine>
#include <iostream>

class Generator {
public:
  struct promise_type {
    int current;
    Generator get_return_object() {
      return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_always initial_suspend() {
      return {};
    }
    std::suspend_always final_suspend() noexcept {
      return {};
    }
    std::suspend_always yield_value(int value) {
      current = value;
      return {};
    }
    void return_void() {
    }
    void unhandled_exception() {
    }
  };
  explicit Generator(std::coroutine_handle<promise_type> handle) {
    h = handle;
  }
  ~Generator() {
    h.destroy();
  }
  bool next() {
    h.resume();
    return !h.done();
  }
  int value() {
    return h.promise().current;
  }

private:
  std::coroutine_handle<promise_type> h;
};

Generator steps() {
  std::cout << "Body started" << std::endl;
  co_yield 10;
  co_yield 20;
}

int main() {
  Generator gen = steps();
  std::cout << "Created" << std::endl;
  gen.next();
  std::cout << "First: " << gen.value() << std::endl;
  return 0;
}

A generator ends

next() returns false once the body has finished. The loop stops on that answer, and main prints the verdict.

#include <coroutine>
#include <iostream>

class Generator {
public:
  struct promise_type {
    int current;
    Generator get_return_object() {
      return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_always initial_suspend() {
      return {};
    }
    std::suspend_always final_suspend() noexcept {
      return {};
    }
    std::suspend_always yield_value(int value) {
      current = value;
      return {};
    }
    void return_void() {
    }
    void unhandled_exception() {
    }
  };
  explicit Generator(std::coroutine_handle<promise_type> handle) {
    h = handle;
  }
  ~Generator() {
    h.destroy();
  }
  bool next() {
    h.resume();
    return !h.done();
  }
  int value() {
    return h.promise().current;
  }

private:
  std::coroutine_handle<promise_type> h;
};

Generator threeSteps() {
  co_yield 10;
  co_yield 20;
  co_yield 30;
}

int main() {
  Generator gen = threeSteps();
  bool has = gen.next();
  while (has) {
    std::cout << gen.value() << std::endl;
    has = gen.next();
  }
  std::cout << std::boolalpha << "Has more: " << has << std::endl;
  std::cout << "Done" << std::endl;
  return 0;
}

Example

#include <coroutine>
#include <iostream>

class Generator {
public:
  struct promise_type {
    int current;
    Generator get_return_object() {
      return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_always initial_suspend() {
      return {};
    }
    std::suspend_always final_suspend() noexcept {
      return {};
    }
    std::suspend_always yield_value(int value) {
      current = value;
      return {};
    }
    void return_void() {
    }
    void unhandled_exception() {
    }
  };
  explicit Generator(std::coroutine_handle<promise_type> handle) {
    h = handle;
  }
  ~Generator() {
    h.destroy();
  }
  bool next() {
    h.resume();
    return !h.done();
  }
  int value() {
    return h.promise().current;
  }

private:
  std::coroutine_handle<promise_type> h;
};

Generator countdown() {
  for (int i = 5; i >= 1; i--) {
    co_yield i;
  }
}

int main() {
  Generator gen = countdown();
  while (gen.next()) {
    std::cout << gen.value() << std::endl;
  }
  return 0;
}

Key points

  • Use co_yield to hand a value to the caller and pause the coroutine.
  • A coroutine handle is the remote control for a paused coroutine.
  • Calling resume() on the handle runs the body until the next pause.
  • initial_suspend makes the body wait until the first resume call.
  • The wrapper owns the handle and destroys it in its destructor.

In this example, the countdown generator yields 5 and pauses. Each next() call resumes it for one more number. The loop ends when next() reports the body is finished, right after the 1.

Note

A paused coroutine keeps its place and its local values until it resumes.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

When does the body of a generator start running?