Skip to content

Commit 5e27910

Browse files
authored
Support returning void from alien coroutines (#42)
* add support for void return type in alien coroutines * lift default initialization requirement for return type * fix emplace requirement, more consistency using `result_type`
1 parent aaf7284 commit 5e27910

3 files changed

Lines changed: 99 additions & 34 deletions

File tree

examples/alien.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,22 @@ tool::Task<tool::StatusCode> tool3_execute(std::string_view parent) {
8181
}
8282
}
8383

84+
// tool4: co_awaits a AsyncTimer, but return void result
85+
tool::Task<void> tool4_execute(std::string_view parent) {
86+
const auto self = format_name(parent, "tool4");
87+
log(self) << "Calling async API in tool4" << std::endl;
88+
auto status = co_await AsyncTimer{std::chrono::milliseconds(50),
89+
StatusCode::SUCCESS, self};
90+
log(self) << "Result from async API in tool4: " << status << std::endl;
91+
if (status.status() != StatusCode::SUCCESS) {
92+
log(self) << "Async operation in tool4 failed, throwing exception"
93+
<< std::endl;
94+
throw std::runtime_error("tool4_async operation failed");
95+
}
96+
log(self) << "Finishing tool4" << std::endl;
97+
co_return;
98+
}
99+
84100
// Task: co_awaits AsyncTimer then tool1_execute then tool2_execute then
85101
// tool3_execute
86102
algorithm::Task<algorithm::StatusCode> algorithm_execute(
@@ -105,6 +121,9 @@ algorithm::Task<algorithm::StatusCode> algorithm_execute(
105121
log(self) << "Caught exception (expected) from tool3: " << e.what()
106122
<< std::endl;
107123
}
124+
log(self) << "Launching tool4" << std::endl;
125+
co_await tool4_execute(self);
126+
log(self) << "tool4 done" << std::endl;
108127
log(self) << "Finishing algorithm" << std::endl;
109128
co_return algorithm::StatusCode::SUCCESS;
110129
}

include/CoroutineTests/alien/subtool.hpp

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <exception>
77
#include <functional>
88
#include <iostream>
9+
#include <optional>
910

