Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
C++20 generator with nice semantics for simple generator<T>
#pragma once
#include <iterator>
#include <utility>
#include <coroutine>
// Only need one of these. Shouldn't depend on generator's T.
struct generator_sentinel {};
template <typename T>
struct generator {
using Pointer = std::add_pointer_t<T>; // strips reference.
struct promise_type {
std::suspend_never initial_suspend() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
auto yield_value(const T& v) requires(!std::is_reference_v<T> &&
std::copy_constructible<T>) {
struct Owner : std::suspend_always {
Owner(const T& val, Pointer& out) : v(val) {
out = &v;
}
Owner(Owner&&) = delete;
T v;
};
return Owner(v, ptr);
}
auto yield_value(T&& v) { // lval ref if T is lval ref
ptr = &v;
return std::suspend_always();
}
auto get_return_object() {
return generator(this);
}
void return_void() {}
void unhandled_exception() {
throw;
}
auto coro() {
return std::coroutine_handle<promise_type>::from_promise(*this);
}
Pointer ptr = {};
};
generator(generator&& source) : p(std::exchange(source.p, nullptr)) {}
explicit generator(promise_type* p) : p(p) {}
~generator() {
if (p)
p->coro().destroy();
}
// generator<T> is a Range. You can use it in a range-for loop.
using sentinel = generator_sentinel;
struct iterator {
using iterator_concept = std::input_iterator_tag;
using difference_type = ptrdiff_t;
using value_type = std::remove_cvref_t<T>;
bool operator==(sentinel) const {
return p->coro().done();
}
iterator& operator++() {
p->ptr = {};
p->coro().resume();
return *this;
}
void operator++(int) {
operator++();
}
Pointer operator->() const {
return p->ptr;
}
T&& operator*() const { // lval ref if T is lval ref
return static_cast<T&&>(*p->ptr);
}
promise_type* p;
};
auto begin() {
return iterator{p};
}
auto end() {
return sentinel();
}
promise_type* p;
};
#ifdef __cpp_lib_ranges
static_assert(std::input_iterator<generator<int>::iterator>);
static_assert(std::ranges::input_range<generator<int>>);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment