There is no event loop in C++ and threads have very little to do with coroutine either.
When you co_await in C++, the execution of the function is suspended and the code continue to execute the caller, just as if the function had returned. In fact, it is implemented this way. co_await will change the internal state machine of the coroutine and will return.
The execution is resumed when the code explicitly resumes the function.
This is cooperative multitasking. Control of the execution is explicit and predictable.
Now, using most libraries you won't necessarily have to call back the coroutine to be resumed. This is where executors comes in. They are a bit like event loops but inside a library instead of baked in the language. User code can implement them as well, and you can have different one for different use cases. They will usually schedule the execution of coroutines and can also manage multiple threads to execute many of them at once.
For example, you could totally implement an executor on top of a thread pool. Large operation that wait on io won't need to block the thread for themselves, it will start the io operation and give back the thread to other tasks. Internally, the io operation will schedule the coroutine back into the thread pool to be resumed.
Another example would be io_uring on linux, which is the new async io api. One could wrap the facility with an executor and run io operation as coroutines. Technically, you don't need threads to do this one. Calls to co_await will simply schedule the io operation and the coroutine will resume once the kernel has enqueued a result.