From 5d43cff9548faf042b4eba78a13db09b22902d5a Mon Sep 17 00:00:00 2001 From: Khalil Estell Date: Mon, 19 Jan 2026 13:23:45 -0800 Subject: [PATCH] :bug: Add missing default ctor for `future` This allows sync functions to return a finished future --- modules/async_context.cppm | 111 ++++++++++++++++++++++--------------- tests/basics.test.cpp | 48 +++++++++++++++- 2 files changed, 112 insertions(+), 47 deletions(-) diff --git a/modules/async_context.cppm b/modules/async_context.cppm index 441d46c..286dee9 100644 --- a/modules/async_context.cppm +++ b/modules/async_context.cppm @@ -893,6 +893,71 @@ public: using handle_type = std::coroutine_handle<>; using full_handle_type = std::coroutine_handle; + future(future const& p_other) = delete; + future& operator=(future const& p_other) = delete; + + /** + * @brief Default initialization for a void future + * + * This future will considered to be done on creation. + */ + future() + requires(std::is_void_v) + : m_state(std::monostate{}) + { + } + + /** + * @brief Construct a future with a value + * + * This future will considered to be done and will contain just the value + * passed into this. + * + * @tparam U - type that can construct a type T (which includes T itself) + */ + template + constexpr future(U&& p_value) noexcept + requires std::is_constructible_v + { + m_state.template emplace(std::forward(p_value)); + }; + + constexpr future(future&& p_other) noexcept + : m_state(std::exchange(p_other.m_state, std::monostate{})) + { + if (std::holds_alternative(m_state)) { + auto handle = std::get(m_state); + full_handle_type::from_address(handle.address()) + .promise() + .set_object_address(&m_state); + } + } + + constexpr future& operator=(future&& p_other) noexcept + { + if (this != &p_other) { + m_state = std::exchange(p_other.m_state, std::monostate{}); + if (std::holds_alternative(m_state)) { + auto handle = std::get(m_state); + full_handle_type::from_address(handle.address()) + .promise() + .set_object_address(&m_state); + } + } + return *this; + } + + constexpr ~future() + { + if (std::holds_alternative(m_state)) { + auto handle = std::get(m_state); + full_handle_type::from_address(handle.address()) + .promise() + .get_context() + .cancel(); + } + } + constexpr void resume() const { if (std::holds_alternative(m_state)) { @@ -1023,52 +1088,6 @@ public: return awaiter{ *this }; } - template - constexpr future(U&& p_value) noexcept - requires std::is_constructible_v - { - m_state.template emplace(std::forward(p_value)); - }; - - future(future const& p_other) = delete; - future& operator=(future const& p_other) = delete; - - constexpr future(future&& p_other) noexcept - : m_state(std::exchange(p_other.m_state, std::monostate{})) - { - if (std::holds_alternative(m_state)) { - auto handle = std::get(m_state); - full_handle_type::from_address(handle.address()) - .promise() - .set_object_address(&m_state); - } - } - - constexpr future& operator=(future&& p_other) noexcept - { - if (this != &p_other) { - m_state = std::exchange(p_other.m_state, std::monostate{}); - if (std::holds_alternative(m_state)) { - auto handle = std::get(m_state); - full_handle_type::from_address(handle.address()) - .promise() - .set_object_address(&m_state); - } - } - return *this; - } - - constexpr ~future() - { - if (std::holds_alternative(m_state)) { - auto handle = std::get(m_state); - full_handle_type::from_address(handle.address()) - .promise() - .get_context() - .cancel(); - } - } - private: friend promise_type; diff --git a/tests/basics.test.cpp b/tests/basics.test.cpp index 76e883b..042ccda 100644 --- a/tests/basics.test.cpp +++ b/tests/basics.test.cpp @@ -8,7 +8,28 @@ import test_utils; boost::ut::suite<"basics"> basics = []() { using namespace boost::ut; - "sync return"_test = []() { + "sync return type void"_test = []() { + // Setup + test_context ctx; + + unsigned step = 0; + auto sync_coroutine = [&step](async::context&) -> async::future { + step = 1; + return {}; + }; + + // Exercise + auto future = sync_coroutine(ctx); + + // Verify + expect(that % 0 == ctx.memory_used()); + expect(that % not ctx.info->scheduled_called_once); + expect(that % future.done()); + expect(that % future.has_value()); + expect(that % 1 == step); + }; + + "sync return type int"_test = []() { // Setup test_context ctx; @@ -32,6 +53,31 @@ boost::ut::suite<"basics"> basics = []() { expect(that % 1 == step); }; + "sync return type std::string"_test = []() { + // Setup + test_context ctx; + + static constexpr auto expected_return_value = "Hello, World\n"; + + unsigned step = 0; + auto sync_coroutine = + [&step](async::context&) -> async::future { + step = 1; + return expected_return_value; + }; + + // Exercise + auto future = sync_coroutine(ctx); + + // Verify + expect(that % 0 == ctx.memory_used()); + expect(that % not ctx.info->scheduled_called_once); + expect(that % future.done()); + expect(that % future.has_value()); + expect(that % expected_return_value == future.value()); + expect(that % 1 == step); + }; + "co_return"_test = []() { // Setup test_context ctx;