April 26th, 2021

C++ coroutines: What does it mean when I declare my coroutine as noexcept?

Suppose you want a coroutine that terminates on unhandled exceptions, or equivalently (looking at it from the consumer side) a coroutine that never throws an exception when awaited. For regular functions, the way to say this is to put the noexcept exception specification on your function declaration:

int GetValue() noexcept
{
    return LoadValue();
}

If the Load­Value() function raises an exception, the exception propagation stops at the noexcept and turns into a std::terminate, which is a fatal error that terminates the application.

Looking at the contract from the other side, the noexcept specification tells the caller that no exceptions can escape the Get­Value() function, so the caller can optimize accordingly. Get­Value() will get you a value or die trying.

Okay, so what happens when you apply this to a coroutine?

simple_task<int> GetValueAsync() noexcept
{
    co_return LoadValue();
}

If an exception is thrown by the Load­Value() function, the exception is captured into the simple_task and is rethrown when the task is co_awaited.

Wait a second. I put the noexcept keyword on this function. Certainly that means that any unhandled exception in the function terminates the program, right?

Yes, that’s what it means, but your coroutine isn’t the function.

The function is the thing that returns a simple_task. And the noexcept says that the Get­Value­Async() function can successfully return a simple_task without raising an exception.

Look at this from the caller’s point of view: The caller sees only

simple_task<int> GetValueAsync() noexcept;

This is not a coroutine definition. This is just a function prototype. The caller doesn’t know how GetValueAsync() is going to produce that simple_task. The implementation could be

simple_task<int> GetValueAsync() noexcept
{
    return simple_task<int>(constructor parameters);
}

Just the usual case of returning a constructed object. No coroutines involved at all.

If Get­Value­Async() is implemented as a coroutine, then any unhandled exception is passed to the coroutine promise’s unhandled_exception method, and it’s up to the promise to decide what to do next.

So what can you do if you really want your coroutine to terminate on unhandled exception? We’ll look at that next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.