A Guest Editorial

A Guest Editorial

By Quasar Chunawala, Frances Buontempo

Overload, 33(190):2-3, December 2025


C++20 introduced coroutines. Quasar Chunawala, our guest editor for this edition, gives an overview.

Introducing Quasar Chunawala – our Guest Editor

It’s about time we had an editorial, so I have handed over to Quasar Chunawala for this issue. He has written about about coroutines in C++, giving an introduction here and a deeper dive later in this issue. Coroutines can seem hard at first sight, but if someone walks you through the steps they aren’t really that hard – they are just able to be configured in various ways. They can simplify code, for example writing a state machine doesn’t need a massive switch to handle different states.

Thanks to Quasar for volunteering. If you would like a go one day, please get in touch with me. You’ll get a preview of our articles and a chance to comment before publication, and can share something you are interested in, too.

You’ve likely heard about this new C++20 feature, coroutines. I think that this is a really important subject and there are several cool use-cases for coroutines. A coroutine in the simplest terms is just a function that you can pause in the middle. At a later point the caller will decide to resume the execution of the function right where you left off. Unlike a function therefore, coroutines are always stateful - you at least need to remember where you left off in the function body.

Coroutines can simplify our code! Coroutines are a great tool, when it comes to implementing parsers.

The coroutine return type

The initial call to the coroutine function will produce a return object of a certain ReturnType and hand it back to the caller. The interface of this type is what is going to determine what the coroutine is capable of. Since coroutines are super-flexible, we can do a whole lot with this return object. If you have some coroutine, and you want to understand what it’s doing, the first thing you should look at is the ReturnType, and what it’s interface is. The important thing here is, we design this ReturnType. If you are writing a coroutine, you can decide what goes into this interface.

How to turn a function into a coroutine?

The compiler looks for one of the three keywords in the implementation: co_yield, co_await and co_return.

Keyword Action State
co_yield Output Suspended
co_return Output Ended
co_await Input Suspended

In the preceding table, we see that after co_yield and co_await, the coroutine suspends itself and after co_return, it is terminated (co_return is the equivalent of the return statement in the C++ function).

Use-cases for coroutines

Asynchronous computation. Suppose we are tasked with designing a simple echo server. We listen for incoming data from a client socket and we simply send it back to the client. At some point in our code for the echo server, we will have a piece of logic like that in Listing 1: we certainly don’t want to write a server like this. Say one of the clients requests communication and we are in a session. They say, they are ready to send the data, so we are blocking on the read, but maybe they send us this data in 2 minutes, or 5 minutes or even more. And other clients keep waiting.

void session(Socket sock){
  char buffer[1024];
  int len = sock.read({buffer});
  sock.write({buffer,len});
  log(buffer);
}
Listing 1

One solution is to use an asynchronous framework and rewrite our code as in Listing 2.

void session(Socket sock){
  struct State{ Socket sock; char buffer[1024];
};
// Heap allocate the state
auto state = std::make_shared<State>(sock, 
  buffer);
auto on_read_finished_callback = [state](
  error_code ec, 
  size_t len
)
{
  auto done = [state](error_code ec, size_t len)
  {
    if(!ec)
      log();
    }
    if(!ec)
    {
      // Perform an asynchronous write
      state->socket.async_write( 
        state->buffer, 
        done 
      );
    }
}
// Perform an asynchronous read
state->socket.async_read( state->buffer, 
  on_read_finished_callback );
}
Listing 2

So, the session makes two associations:

On finishing read ↦ on_finished_read_callback

On finishing write ↦ done

And implicitly there is a third association even though we cannot see it here – this entire function session is most likely a callback, in response to an event like On client connection established.

Accepting a new client connection ↦ session

So, the server will be many different associations of events to callbacks at different levels.

Pay attention to the state. We said that, we wanted to allocate it on the heap and manage it through a shared_ptr. We pass this shared_ptr<State> by value to every single callback. This way, I make sure that the last one who touches this session turns off the lights and deallocates state.

While this is a toy-example, in real production code, there can be a long sequence of steps and calling lambdas inside lambdas can obfuscate the meaning of the code.

A coroutine implementation of the same echoing session would look like this:

  Task<void> session(Socket sock){
    char buffer[1024];
    int len = co_await sock.async_read({buffer});
    co_await sock.async_write({buffer,len});
    log(buffer);
  }

This looks very similar to the sequential code, except that we use this co_await keyword. You have clear indication of the points where the coroutine will be suspended. Also, note that previously the function session returned void. Now, we are returning something: a Task<void>. This will be a handle to the coroutine and it’s how the outside world will be communicating with the coroutine.

Suspended computation. A second use-case is that coroutines support lazy evaluation. Lazy evaluation doesn’t do any work unless it’s absolutely necessary. This can also potentially make your code more efficient. Lazy evaluation also supports programming with infinite lists.

The lay of the land

The diagram in Figure 1 shows the relationships between the components of coroutines [Weis2022].

Figure 1

ReturnType – The initial call to the coroutine returns an object of the type ReturnType. This interface determines what the coroutine is actually capable of.

Promise – If we think of the coroutine as the producer of data and the caller as the consumer, on the producer side, the coroutine will store the result in a promise object. On the consumer side, the caller can retrieve the result using the return object of type ReturnType. So, the promise is the interface through which the caller interacts with the coroutine.

coroutine_handle – The coroutine_handle is like a raw pointer to the coroutine frame. The coroutine frame consists of the values of local variables, the promise and any internal state.

Awaitable – A coroutine may send – aka yield – a value to the caller or may (co)await an asynchronous operation to complete. In both cases, the coroutine will suspend itself, save its state in the coroutine frame and return control to the caller. An awaitable is therefore the thing the coroutine awaits on.

Reference

[Weis2022] Andreas Weis, ‘Deciphering Coroutines’, CppCon 2022 , available at https://www.youtube.com/watch?v=JXZswq3m41I.

Quasar Chunawala has a bachelor’s degree in Computer Science. He is a software programmer turned quant engineer, enjoys building things ground-up and is deeply passionate about programming in C++, Rust and concurrency and performance-related topics. He is a long-distance hiker and his favorite trekking routes are the Goechala route in Sikkim, India and the Tour-Du-Mont Blanc (TMB) circuit in the French Alps.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.