Skip to content

Commit

Permalink
first
Browse files Browse the repository at this point in the history
  • Loading branch information
kelbon committed Jul 28, 2023
1 parent 47c9add commit 48f2ff3
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 143 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
################################################################################

/build
/.cache
/.vscode
13 changes: 13 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 5,
"configurePresets": [
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "1"
}
}
]
}
3 changes: 2 additions & 1 deletion include/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ concept co_awaitable = has_member_co_await<T> || has_global_co_await<T> || co_aw
// TODO support trailing allocator convention
template <typename Alloc>
struct memory_block {
// TODO support coroutine traits <Alloc>
// leading allocator convention
template <typename... Args>
static void* operator new(std::size_t frame_size, std::allocator_arg_t, Alloc resource, Args&&...) {
Expand Down Expand Up @@ -124,7 +125,7 @@ struct [[nodiscard("co_await it!")]] transfer_control_to {
std::coroutine_handle<void> await_suspend(std::coroutine_handle<void>) noexcept {
return who_waits; // symmetric transfer here
}
void await_resume() const noexcept {
static constexpr void await_resume() noexcept {
}
};

Expand Down
279 changes: 161 additions & 118 deletions include/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,173 +4,216 @@
#include <iterator>
#include <memory>
#include <utility>
#include <ranges>

#include "common.hpp"

#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-attributes"
#endif

namespace dd {

struct input_and_output_iterator_tag : std::input_iterator_tag, std::output_iterator_tag {};
template <typename>
struct elements_of_t;

template <typename Yield>
struct generator_promise_base {
// invariant: root != nullptr
generator_promise_base* root = this;
// invariant: never nullptr, initialized when generator created
Yield* current_result;
std::coroutine_handle<> current_worker = get_return_object();
std::coroutine_handle<> owner = std::noop_coroutine();

template <typename Yield, typename Alloc>
struct generator_promise : memory_block<Alloc> {
Yield* current_result = nullptr;
generator_promise_base() = default;

// value type required, so no allocator traits(support memory resources too!)
static_assert(std::is_same_v<std::byte, typename Alloc::value_type>);
generator_promise_base(generator_promise_base&&) = delete;
void operator=(generator_promise_base&&) = delete;

static constexpr std::suspend_always initial_suspend() noexcept {
auto get_return_object() noexcept {
return std::coroutine_handle<generator_promise_base>::from_promise(*this);
}
std::suspend_always yield_value(Yield& lvalue) noexcept {
root->current_result = std::addressof(lvalue);
return {};
}
static constexpr std::suspend_always final_suspend() noexcept {
std::suspend_always yield_value(Yield&& rvalue) noexcept {
root->current_result = std::addressof(rvalue);
return {};
}
transfer_control_to yield_value(elements_of_t<Yield>&&);

static constexpr std::suspend_never initial_suspend() noexcept {
return {};
}
auto get_return_object() {
return std::coroutine_handle<generator_promise>::from_promise(*this);
transfer_control_to final_suspend() const noexcept {
assert(root && !root->current_worker.done());
root->current_worker = owner;// TODO in awaiter?
// TODO почему то не считается .done() после final suspend
return transfer_control_to{owner}; // noop coro if done
}
static constexpr void return_void() noexcept {
}
[[noreturn]] void unhandled_exception() const {
throw;
}

auto await_transform(get_handle_t) const noexcept {
return return_handle_t<generator_promise>{};
// interface for iterator, used only on top-level generator

bool done() noexcept {
return get_return_object().done();
}
template <typename T>
decltype(auto) await_transform(T&& v) const noexcept {
return build_awaiter(std::forward<T>(v));
void produce_next() {
assert(root == this && !done());

current_worker.resume();
}
// yield things
};
// TODO
// template <typename Yield, typename Alloc>
// struct generator_promise : generator_promise_base<Yield>, memory_block<Alloc> {
// static_assert(std::is_same_v<std::byte, typename Alloc::value_type>);
//};

template <typename Yield>
struct giterator {
private:
struct save_value_before_resume_t {
Yield saved_value;

bool await_ready() const noexcept {
return false;
}
void await_suspend(std::coroutine_handle<generator_promise> handle) noexcept {
handle.promise().current_result = std::addressof(saved_value);
}
void await_resume() const noexcept {
}
};
std::coroutine_handle<> handle;
generator_promise_base<Yield>* promise = nullptr;

public:
// lvalue
std::suspend_always yield_value(Yield& lvalue) noexcept {
current_result = std::addressof(lvalue);
return {};
giterator() noexcept;
giterator(generator_promise_base<Yield>* p) noexcept;

using iterator_category = std::input_iterator_tag;
using value_type = Yield;
using difference_type = ptrdiff_t;

bool operator==(std::default_sentinel_t) const noexcept {
return handle.done();
}
// rvalue or some type which is convertible to Yield
template <typename U>
auto yield_value(U&& value) noexcept(std::is_nothrow_constructible_v<Yield, U&&>) {
return save_value_before_resume_t{Yield(std::forward<U>(value))};
value_type& operator*() const noexcept {
return *promise->current_result;
}
giterator& operator++() {
assert(!promise->done());

[[noreturn]] void unhandled_exception() const {
throw;
promise->produce_next();
return *this;
}
// TODO void
void operator++(int) {
++(*this);
}
};

// synchronous producer
template <typename Yield, typename Alloc = std::allocator<std::byte>>
template <typename Yield>
struct generator {
public:
using value_type = Yield;
using promise_type = generator_promise<Yield, Alloc>;
using handle_type = std::coroutine_handle<promise_type>;

private:
handle_type handle_;
generator_promise_base<Yield>* promise = nullptr;

template <typename Y>
friend struct empty_generator_handle_t;
template <typename Y>
friend struct generator_promise_base;

public:
static_assert(!std::is_reference_v<Yield>);
using value_type = Yield;
// TODO coroutine traits specialization using promise_type = generator_promise_base<Yield>;
using promise_type = generator_promise_base<Yield>;
constexpr generator() noexcept = default;
constexpr generator(handle_type handle) : handle_(handle) {
constexpr generator(std::coroutine_handle<generator_promise_base<Yield>> handle) noexcept
: promise(std::addressof(handle.promise())) {
}
constexpr generator(generator&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {
constexpr generator(generator&& other) noexcept : promise(std::exchange(other.promise, nullptr)) {
}
constexpr generator& operator=(generator&& other) noexcept {
std::swap(handle_, other.handle_);
std::swap(promise, other.promise);
return *this;
}

[[nodiscard]] handle_type release() noexcept {
return std::exchange(handle_, nullptr);
}

~generator() {
if (handle_)
handle_.destroy();
constexpr ~generator() {
if (promise)
promise->get_return_object().destroy();
}

struct iterator {
handle_type owner;

using iterator_category = input_and_output_iterator_tag;
using value_type = Yield;
using difference_type = ptrdiff_t; // requirement of concept input_iterator

bool operator==(std::default_sentinel_t) const noexcept {
return owner.done();
}
value_type& operator*() const noexcept {
return *owner.promise().current_result;
}
iterator& operator++() {
owner.resume();
return *this;
}
// postfix version impossible and logically incorrect for input iterator,
// but it is required for concept of input iterator
iterator operator++(int) {
return ++(*this);
}
};

iterator begin() {
assert(!handle_.done());
handle_.resume();
return iterator{handle_};
giterator<Yield> begin() const noexcept {
return giterator(promise);
}

static std::default_sentinel_t end() noexcept {
static constexpr std::default_sentinel_t end() noexcept {
return std::default_sentinel;
}

// no range-for-loop access
bool has_next() const noexcept {
return !handle_.done();
}
[[nodiscard]] value_type& next() noexcept {
assert(has_next());
handle_.promise.resume();
return *handle_.promise().current_result;
bool empty() const noexcept {
return !promise || promise->done();
}
};

template <std::forward_iterator It, std::sentinel_for<It> Sent>
generator<typename std::iterator_traits<It>::value_type> circular_view(It it, Sent sent) {
while (true) {
for (auto it_ = it; it_ != sent; ++it_)
co_yield *it_;
template <typename Yield>
struct empty_generator_handle_t {
static auto value() noexcept {
// TODO custom alloc
static generator<Yield> g = []() -> generator<Yield> { co_return; }();

return g.promise->get_return_object();
}
};

template <typename Yield>
std::coroutine_handle<generator_promise_base<Yield>> empty_generator_handle() {
return empty_generator_handle_t<Yield>::value();
}
// clang-format off
template <std::ranges::forward_range Rng>
requires(std::ranges::borrowed_range<Rng>)
generator<std::ranges::range_value_t<Rng>> circular_view(Rng&& r) {
// clang-format on
const auto it = std::ranges::begin(r);
const auto sent = std::ranges::end(r);
while (true) {
for (auto it_ = it; it_ != sent; ++it_)
co_yield *it_;
}

template <typename Yield>
transfer_control_to generator_promise_base<Yield>::yield_value(elements_of_t<Yield>&& e) {
if (e.g.empty()) {
// continue execution while not yields a real value
return transfer_control_to{get_return_object()};
}
auto& p = *e.g.promise;
std::coroutine_handle h = e.g.promise->get_return_object();
p.root = root;
p.owner = get_return_object();
root->current_worker = h;
root->current_result = p.current_result; // first value was created by e.g itself
return transfer_control_to{std::noop_coroutine()};
}
template <typename First, std::same_as<First>... Ts>
generator<First> fixed_circular_view(First& f, Ts&... args) {
while (true) {
co_yield f;
(co_yield args, ...);
}

template <typename Yield>
giterator<Yield>::giterator() noexcept : handle(empty_generator_handle<Yield>()) {
}
template <typename Yield>
giterator<Yield>::giterator(generator_promise_base<Yield>* p) noexcept
: handle(p ? p->get_return_object() : empty_generator_handle<Yield>()), promise(p) {
}

template <typename Yield>
struct elements_of_t {
generator<Yield> g;
};
template <typename T>
elements_of_t(generator<T>) -> elements_of_t<T>;

template <std::ranges::range R>
[[nodiscard]] auto elements_of(R&& r) {
auto g = [&]() -> generator<std::ranges::range_value_t<R>> {
for (auto&& x : r)
co_yield x;
}();

return elements_of_t{g};
}
// TODO &&g
template <typename Yield>
[[nodiscard]] constexpr elements_of_t<Yield> elements_of(generator<Yield> g) noexcept {
return elements_of_t{std::move(g)};
}
// TODO ? consumer

} // namespace dd
} // namespace dd

#if __clang__
#pragma clang diagnostic pop
#endif
11 changes: 11 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,16 @@ set_target_properties(kelcorotest PROPERTIES
CXX_STANDARD 20
)

add_executable(generator_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests_generator.cpp)

target_link_libraries(generator_tests PUBLIC kelcorolib)
set_target_properties(generator_tests PROPERTIES
CMAKE_CXX_EXTENSIONS OFF
LINKER_LANGUAGE CXX
CMAKE_CXX_STANDARD_REQUIRED ON
CXX_STANDARD 20
)
add_test(NAME generator_tests COMMAND generator_tests)

add_test(NAME test_kelcorotest COMMAND kelcorotest)

Loading

0 comments on commit 48f2ff3

Please sign in to comment.