Submitted for review ... A co-routine class called generator
that co_yields values of a parameterized type. I am interested of course in whether it is bug-free. I would also like to know where noexcept
would be proper and good. Finally, remarks about exception-handling would be welcome. At present there is none.
There are two ways to use the class. One is with member functions resume()
and operator()
. The other is via iterators. (The usages could be mixed, but I see no reason someone would do that.) Both manners of using the class are demonstrated in the "Demo stuff" at the bottom of this file.
#include <experimental/coroutine>
namespace dj{
using std::experimental::coroutine_handle;
template <typename Type>
class generator
{
bool done() { return coro_handle_.done(); }
Type const *cur_val() { return coro_handle_.promise().cur_value_; }
public:
bool resume() {
if (done())
return false;
coro_handle_.resume();
return !done();
}
[[nodiscard]]
const Type& operator() ()
{ return *cur_val(); }
// To be used with generators that never finish.
const Type& get() {
if (!done())
coro_handle_.resume();
return *cur_val();
}
struct promise_type
{
Type const *cur_value_ = nullptr;
bool initial_suspend() { return true; }
bool final_suspend() { return true; }
promise_type &get_return_object() { return *this; }
void yield_value(Type const &value)
{ cur_value_ = std::addressof(value); }
};
struct iterator
{
using value_type = Type;
using iterator_category = std::input_iterator_tag;
using pointer = Type const *;
using difference_type = ptrdiff_t;
using reference = Type const &;
coroutine_handle<promise_type> coroutine_ = nullptr;
iterator() = default;
iterator(nullptr_t) : coroutine_(nullptr)
{}
iterator(coroutine_handle<promise_type> coroutine_Arg)
: coroutine_(coroutine_Arg)
{}
iterator &operator++() {
coroutine_.resume();
if (coroutine_.done())
coroutine_ = nullptr;
return *this;
}
void operator++(int)
{ ++*this; }
[[nodiscard]] bool operator==(iterator const &rhs_) const
{ return coroutine_ == rhs_.coroutine_; }
[[nodiscard]] bool operator!=(iterator const &rhs_) const
{ return !(*this == rhs_); }
[[nodiscard]] reference operator*() const
{ return *coroutine_.promise().cur_value_; }
[[nodiscard]] pointer operator->() const
{ return coroutine_.promise().cur_value_; }
};
[[nodiscard]] iterator begin()
{
if (coro_handle_) {
coro_handle_.resume();
if (coro_handle_.done())
return { nullptr };
}
return { coro_handle_ };
}
[[nodiscard]] iterator end()
{ return { nullptr }; }
explicit generator(promise_type &promise_)
: coro_handle_(coroutine_handle<promise_type>::from_promise(promise_))
{}
generator() = default;
generator(generator const &) = delete;
generator &operator=(generator const &) = delete;
generator(generator &&rval_) : coro_handle_(rval_.coro_handle_)
{ rval_.coro_handle_ = nullptr; }
~generator() { if (coro_handle_) { coro_handle_.destroy(); } }
generator &operator=(generator &&rhs_)
{
if (this != std::addressof(rhs_)) {
coro_handle_ = rhs_.coro_handle_;
rhs_.coro_handle_ = nullptr;
}
return *this;
}
private:
coroutine_handle<promise_type> coro_handle_ = nullptr;
};
}
//////////// Demo stuff follows. ///////////////
#include <string_view>
#include <iostream>
static
void press_enter(std::string_view const sv = "\nPress enter...") {
std::cout << sv;
std::cin.ignore(500, '\n');
}
static // A generator that yields all the substrings of a string
dj::generator<std::string_view>
substrings(std::string_view const s) {
const auto m = s.length();
for (auto n = m; n > 0; --n) {
for (auto i = 0u; i <= m-n; ++i) {
co_yield s.substr(i, n);
}
}
}
int main() {
// coro.resume() resumes the generator to yield
// the next value if any. Returns false when generator
// is done.
auto coro = substrings("foobar");
std::cout << "Press enter 21 times...";
while ( coro.resume()) {
press_enter("");
std::cout << coro(); // Returns const reference to yielded value.
}
press_enter();
std::cout << "\n...................." << std::endl;
// Iterator interface...
for(auto str: substrings("baroque")) {
std::cout << '\n' << str;
}
press_enter("\n\nPress enter to exit.");
return 0;
}