1011
namespace CoroutineTests::alien::subtool {
1112

@@ -51,17 +52,17 @@ concept HasScheduler = requires(T t) {
5152
} // namespace concepts
5253

5354
// Nestable coroutine, can be co_awaited by other coroutines.
54-
// Returns a single value of templated type via co_return, doesn't co_yield.
55+
// Returns a single value of templated type via co_return if it isn't void
56+
// Doesn't co_yield.
5557
template <typename ResultType>
5658
class [[nodiscard]] Task {
5759

58-
static_assert(
59-
std::default_initializable<ResultType>,
60-
"Task<ResultType> requires ResultType to be default-initializable");
61-
static_assert(std::movable<ResultType>,
62-
"Task<ResultType> requires ResultType to be movable");
60+
static_assert(std::movable<ResultType> || std::same_as<ResultType, void>,
61+
"Task<ResultType> requires ResultType to be movable or void");
6362

6463
public:
64+
using result_type = ResultType;
65+
6566
struct promise_type; // typedef required by coroutines
6667
using handle_type =
6768
std::coroutine_handle<promise_type>; // not required but useful
@@ -97,16 +98,39 @@ class [[nodiscard]] Task {
9798
template <concepts::HasScheduler T>
9899
inline handle_type await_suspend(std::coroutine_handle<T> handle) noexcept;
99100
// Awaitable interface: return result or rethrow exception on resume
100-
ResultType await_resume() const;
101+
result_type await_resume() const;
101102

102103
private:
103104
handle_type m_coroutine = nullptr;
104105
};
105106

107+
// Helper for handling co_return in promise_type, default implementation for
108+
// non-void ResultType
106109
template <typename ResultType>
107-
struct Task<ResultType>::promise_type {
110+
struct ReturnHelper {
108111
// Storage for the co_return result value
109-
ResultType m_value;
112+
std::optional<ResultType> m_value;
113+
114+
// Required by coroutines, mutually exclusive with return_void
115+
// Store the co_return value
116+
template <typename T>
117+
requires std::constructible_from<ResultType, T&&>
118+
void return_value(T&& value) {
119+
m_value.emplace(std::forward<T>(value));
120+
}
121+
};
122+
123+
// Specialization for void return type
124+
template <>
125+
struct ReturnHelper<void> {
126+
// Required by coroutines, mutually exclusive with return_value
127+
// Handle co_return without value
128+
void return_void() {}
129+
};
130+
131+
template <typename ResultType>
132+
struct Task<ResultType>::promise_type
133+
: public ReturnHelper<typename Task<ResultType>::result_type> {
110134
// Storage for exceptions thrown in the coroutine body
111135
std::exception_ptr m_exception;
112136
// Handle to the parent coroutine that co_awaited this task
@@ -144,12 +168,6 @@ struct Task<ResultType>::promise_type {
144168
}
145169
// Required by coroutines: capture exceptions for later rethrowing
146170
void unhandled_exception() { m_exception = std::current_exception(); }
147-
// Required by coroutines: store the co_return value
148-
template <typename T>
149-
requires std::assignable_from<ResultType&, T&&>
150-
void return_value(T&& value) {
151-
m_value = std::forward<T>(value);
152-
}
153171
};
154172

155173
template <typename ResultType>
@@ -162,11 +180,16 @@ inline Task<ResultType>::handle_type Task<ResultType>::await_suspend(
162180
}
163181

164182
template <typename ResultType>
165-
inline ResultType Task<ResultType>::await_resume() const {
183+
inline typename Task<ResultType>::result_type Task<ResultType>::await_resume()
184+
const {
166185
if (m_coroutine.promise().m_exception) {
167186
std::rethrow_exception(m_coroutine.promise().m_exception);
168187
}
169-
return std::move(m_coroutine.promise().m_value);
188+
if constexpr (std::same_as<result_type, void>) {
189+
return;
190+
} else {
191+
return std::move(m_coroutine.promise().m_value).value();
192+
}
170193
}
171194
} // namespace CoroutineTests::alien::subtool
172195
#endif // COROUTINETESTS_ALIEN_SUBTOOL_H

include/CoroutineTests/alien/tool.hpp

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <exception>
77
#include <functional>
88
#include <iostream>
9+
#include <optional>
910

1011
namespace CoroutineTests::alien::tool {
1112

@@ -51,17 +52,17 @@ concept HasScheduler = requires(T t) {
5152
} // namespace concepts
5253

5354
// Nestable coroutine, can be co_awaited by other coroutines.
54-
// Returns a single value of templated type via co_return, doesn't co_yield.
55+
// Returns a single value of templated type via co_return if it isn't void
56+
// Doesn't co_yield.
5557
template <typename ResultType>
5658
class [[nodiscard]] Task {
5759

58-
static_assert(
59-
std::default_initializable<ResultType>,
60-
"Task<ResultType> requires ResultType to be default-initializable");
61-
static_assert(std::movable<ResultType>,
62-
"Task<ResultType> requires ResultType to be movable");
60+
static_assert(std::movable<ResultType> || std::same_as<ResultType, void>,
61+
"Task<ResultType> requires ResultType to be movable or void");
6362

6463
public:
64+
using result_type = ResultType;
65+
6566
struct promise_type; // typedef required by coroutines
6667
using handle_type =
6768
std::coroutine_handle<promise_type>; // not required but useful
@@ -97,16 +98,39 @@ class [[nodiscard]] Task {
9798
template <concepts::HasScheduler T>
9899
inline handle_type await_suspend(std::coroutine_handle<T> handle) noexcept;
99100
// Awaitable interface: return result or rethrow exception on resume
100-
ResultType await_resume() const;
101+
result_type await_resume() const;
101102

102103
private:
103104
handle_type m_coroutine = nullptr;
104105
};
105106

107+
// Helper for handling co_return in promise_type, default implementation for
108+
// non-void ResultType
106109
template <typename ResultType>
107-
struct Task<ResultType>::promise_type {
110+
struct ReturnHelper {
108111
// Storage for the co_return result value
109-
ResultType m_value;
112+
std::optional<ResultType> m_value;
113+
114+
// Required by coroutines, mutually exclusive with return_void
115+
// Store the co_return value
116+
template <typename T>
117+
requires std::constructible_from<ResultType, T&&>
118+
void return_value(T&& value) {
119+
m_value.emplace(std::forward<T>(value));
120+
}
121+
};
122+
123+
// Specialization for void return type
124+
template <>
125+
struct ReturnHelper<void> {
126+
// Required by coroutines, mutually exclusive with return_value
127+
// Handle co_return without value
128+
void return_void() {}
129+
};
130+
131+
template <typename ResultType>
132+
struct Task<ResultType>::promise_type
133+
: public ReturnHelper<typename Task<ResultType>::result_type> {
110134
// Storage for exceptions thrown in the coroutine body
111135
std::exception_ptr m_exception;
112136
// Handle to the parent coroutine that co_awaited this task
@@ -144,12 +168,6 @@ struct Task<ResultType>::promise_type {
144168
}
145169
// Required by coroutines: capture exceptions for later rethrowing
146170
void unhandled_exception() { m_exception = std::current_exception(); }
147-
// Required by coroutines: store the co_return value
148-
template <typename T>
149-
requires std::assignable_from<ResultType&, T&&>
150-
void return_value(T&& value) {
151-
m_value = std::forward<T>(value);
152-
}
153171
};
154172

155173
template <typename ResultType>
@@ -162,11 +180,16 @@ inline Task<ResultType>::handle_type Task<ResultType>::await_suspend(
162180
}
163181

164182
template <typename ResultType>
165-
inline ResultType Task<ResultType>::await_resume() const {
183+
inline typename Task<ResultType>::result_type Task<ResultType>::await_resume()
184+
const {
166185
if (m_coroutine.promise().m_exception) {
167186
std::rethrow_exception(m_coroutine.promise().m_exception);
168187
}
169-
return std::move(m_coroutine.promise().m_value);
188+
if constexpr (std::same_as<result_type, void>) {
189+
return;
190+
} else {
191+
return std::move(m_coroutine.promise().m_value).value();
192+
}
170193
}
171194
} // namespace CoroutineTests::alien::tool
172195
#endif // COROUTINETESTS_ALIEN_TOOL_H

0 commit comments

Comments
 (0)