diff --git a/modules/coroutine.cppm b/modules/coroutine.cppm index 3865a40..458f4ac 100644 --- a/modules/coroutine.cppm +++ b/modules/coroutine.cppm @@ -1045,6 +1045,22 @@ private: // // ============================================================================= +// Forward declaration - promise_base stores a future_base* (m_owner) so that +// cancel()/unhandled_exception() can be shared, non-template functions +// instead of being re-instantiated per future. Only the pointer type is +// needed here; the bodies that dereference it are defined out-of-line below, +// after future_base is complete. +export class future_base; + +// Forward declaration - promise_return_base::return_value()/return_void() +// reach promise_base::m_owner (via a static_cast through promise, their +// only ever enclosing type) rather than keeping a second, redundant +// future*-typed copy of the same pointer. Needs a friend declaration +// here since promise_return_base is a sibling base of promise_base +// (both are direct bases of promise), not a derived class of it. +export template +struct promise_return_base; + /** * @brief The base promise class for coroutine operations * @@ -1058,6 +1074,8 @@ class promise_base { public: friend class context; + template + friend struct promise_return_base; // For regular functions template @@ -1259,33 +1277,38 @@ public: * @brief Cancel this coroutine operation * * This method cancels the current coroutine operation by setting its state - * to cancelled and cleaning up resources. + * to cancelled and cleaning up resources. Defined out-of-line because it + * needs future_base to be a complete type. */ - void cancel() - { - // Set future state to cancelled - m_cancel(this); - // Pop self off context stack - pop_active_coroutine(); - // Destroy promise objects & deallocate memory - std::coroutine_handle::from_promise(*this).destroy(); - } + void cancel(); -protected: /** - * @brief Type alias for cancellation function pointer + * @brief Handle unhandled exceptions in coroutines * - * This type represents the function signature used for cancellation - * callbacks. + * This method is called when a coroutine throws an exception that isn't + * handled within the coroutine itself. Only ever touches future_base's + * fields, never T, so this is one shared function for every promise + * instead of being re-instantiated per T. Defined out-of-line because it + * needs future_base to be a complete type. */ - using cancellation_fn = void(void*); + void unhandled_exception() noexcept; +protected: // Consider m_continuation as the return address of the coroutine. The // coroutine handle for the coroutine that called and awaited the future that // generated this promise is stored here. std::coroutine_handle<> m_continuation = context::noop_sentinel; class context* m_context = nullptr; - cancellation_fn* m_cancel = nullptr; + + // The future_base that owns this promise's result, set at future + // construction (and re-set on future move). Typed as future_base* - + // rather than future* - so that cancel() and unhandled_exception() + // above can be non-template: both only ever touch m_tag/m_base, which + // live on future_base regardless of T. promise_return_base's + // return_value()/return_void() also reach through this same pointer + // (downcasting to future*, via the friend declaration above) rather + // than keeping a second, redundant copy of it. + future_base* m_owner = nullptr; }; export template @@ -1324,6 +1347,11 @@ struct busy_state export class future_base { public: + // promise_base stores a future_base* (m_owner) and reaches into m_tag / + // m_base / state_tag directly from its (non-template, shared) + // cancel()/unhandled_exception() - see promise_base's declarations. + friend class promise_base; + using handle_type = std::coroutine_handle<>; /** @@ -1663,12 +1691,6 @@ struct promise_return_base template void return_value(U&& p_value) noexcept requires std::is_constructible_v; - - /** - * @brief Pointer to the future that owns this promise's result, set at - * future construction. - */ - future* m_owner = nullptr; }; /** @@ -1685,12 +1707,6 @@ struct promise_return_base * Defined out-of-line after future is complete. */ void return_void() noexcept; - - /** - * @brief Pointer to the future that owns this promise's result, set - * at future construction. - */ - future* m_owner = nullptr; }; /** @@ -1725,28 +1741,9 @@ public: return {}; } - /** - * @brief Handle unhandled exceptions in coroutines - * - * This method is called when a coroutine throws an exception that isn't - * handled within the coroutine itself. Defined out-of-line after future - * is complete. - */ - void unhandled_exception() noexcept; - - /** - * @brief Set future object associated with this promise to the - * cancelled state. - * - * This static method is used to cancel a promise by setting its owning - * future's state to cancelled. The exact promise type information is type - * erased and saved into the promise_base such that the `context` class can - * safely cancel its future objects. Defined out-of-line after future is - * complete. - * - * @param p_self Pointer to the promise to cancel - */ - static void cancel_promise(void* p_self); + // unhandled_exception() and cancel() are inherited, unmodified, from + // promise_base - neither ever needed T, so there is no per-T override + // here anymore (see promise_base's declarations above). /** * @brief Get the return object for this promise @@ -1993,9 +1990,7 @@ private: explicit constexpr future(full_handle_type p_handle) : future_base(p_handle) { - auto& promise = p_handle.promise(); - promise.m_owner = this; - promise.m_cancel = &promise_type::cancel_promise; + p_handle.promise().m_owner = this; } union value_storage @@ -2029,6 +2024,9 @@ export using task = future; * @brief Handle return value for non-void futures * * Defined out-of-line because it needs future to be a complete type. + * promise_return_base is always used exclusively as a base of + * promise, so the static_cast to reach promise_base::m_owner (rather + * than keeping a second, redundant future*-typed copy of it) is safe. * * @param p_value The value to return from the coroutine */ @@ -2041,8 +2039,10 @@ void promise_return_base::return_value(U&& p_value) noexcept // assumes this pointer is uninitialized. The promise is constructed from // the future returned by `get_return_object()`, which properly initializes // this promise. - new (&m_owner->m_storage.value) T(std::forward(p_value)); - m_owner->m_tag = future::state_tag::value; + auto* owner = + static_cast*>(static_cast*>(this)->m_owner); + new (&owner->m_storage.value) T(std::forward(p_value)); + owner->m_tag = future::state_tag::value; // NOLINTEND(clang-analyzer-core.CallAndMessage) } @@ -2050,40 +2050,51 @@ void promise_return_base::return_value(U&& p_value) noexcept * @brief Handle return void for void futures * * Defined out-of-line because it needs future to be a complete type. + * Same static_cast-through-promise reasoning as return_value() above. */ inline void promise_return_base::return_void() noexcept { // NOLINTBEGIN(clang-analyzer-core.CallAndMessage) - m_owner->m_tag = future::state_tag::value; + auto* owner = + static_cast*>(static_cast*>(this)->m_owner); + owner->m_tag = future::state_tag::value; // NOLINTEND(clang-analyzer-core.CallAndMessage) } /** * @brief Handle unhandled exceptions in coroutines * - * Defined out-of-line because it needs future to be a complete type. + * Only ever touches future_base's fields (m_tag, m_base.exception), never + * anything specific to a future's T, so this is one shared, non-template + * function for every promise in the program - unlike before, when this + * lived on promise and was re-instantiated per T. Defined out-of-line + * because it needs future_base to be a complete type. */ -template -void promise::unhandled_exception() noexcept +inline void promise_base::unhandled_exception() noexcept { - auto* owner = promise_return_base::m_owner; - new (&owner->m_base.exception) std::exception_ptr(std::current_exception()); - owner->m_tag = future::state_tag::exception; + new (&m_owner->m_base.exception) std::exception_ptr(std::current_exception()); + m_owner->m_tag = future_base::state_tag::exception; } /** - * @brief Set future object associated with this promise to the cancelled - * state. - * - * Defined out-of-line because it needs future to be a complete type. + * @brief Cancel this coroutine operation * - * @param p_self Pointer to the promise to cancel + * Sets the owning future_base's state to cancelled, pops this coroutine off + * its context's active-handle stack, and destroys the coroutine frame. Only + * ever touches future_base's fields, so - like unhandled_exception() above - + * this is one shared, non-template function instead of being re-instantiated + * per future via a per-T cancel_promise() and an m_cancel function + * pointer. Defined out-of-line because it needs future_base to be a + * complete type. */ -template -void promise::cancel_promise(void* p_self) +inline void promise_base::cancel() { - auto* self = static_cast*>(p_self); - self->m_owner->m_tag = future::state_tag::cancelled; + // Set future state to cancelled + m_owner->m_tag = future_base::state_tag::cancelled; + // Pop self off context stack + pop_active_coroutine(); + // Destroy promise objects & deallocate memory + std::coroutine_handle::from_promise(*this).destroy(); } /**