Why does coroutine_handle's operator bool return true after destruction?

Viewed 79

I am new to C++20 coroutines and surprised to know that coroutine_handle::operator bool returns true after destruction?

Sample program:

#include <coroutine>
#include <iostream>

struct ReturnObject {
  struct promise_type {
    void return_void() {}
    ReturnObject get_return_object() { return {}; }
    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void unhandled_exception() {}
  };
};

struct Awaiter {
  std::coroutine_handle<> *hp_;
  constexpr bool await_ready() const noexcept { return false; }
  void await_suspend(std::coroutine_handle<> h) { *hp_ = h; }
  constexpr void await_resume() const noexcept {}
};

ReturnObject
counter(std::coroutine_handle<> *continuation_out)
{
  Awaiter a{continuation_out};
  for (;;)
    co_await a;
}

int main()
{
  std::coroutine_handle<> h;
  std::cout << "before construction " << (bool)h << '\n';
  counter(&h);
  std::cout << "after construction " << (bool)h << '\n';
  h.destroy();
  std::cout << "after destruction " << (bool)h << '\n';
}

https://gcc.godbolt.org/z/a7ehjzhab

It prints

before construction 0
after construction 1
after destruction 1

Why does it still return true after destruction? So it is not possible to distinguish active coroutine_handle from destructed one?

1 Answers

Because coroutine handle fundamentally just holds an address. You can almost think of it as a "coroutine view", it doesn't own the coroutine. destroy exists if the coroutine wouldn't exit normally via standard control flow e.g. generators. operator bool for a std::coroutine_handle is defined as being equivalent to return (bool)address(); unless it came from a noop promise (in which case it's just true).

As such when you call .destroy() there is no requirement (that I can find) that the handle set itself back to nullptr as literally doing anything with the handle after calling destroy (even checking operator bool) is as best I can tell undefined behavior.

So it is not possible to distinguish active coroutine_handle from destructed one?

Not really? You're not intended to. Once destroy is called you should either get rid of the handle or reinitialize it (either with nullptr or a coroutine).

Related