Search code examples
c++gcccoroutine

Is C++ coroutine_handle invoking undefined behaviour?


I was going through the coroutine_handle implementation from https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/std/coroutine#L251 and I stumbled across the fact that coroutine_handle have void* member variable to store the address.

Since we can convert from address to any promise type and vice versa using coroutine_handle API, doesn't it imply that type system is being abused here?

Was it not possible to implement the coroutine_handle using type safe methods?

Edit: Just to give some context.. I was writing an event loop and was passing task object around different threads, with task class being a template type. I introduced a bug (unintentionally) and I was basically able to convert one task type to another, which had different memory layout (due to specialization).

As per cppcon videos I saw on YouTube (which I am following for almost a year by now), the committee is trying to fill the holes of type system in C++ (I guess the title of video was 'type punning in C++'). I didn't expect that I will be able to do type punning using the feature of C++ that was introduced in C++20.


Solution

  • First, the standard library cannot invoke undefined behavior any more than the compiler can invoke undefined behavior. They form the implementation of the language together. A user of the standard library may invoke undefined behavior by violating the rules of the implementation.

    Second, storing void* is a form of type erasure. It is often helpful to pass objects through layers of code or store them in containers, without everything becoming a template. The standard library has a few type-erased types, including std::function<Sig> and std::any.

    Type erasure is not type punning. When the library provides the ability to restore the type, the user must supply the correct type. One is not allowed to read a type as another type.

    The main difference is std::coroutine_handle::from_address enforces that with a precondition (and UB on violation) while std::any_cast checks the condition. While UB is falling out of favor, preconditions and UB continue to appear in new non-trivial C++ types and that is unlikely to change.