wwa-coro 0.0.1
Yet Another C++20 Coroutine Library
task.h
Go to the documentation of this file.
1#ifndef B6E7FCEC_A837_4D2C_B71A_E02D65C82406
2#define B6E7FCEC_A837_4D2C_B71A_E02D65C82406
3
16
17#include <cassert>
18#include <coroutine>
19#include <exception>
20#include <memory>
21#include <optional>
22#include <type_traits>
23#include <utility>
24
25#include "detail.h"
26#include "exceptions.h"
27
29namespace wwa::coro {
30
31template<typename Result>
32class task;
33
35
36namespace detail {
37
38// NOLINTBEGIN(readability-convert-member-functions-to-static)
39template<typename Promise>
40struct promise_base {
45 void unhandled_exception() noexcept { this->m_exception = std::current_exception(); }
46
47 [[nodiscard]] constexpr auto initial_suspend() const noexcept { return std::suspend_always{}; }
48
49 [[nodiscard]] constexpr auto final_suspend() const noexcept
50 {
51 struct nested_awaiter {
52 [[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
53
54 [[nodiscard]] auto await_suspend(std::coroutine_handle<Promise> handle) const noexcept
55 {
56 const auto& promise = handle.promise();
57 return promise.m_next ? promise.m_next : std::noop_coroutine();
58 }
59
60 void await_resume() const noexcept {} // GCOVR_EXCL_LINE -- this method is not used
61 };
62
63 return nested_awaiter{};
64 }
65
66 void set_next(std::coroutine_handle<> next) noexcept { this->m_next = next; }
67
68protected:
73 void rethrow_if_exception() const
74 {
75 if (this->m_exception != nullptr) {
76 std::rethrow_exception(this->m_exception);
77 }
78 }
79
80private:
81 std::coroutine_handle<> m_next;
82 std::exception_ptr m_exception = nullptr;
83
84 friend Promise;
85
90 promise_base() noexcept = default;
91};
92// NOLINTEND(readability-convert-member-functions-to-static)
93
94template<typename T>
95struct promise_type : promise_base<promise_type<T>> {
96 using value_type = std::remove_reference_t<T>;
97 using storage_type =
98 std::conditional_t<std::is_lvalue_reference_v<T>, std::add_pointer_t<value_type>, std::optional<value_type>>;
99 using reference = std::conditional_t<std::is_rvalue_reference_v<T>, T, std::add_lvalue_reference_t<value_type>>;
100
101 auto get_return_object() { return task<T>{std::coroutine_handle<promise_type>::from_promise(*this)}; }
102
107 void return_value(const T& res) noexcept(std::is_nothrow_copy_constructible_v<T>)
108 requires(std::is_const_v<T> && !std::is_lvalue_reference_v<T>)
109 {
110 this->m_result.emplace(res);
111 }
112
118 void return_value(T res) noexcept(std::is_nothrow_move_constructible_v<T>)
119 requires(!std::is_const_v<T> && !std::is_lvalue_reference_v<T>)
120 {
121 this->m_result = std::move(res);
122 }
123
128 void return_value(T& res) noexcept
129 requires(std::is_lvalue_reference_v<T>)
130 {
131 this->m_result = std::addressof(res);
132 }
133
140 reference result_value()
141 {
142 this->rethrow_if_exception();
143 if constexpr (std::is_lvalue_reference_v<T>) {
144 if (this->m_result != nullptr) {
145 return *this->m_result;
146 }
147 }
148 else if (this->m_result.has_value()) {
149 if constexpr (std::is_rvalue_reference_v<T>) {
150 return std::move(this->m_result.value());
151 }
152 else {
153 return this->m_result.value();
154 }
155 }
156
157 throw bad_result_access("task<T> result accessed before it was set");
158 }
159
160private:
169 storage_type m_result{};
170};
171
172template<>
173struct promise_type<void> : promise_base<promise_type<void>> {
174 task<void> get_return_object();
175 void return_void() const noexcept {}
176 void result_value() const { this->rethrow_if_exception(); }
177};
178
179} // namespace detail
180
182
195template<typename Result = void>
196class [[nodiscard]] task {
197public:
203 using promise_type = detail::promise_type<Result>;
204
211 task() noexcept = default;
212
214 task(const task&) = delete;
215 task& operator=(const task&) = delete;
217
224 task(task&& other) noexcept : m_coroutine(std::exchange(other.m_coroutine, nullptr)) {}
225
234 task& operator=(task&& other) noexcept
235 {
236 if (this != std::addressof(other)) {
237 if (this->m_coroutine) {
238 this->m_coroutine.destroy();
239 }
240
241 this->m_coroutine = std::exchange(other.m_coroutine, nullptr);
242 }
243
244 return *this;
245 }
246
251 {
252 if (this->m_coroutine) {
253 this->m_coroutine.destroy();
254 }
255 }
256
269 [[nodiscard]] constexpr bool is_ready() const noexcept { return detail::is_ready(this->m_coroutine); }
270
285 // NOLINTNEXTLINE(modernize-use-nodiscard)
286 bool resume() const
287 {
288 if (!this->is_ready()) {
289 this->m_coroutine.resume();
290 }
291
292 return !this->is_ready();
293 }
294
307 bool destroy()
308 {
309 if (this->m_coroutine) {
310 this->m_coroutine.destroy();
311 this->m_coroutine = nullptr;
312 return true;
313 }
314
315 return false;
316 }
317
319 using value_type = std::remove_reference_t<Result>;
321 using reference =
322 std::conditional_t<std::is_rvalue_reference_v<Result>, Result, std::add_lvalue_reference_t<value_type>>;
324 using const_reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
326 using rvalue_reference = std::add_rvalue_reference_t<std::remove_cv_t<value_type>>;
327
344 {
345 detail::check_coroutine(this->m_coroutine);
346
347 if constexpr (std::is_rvalue_reference_v<Result>) {
348 return std::move(this->m_coroutine.promise().result_value());
349 }
350 else {
351 return this->m_coroutine.promise().result_value();
352 }
353 }
354
367 // NOLINTNEXTLINE(modernize-use-nodiscard)
369 {
370 detail::check_coroutine(this->m_coroutine);
371 return this->m_coroutine.promise().result_value();
372 }
373
387 {
388 detail::check_coroutine(this->m_coroutine);
389 return std::move(this->m_coroutine.promise().result_value());
390 }
391
405 auto operator co_await() const& noexcept
406 {
407 struct awaiter {
408 [[nodiscard]] constexpr bool await_ready() const noexcept { return detail::is_ready(this->coroutine); }
409
410 auto await_suspend(std::coroutine_handle<> awaiting) noexcept
411 {
412 this->coroutine.promise().set_next(awaiting);
413 return this->coroutine;
414 }
415
416 decltype(auto) await_resume()
417 {
418 detail::check_coroutine(this->coroutine);
419 auto& promise = this->coroutine.promise();
420 if constexpr (std::is_rvalue_reference_v<Result>) {
421 return std::move(promise.result_value());
422 }
423 else {
424 return promise.result_value();
425 }
426 }
427
428 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
429 std::coroutine_handle<promise_type> coroutine;
430 };
431
432 return awaiter{this->m_coroutine};
433 }
434
445 auto operator co_await() && noexcept
446 requires(!std::is_void_v<Result>)
447 {
448 struct awaiter {
449 [[nodiscard]] constexpr bool await_ready() const noexcept { return detail::is_ready(this->coroutine); }
450
451 auto await_suspend(std::coroutine_handle<> awaiting) noexcept
452 {
453 this->coroutine.promise().set_next(awaiting);
454 return this->coroutine;
455 }
456
457 decltype(auto) await_resume()
458 {
459 detail::check_coroutine(this->coroutine);
460 return std::move(this->coroutine.promise().result_value());
461 }
462
463 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
464 std::coroutine_handle<promise_type> coroutine;
465 };
466
467 return awaiter{this->m_coroutine};
468 }
469
470private:
471 std::coroutine_handle<promise_type> m_coroutine;
472
473 friend promise_type;
474
482 explicit task(std::coroutine_handle<promise_type> handle) : m_coroutine(handle) {}
483};
484
489
491
492inline task<void> detail::promise_type<void>::get_return_object()
493{
494 return task<void>{std::coroutine_handle<promise_type>::from_promise(*this)};
495}
496
498
499} // namespace wwa::coro
500
501#endif /* B6E7FCEC_A837_4D2C_B71A_E02D65C82406 */
A coroutine task.
Definition task.h:196
std::remove_reference_t< Result > value_type
Value type; a Result with all references stripped.
Definition task.h:319
task() noexcept=default
Default constructor.
std::conditional_t< std::is_rvalue_reference_v< Result >, Result, std::add_lvalue_reference_t< value_type > > reference
Reference type; Result&& if Result is an rvalue reference, Result& otherwise.
Definition task.h:321
rvalue_reference result_value() &&
Returns the result produced by the task.
Definition task.h:386
~task()
Destructor.
Definition task.h:250
task & operator=(task &&other) noexcept
Move assignment operator.
Definition task.h:234
reference result_value() &
Returns the result produced by the task.
Definition task.h:343
std::add_rvalue_reference_t< std::remove_cv_t< value_type > > rvalue_reference
Rvalue reference type.
Definition task.h:326
bool destroy()
Destroys the task.
Definition task.h:307
std::add_lvalue_reference_t< std::add_const_t< value_type > > const_reference
Constant version of reference.
Definition task.h:324
detail::promise_type< Result > promise_type
The promise type associated with the task.
Definition task.h:203
bool resume() const
Resumes the task.
Definition task.h:286
constexpr bool is_ready() const noexcept
Checks if the task is ready.
Definition task.h:269
const_reference result_value() const &
Returns the result produced by the task.
Definition task.h:368
Internal utility functions for coroutine handling.
Custom exception classes for coroutine handling.
Library namespace.