Back to TILs

C++ coroutine — 4

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



```cpp
#include <cassert>
#include <coroutine>
#include <iostream>

template <typename T>
struct generator {
  struct promise_type {
    T m_value;
    generator get_return_object() { return generator(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(T val) {
      m_value = val;
      return {};
    }

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

  T operator()() {
    assert(m_handle != nullptr);
    m_handle.resume();
    return (m_handle.promise().m_value);
  }
  std::coroutine_handle<promise_type> m_handle;
};

generator<int> generate_numbers() {
  std::cout << "generate_numbers starting" << std::endl;
  co_yield 10;  // Return 10 and pause

  std::cout << "After stop point #1" << std::endl;
  co_yield 20;

  std::cout << "After stop point #2" << std::endl;
  co_yield 30;

  std::cout << "After stop point #3" << std::endl;
  std::cout << "generate_numbers ending" << std::endl;
}

generator<int> infinite_number_stream(int start = 0) {
  auto value = start;
  for ([[maybe_unused]] int i = 0;; ++i) {
    std::cout << "In infinite_number stream..." << std::endl;
    co_yield value;
    ++value;
  }
}

generator<int> range(int first, int last) {
  while (first != last) {
    co_yield first++;
  }
}

int main() {
  auto task1 = generate_numbers();
  std::cout << "value : " << task1() << std::endl;
  std::cout << "value : " << task1() << std::endl;
  std::cout << std::boolalpha;
  std::cout << "value : " << task1() << std::endl;
  std::cout << "coro done : " << task1.m_handle.done() << std::endl;
  task1();
  std::cout << "coro done : " << task1.m_handle.done() << std::endl;

  auto task2 = infinite_number_stream();
  for (size_t i{}; i < 10; ++i) {
    std::cout << "infinite value : " << task2() << std::endl;
  }

  // Range
  auto task3 = range(0, 25);
  for (size_t i{}; i < 26; ++i) {
    std::cout << "range value[" << i << "] : " << task3() << std::endl;
  }

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

  return 0;
}

Possible output

value : generate_numbers starting
10
value : After stop point #1
20
value : After stop point #2
30
coro done : false
After stop point #3
generate_numbers ending
Returning void...
coro done : true
infinite value : In infinite_number stream...
0
infinite value : In infinite_number stream...
1
infinite value : In infinite_number stream...
2
infinite value : In infinite_number stream...
3
infinite value : In infinite_number stream...
4
infinite value : In infinite_number stream...
5
infinite value : In infinite_number stream...
6
infinite value : In infinite_number stream...
7
infinite value : In infinite_number stream...
8
infinite value : In infinite_number stream...
9
range value[0] : 0
range value[1] : 1
range value[2] : 2
range value[3] : 3
range value[4] : 4
range value[5] : 5
range value[6] : 6
range value[7] : 7
range value[8] : 8
range value[9] : 9
range value[10] : 10
range value[11] : 11
range value[12] : 12
range value[13] : 13
range value[14] : 14
range value[15] : 15
range value[16] : 16
range value[17] : 17
range value[18] : 18
range value[19] : 19
range value[20] : 20
range value[21] : 21
range value[22] : 22
range value[23] : 23
range value[24] : 24
range value[25] : Returning void...
24
Done!
Handle destroyed...
Handle destroyed...
Handle destroyed...

References