Hi Julie,

That's a clever hack! I'd be curious to see what you think of solving your problem with my proposed std::execution::function (https://wg21.link/p4223) and C++26's reflection capabilities. I think that, compared to coroutines, senders give you more control over memory allocation, and they're "just objects" (albeit complicated ones), so they may be more amenable to reflection-based approaches to serialization than coroutines. On the downside, of course, senders have no language support to make expressing the logic of your program convenient, which is a significant drawback compared to coroutines. I think this could be addressed with a language change that permits the compiler to generate a sender that represents a coroutine function.

Ian

On Fri, Nov 6, 2026 at 1:19 PM Julie Lim via SG14 <sg14@lists.isocpp.org> wrote:
Hello SG14,

 

I’m a game developer working out of Western Canada, Vancouver and I was hoping to open a discussion on some explorative work I’ve done in applying C++20 coroutines to games.


As a gameplay programmer specifically, my interest is less focused on the high-throughput, multithreaded asynchronous programming goals that coroutines were designed to support, and more focused on their application as an intuitive means to create compact, ad-hoc state machines via a single function definition. These ad-hoc state machines are especially useful for creating sequenced behaviors for characters or other game logic and have seen use in both public game engines like Unity as well as (citing from own experience) proprietary game engines with scripting solutions that support await-like syntax.

 

The two problems I hope to solve to make C++20 coroutines more suitable for use as general purpose state machines are as follows:

  • Completely opaque internal state (preventing serialization for game state save/load/replay, network replication, and runtime CRC checking)
  • Allocation at the point of invocation (as opposed to allocating once upon initialization of game or entity state and re-using as needed)

 

With these goals in mind, I’d like to consider the following promise type implementation: https://godbolt.org/z/ccbjWxWd3

 

The above code implements a seemingly compiler-agnostic coroutine save/load solution that works for all versions of MSVC, GCC, and Clang dating back to their earliest implementations of coroutines. It also provides a mechanism to measure the size of a coroutine frame before execution and a way to optionally invoke a coroutine with a pre-allocated field of memory. While it's a neat proof-of-concept, it's not really production viable due to the gross misuse of the coroutine’s destroy chain to visit each in-scope variable on the coroutine frame for identification and serialization.

 

However, from this implementation we can derive an insight: “Any constraints on the compiler’s ability to optimize the lifetime and layout of variables in the coroutine frame resulting from the introduction of a hypothetical coroutine variable visitor function into the C++ Standard would be superseded by the constraints that already exist via the presence of the destroy function.”

 

My question(s) to the study group are: does the above inferred insight hold up to scrutiny? Would the language benefit from a way for external callers to access coroutine variables? And finally, what would the introduction of a hypothetical variable visitor function into the standard look like?


For me personally, I would like to see an opt-in feature using a contents_visitor callable object type that could be defined in the promise type. If present, the compiler would generate a visitor function that takes a contents_visitor& and invokes contents_visitor::operator()(T&) with each variable, input parameter, and active awaiter stored in the frame. The visitor function pointer would be stored on the coroutine frame alongside the resume and destroy function and would be callable via a visit_contents(Promise::contents_visitor&) function on the coroutine handle (only defined if a contents_visitor is defined).

 

Before moving on to the next topic, I do want to note that the idea of a visitor function is not a silver bullet for enabling full general serialization of co-routines or nested coroutine chains. My own implementation above is somewhat crude in that it stores a whole memory snapshot and also limited in that it doesn’t support coroutine nesting at all. However, I think the presence of a Standard defined variable visitor function would provide a much wider range of possibilities for programmers to implement their own coroutine serialization functions constrained to the needs of their project’s requirements. The implementation of a visitor function could also lead to the emergence of other novel patterns such as bi-directional communication between the caller and the coroutine function body without the need to pass a reference into the function and risking it becoming dangling.


Turning our attention to the second problem, let’s take a look at the memory allocation features in the above code sample. The main kludge here is the use of the tls_delete variable in the promise type to optionally suppress the delete call on the coroutine memory in the event that the coroutine memory was allocated and owned by the caller. A straightforward language feature to propose from this would be an optional suppress_delete function implementable on the promise type to indicate whether delete should be called on the coroutine memory or not.


However, this approach requires that the promise type be aware of the memory context that it was allocated into. If we are to include the possibility of using memory allocated by an outside caller, then the natural approach is to pass an allocator or allocation info struct in as a parameter to the coroutine. Looking back through the group’s mailing archive, I saw that this practice has already been discussed by the group back in April with particular attention paid to the associated issue of function signature pollution. I would like to hear more about this discussion especially with the added possible use case of forwarding pre-allocated memory onto coroutines or even nested coroutines.

 

This got a little long winded, so I’ll pause here and thank the group members for their time in reading this far. I look forward to hearing the group’s thoughts on the ideas I have shared here and hope it can lead to some fruitful discussion.

 

Kind regards,

 

Julie Lim

LinkedIn: in/julie-lim-2a687262


_______________________________________________
SG14 mailing list
SG14@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg14