wwa-coro 0.0.1
Yet Another C++20 Coroutine Library
async_generator.h
Go to the documentation of this file.
1#ifndef C16AFA99_7780_4436_8BC0_60F24EF771A4
2#define C16AFA99_7780_4436_8BC0_60F24EF771A4
3
17
18#include <coroutine>
19#include <exception>
20#include <functional>
21#include <iterator>
22#include <memory>
23#include <type_traits>
24#include <utility>
25
26#include "detail.h"
27#include "exceptions.h"
28#include "task.h"
29
30namespace wwa::coro {
31
45template<typename Result>
46class [[nodiscard]] async_generator {
47public:
57 public:
59 using value_type = std::remove_reference_t<Result>;
61 using pointer = std::add_pointer_t<value_type>;
62
68 promise_type() noexcept = default;
69
73 ~promise_type() = default;
74
76 promise_type(const promise_type&) = delete;
77 promise_type(promise_type&&) noexcept = delete;
78 promise_type& operator=(const promise_type&) = delete;
81
89 auto get_return_object() noexcept { return async_generator<Result>{*this}; }
90
100 [[nodiscard]] constexpr auto initial_suspend() const noexcept { return std::suspend_always{}; }
101
111 auto final_suspend() noexcept
112 {
113 this->m_current_value = nullptr;
114 return yield_op{this->m_consumer};
115 }
116
125 void unhandled_exception() noexcept { this->m_exception = std::current_exception(); }
126
132 constexpr void return_void() const noexcept {}
133
145 auto yield_value(value_type& value) noexcept
146 {
147 this->m_current_value = std::addressof(value);
148 return yield_op{this->m_consumer};
149 }
150
164 auto yield_value(std::add_rvalue_reference_t<std::remove_cv_t<value_type>> value) noexcept
165 {
166 this->m_current_value = std::addressof(value);
167 return yield_op{this->m_consumer};
168 }
169
177 [[nodiscard]] constexpr std::add_lvalue_reference_t<value_type> value() const noexcept
178 {
179 return *this->m_current_value;
180 }
181
187 [[nodiscard]] constexpr bool finished() const noexcept { return this->m_current_value == nullptr; }
188
198 void rethrow_if_unhandled_exception()
199 {
200 if (this->m_exception) {
201 std::rethrow_exception(std::move(this->m_exception));
202 }
203 }
204
213 void set_consumer(std::coroutine_handle<> consumer) noexcept { this->m_consumer = consumer; }
214
215 private:
217 pointer m_current_value = nullptr;
219 std::coroutine_handle<> m_consumer;
221 std::exception_ptr m_exception = nullptr;
222
223 // NOLINTBEGIN(readability-convert-member-functions-to-static)
230 class yield_op {
231 public:
236 constexpr explicit yield_op(std::coroutine_handle<> consumer) noexcept : m_consumer(consumer) {}
237
245 [[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
246
255 [[nodiscard]] constexpr auto await_suspend([[maybe_unused]] std::coroutine_handle<> h) const noexcept
256 {
257 return this->m_consumer;
258 }
259
265 constexpr void await_resume() const noexcept {}
266
267 private:
269 std::coroutine_handle<> m_consumer;
270 };
271 // NOLINTEND(readability-convert-member-functions-to-static)
272
274 };
275
285 class iterator {
286 public:
288
297 class advance_op {
298 public:
307 constexpr advance_op() noexcept : m_promise(nullptr), m_producer(nullptr) {}
308
316 explicit advance_op(std::coroutine_handle<promise_type> producer) noexcept
317 : m_promise(std::addressof(producer.promise())), m_producer(producer)
318 {}
319
332 [[nodiscard]] constexpr bool await_ready() const noexcept { return this->m_promise == nullptr; }
333
342 constexpr std::coroutine_handle<> await_suspend(std::coroutine_handle<> consumer) noexcept
343 {
344 this->m_promise->set_consumer(consumer);
345 return this->m_producer;
346 }
347
364 iterator await_resume()
365 {
366 if (this->m_promise == nullptr) {
367 return iterator{nullptr};
368 }
369
370 if (this->m_promise->finished()) {
371 this->m_promise->rethrow_if_unhandled_exception();
372 return iterator{nullptr};
373 }
374
375 return iterator{std::coroutine_handle<promise_type>::from_promise(*this->m_promise)};
376 }
377
378 private:
380 promise_type* m_promise;
382 std::coroutine_handle<> m_producer;
383 };
384
386
388 using iterator_concept = std::input_iterator_tag;
389
394 using difference_type = std::ptrdiff_t;
395
397
398 using value_type = std::remove_reference_t<Result>;
400 using reference = std::add_lvalue_reference_t<value_type>;
402 using pointer = std::add_pointer_t<value_type>;
403
411 explicit iterator(std::coroutine_handle<promise_type> coroutine) noexcept : m_coroutine(coroutine) {}
413
423 [[nodiscard]] advance_op operator++()
424 {
425 if (detail::is_good_handle(this->m_coroutine)) {
426 return advance_op{this->m_coroutine};
427 }
428
430 throw bad_result_access{"Incrementing past the end of the generator"};
431 }
432
441 reference operator*() const
442 {
443 if (detail::is_good_handle(this->m_coroutine)) [[likely]] {
444 return this->m_coroutine.promise().value();
445 }
446
448 throw bad_result_access{"Dereferencing the end of the generator"};
449 }
450
466 friend bool operator==(const iterator& it, [[maybe_unused]] std::default_sentinel_t sentinel) noexcept
467 {
468 return !detail::is_good_handle(it.m_coroutine);
469 }
470
482 bool operator==(const iterator& other) const noexcept
483 {
484 return this->m_coroutine == other.m_coroutine ||
485 (!detail::is_good_handle(this->m_coroutine) && !detail::is_good_handle(other.m_coroutine));
486 }
487
488 private:
489 std::coroutine_handle<promise_type> m_coroutine;
490 };
491
500 constexpr async_generator() noexcept = default;
501
511 constexpr async_generator(async_generator&& other) noexcept : m_coroutine(std::exchange(other.m_coroutine, nullptr))
512 {}
513
524 {
525 if (this->m_coroutine) {
526 this->m_coroutine.destroy();
527 }
528 }
529
543 {
544 if (this != std::addressof(other)) {
545 if (this->m_coroutine) {
546 this->m_coroutine.destroy();
547 }
548
549 this->m_coroutine = std::exchange(other.m_coroutine, nullptr);
550 }
551
552 return *this;
553 }
554
556 async_generator(const async_generator&) = delete;
557 async_generator& operator=(const async_generator&) = delete;
559
575 [[nodiscard]] auto begin() noexcept
576 {
577 if (!detail::is_good_handle(this->m_coroutine)) {
578 return typename iterator::advance_op();
579 }
580
581 return typename iterator::advance_op{this->m_coroutine};
582 }
583
591 [[nodiscard]] constexpr auto end() const noexcept { return std::default_sentinel; }
592
593private:
594 std::coroutine_handle<promise_type> m_coroutine;
595
601 explicit async_generator(promise_type& promise) noexcept
602 : m_coroutine(std::coroutine_handle<promise_type>::from_promise(promise))
603 {}
604};
605
606template<typename AsyncGenerator, typename Callable>
607task<void> async_for_each(AsyncGenerator gen, Callable callable)
608{
609 auto it = co_await gen.begin();
610 auto end = gen.end();
611
612 while (it != end) {
613 std::invoke(std::forward<Callable>(callable), *it);
614 co_await ++it;
615 }
616}
617
622
623} // namespace wwa::coro
624
625#endif /* C16AFA99_7780_4436_8BC0_60F24EF771A4 */
An input iterator that asynchronously produces values of type Result.
friend bool operator==(const iterator &it, std::default_sentinel_t sentinel) noexcept
Compares the iterator it with the sentinel sentinel.
std::input_iterator_tag iterator_concept
The strongest iterator concept supported by the iterator.
advance_op operator++()
Advances the iterator to the next value.
std::ptrdiff_t difference_type
Difference between the addresses of any two elements in the controlled sequence.
bool operator==(const iterator &other) const noexcept
Compares two iterators for equality.
reference operator*() const
Dereferences the iterator to obtain the current value.
The promise type of the generator.
An asynchronous generator that produces values of type Result.
constexpr async_generator() noexcept=default
Default constructor.
async_generator & operator=(async_generator &&other) noexcept
Move assignment operator.
constexpr auto end() const noexcept
Returns a sentinel iterator.
auto begin() noexcept
Returns an awaitable iterator to the current item of the generator.
Exception thrown when accessing a result that is not available.
Definition exceptions.h:23
A coroutine task.
Definition task.h:196
Internal utility functions for coroutine handling.
Custom exception classes for coroutine handling.
Library namespace.
task< void > async_for_each(AsyncGenerator gen, Callable callable)
Coroutine-based task.