2
\$\begingroup\$

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;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.