Back to TILs

C++ coroutine — 1

Date: 2023-03-20Last modified: 2023-03-30

Table of contents

Introduction

Parts

Awaiter methods

struct My_Awaitable {
  auto operator co_await() {
    return std::suspend_always{};
  }
};

Function

Coroutines

When paused the function state is save on heap topically.

Lazy computation

auto f = some_coroutine(); // store computation info in f
// Use f to manipulate the computation

Keywords

C++ 20 introduces three keywords that help pause and resume coroutines

If a function has one of those keywords, it becomes a coroutine. There is no other special syntax for coroutines.

Which function can be a coroutine

It’s not every function in C++ that can be a coroutine. The functions below can’t be coroutines.

C++20 warnings

C++ 20 doesn’t provide actual usable coroutine types like Corolype

It provides the low level infrastructure to build them (promises, awaitables, coroutine handles,…

Building your own coroutine types is not recommended. It’s only reserved for hard core, highly experienced library developers who really know what they’re doing

It is expected that C++23 will provide high level coroutine types built into C++, ready to use just by including some headers

If you want to use them know, there are third party libraries that can help, like cppcoro and some others

See Also

#include <coroutine>
#include <iostream>

// Absolute minimum to satisfy the compiler
struct CoroType {
  struct promise_type {
    CoroType get_return_object() { return CoroType(this); }
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }

    void unhandled_exception() noexcept {
      std::rethrow_exception(std::current_exception());
    }
    void return_void(){};
  };

  CoroType(promise_type* p)
      : m_handle(std::coroutine_handle<promise_type>::from_promise(*p)) {}
  ~CoroType() {
    std::cout << "Handle destroyed..." << std::endl;
    m_handle.destroy();
  }
  std::coroutine_handle<promise_type> m_handle;
};

CoroType do_work() {
  std::cout << "Doing first thing... " << std::endl;
  co_await std::suspend_always{};
  std::cout << "Doing second thing..." << std::endl;
  co_await std::suspend_always{};
  std::cout << "Doing Third thing..." << std::endl;
}

int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
  auto task = do_work();

  // Resume
  task.m_handle(); // Doing first thing... 
  std::cout << std::boolalpha;
  std::cout << "coro done : " << task.m_handle.done() << std::endl;  // false

  // Resume for second time
  task.m_handle.resume(); // Doing second thing...
  std::cout << "coro done : " << task.m_handle.done() << std::endl;  // false

  // Resume for third time
  task.m_handle.resume(); // Doing Third thing...
  std::cout << "coro done : " << task.m_handle.done() << std::endl;  // true

  // Resuming after coroutine has run to completion : BAD!
  // std::cout << "------" << std::endl;
  // task.m_handle.resume();

  std::cout << "Done!" << std::endl;

  return 0;
}

Possible output

Doing first thing... 
coro done : false
Doing second thing...
coro done : false
Doing Third thing...
coro done : true
Done!
Handle destroyed...

References