How to Implement Coroutines in C++?

Learn via video course
FREE
View all courses
C++ Course: Learn the Essentials
C++ Course: Learn the Essentials
by Prateek Narang
1000
5
Start Learning
C++ Course: Learn the Essentials
C++ Course: Learn the Essentials
by Prateek Narang
1000
5
Start Learning
Topics Covered

What are Coroutines in C++?

The C++ coroutines are a control structure in which the control flow is passed along with the different routines without returning. The C++ coroutine feature is added in the C++20. The C++ coroutine is a function that can prevent the execution that needs to be resumed later. Coroutines are utilized in numerous stages of development just like the execution of code non-concurrently with a set of legitimate consecutive codes in place. The C++ coroutines are of stackless behavior which is why this function returns the result to the caller and continues the specified coroutine that's again stored within the isolated area or stack that's as of now defined.

Need of C++ Coroutines

To read a record and define it whereas reading into a few essential and meaningful pieces of information, you can either read step by step at each line, which is fine. It is also possible that we can load all the large content in the memory, but that would not be suggested for large text file cases such as MS Word, text editors, etc.

For solving this problem, Donald Knuth introduced a solution for this problem in computer programming. Donald Knuth said that we can remove the stack concept completely. We do not need to let any process as the caller and caller. We can just consider them as cooperating equals.

Implementation of Coroutines in C++

For implementing C++ coroutines, there is the requirement of the following two things:

  1. Resume control to its last state
  2. Make data persist through calls

The above problem can be solved by using static variables. But how to keep in mind the state additionally returns to the same execution state as some time recently i.e., the lines of code after the return or the loop? We can use here the GOTO statements. Let us see the solution below.

We now basically have numerous return explanations defined inside the for loop and each time we return to a diverse state to execute the program on the off chance that it is programmed to do so. This approach represents a function in python named the range function of python. That function of python is also based on the C++ coroutine concept of C++.

How Do C++ Coroutines Work?

The C++ coroutines work as described below.

  • C++ coroutines are functions that can be resumed at a later point even when the primary execution gets completed. The C++ coroutines mainly call the main function and then the data is saved in another location. Whenever we use coroutines as a function then we must associate its data type to make it work.
  • The C++ coroutines are always attached with variating arguments and return statements which makes them perfect coroutines. But there still exist some restrictions. The constructors, destructors, constant expression function, and the main function can not form the perfect coroutines.

Some important concepts related to C++ coroutines are:

Coroutine execution paradigm:

A C++coroutine is associated with two things. The first one is the promise object and the second one is the coroutine object. A promise object is an object which can store a value that can be retrieved by any future a=object (generally another thread) and it offers a synchronization point. And the coroutine state is used with a heap where the heap is available in an optimized manner. The state object has the following properties:

  1. available Parameters.
  2. Until the current suspension point, the local variables and the temporary variables have a little time stamp and scope.
  3. Promise objects for manipulation within.
  4. To understand where to resume and continue the execution, we represent the proper state and value of the local variables.

coroutine handles:

Whenever we need to compile and execute the outside scope of the coroutine frame we use the coroutine handles. We use the non-use handling handlers of the coroutine hold to resume the execution of the coroutine and simultaneously delete the C++ coroutines from the frame. A C++coroutine handle is somewhat similar to a c pointer which can be easily copied but we do not have an associated destructor to destroy the occupied memory. So, we must use the thecoroutine_handle:: destroy method to destroy the coroutine and avoid memory leakage. When the coroutine is destroyed, the coroutine handler which was pointing to the deleted coroutine. The coroutine handler will now point to a garbage value and hence it will not work when invoked. Even if the control flows in and out of the coroutines, a coroutine handle is still valid for executing the entire coroutine.

coroutine return object:

The return type of the C++coroutine is an object with a nested type ::promise_type. The r::promise_type should include a method namely r get_return_object which returns an outer object of type r. The get_return_object function returns a coroutine function.

The promise object:

The coroutine state has an instance of promise type. We can add a field namely value to transmit the values from the coroutine to the main function. We do not convert the coroutine handle to a std::coroutine_handle<>, we keep it as std::coroutine_handle<ReturnObject3::promise_type>.

The co_yield operator:

The co_yield operator yields a value from the expression and returns the value to the caller. It suspends the current coroutine and it is one of the most common building blocks of the resumable generator functions.

The co_return operator:

The co_return operator signals the end of a coroutine. There are three ways of doing the signaling.

  1. Co_return e can be used to return the value e.
  2. To denote the end of a coroutine without a final value, the co_routine can use the co_return operator.
  3. If we want to fall off the end of the function, we can use the co_return operator just like the previous point.

Important Points to Remember When Using Coroutines

Following are some important points that we should always keep in mind while using the C++ coroutines.

  • The C++ coroutines can only use the co_return operator but it can not use return.
  • We can not use varargs and it can not be a constexpr.
  • It can not be a constructor or a destructor.
  • The coroutines can not be in the main function as well.
  • When we are using the C++ coroutines, we should use parameters by value to be on the safer side and avoid the dangling reference.

Coroutines and Reference Parameters

The C++ coroutines are implemented in such a way that the entire call stack is not saved when the coroutine is suspended. We only store the local variable. This kind of implementation of the coroutine is known as a stack full coroutine. On the other hand, we have a stack-less coroutine in which the entire call stack is saved and restored. A coroutine reference parameter similar to any other asynchronous parameter takes a reference parameter that the caller object must provide. This ensures that the referenced object continues to exist throughout the existence of the object.

Examples of C++ Coroutines

Given below are some examples that will help us to understand how the C++ coroutines work.

  • Example - 1:
    This example shows how the coroutine greedy generator works when there is a program in which the function returns all the integers from the start to the end of the given program.

    Output:

  • Example -2:
    In this example, we used the C++ coroutines for storing the values that are being returned from the output. And that output is called by the returning value of the future as given below.

    Output:

Conclusion

  • The C++ coroutines are a control structure in which the control flow is passed along with the different routines without returning.
  • The C++ coroutine was introduced afterward from the C++11.
  • The C++coroutine is a stack-based operation.
  • The C++ coroutines are functions that can be resumed at a later point even when the primary execution gets completed.
  • Two things are associated with the C++ coroutines:
    • the promise object and
    • the coroutine object.
  • The coroutine handle is used to pause or stop the currently executing coroutine.