Back to TILs

C++ coroutine — 3

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>

struct CoroType {
  struct promise_type {
    int m_value;
    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());
    }
    std::suspend_always yield_value(int val) {
      m_value = val;
      return {};
    }
    void return_value(int val) {
      std::cout << "Returning value " << std::endl;
      m_value = val;
    }

    // void return_void() { std::cout << "Returning void..." << std::endl; }
  };
  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 << "Starting the coroutine..." << std::endl;
  co_yield 1;
  co_yield 2;
  co_yield 3;
  co_return 4;
}

int main() {
  auto task = do_work();

  task.m_handle();  // This resumes the couroutine. When next suspension point
                    // is hit it pauses
  std::cout << "value : " << task.m_handle.promise().m_value << std::endl;

  task.m_handle();  // This resumes the couroutine. When next suspension point
                    // is hit it pauses
  std::cout << std::boolalpha;
  std::cout << "value : " << task.m_handle.promise().m_value << std::endl;
  std::cout << "coro done : " << task.m_handle.done() << std::endl;

  task.m_handle();  //
  std::cout << std::boolalpha;
  std::cout << "value : " << task.m_handle.promise().m_value << std::endl;
  std::cout << "coro done : " << task.m_handle.done() << std::endl;

  task.m_handle();  //
  std::cout << std::boolalpha;
  std::cout << "value : " << task.m_handle.promise().m_value << std::endl;
  std::cout << "coro done : " << task.m_handle.done() << std::endl;

  std::cout << "Done!" << std::endl;
  return 0;
}

Possible output

Starting the coroutine...
value : 1
value : 2
coro done : false
value : 3
coro done : false
Returning value 
value : 4
coro done : true
Done!
Handle destroyed...

References