协程库:https://godbolt.org/z/icfqLr
说明: 以下代码在Ubuntu18.04 + GCC10上编译,GCC10构建方法参看【C++20】GCC10.1构建经验
coroutines.hpp
#ifndef COROUTINE_HPP
#define COROUTINE_HPP
#if __has_include(<coroutine>)
#include <coroutine>
#else
#include <experimental/coroutine>
namespace std {
using std::experimental::suspend_always;
using std::experimental::suspend_never;
using std::experimental::noop_coroutine;
using std::experimental::coroutine_handle;
}
#endif
#if __has_include(<ranges>)
#include <ranges>
#endif
#include <type_traits>
#include <iterator>
#include <cstdio>
#include <exception>
#include <utility>
template<typename Ref, typename Value = std::remove_cvref_t<Ref>>
class generator {
public:
class promise_type {
public:
promise_type() : root_(this) {}
generator get_return_object() noexcept {
return generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
void unhandled_exception() {
if (exception_ == nullptr)
throw;
*exception_ = std::current_exception();
}
void return_void() noexcept {}
std::suspend_always initial_suspend() noexcept { return {}; }
// Transfers control back to the parent of a nested coroutine
struct final_awaiter {
bool await_ready() noexcept {
return false;
}
std::coroutine_handle<> await_suspend(
std::coroutine_handle<promise_type> h) noexcept {
auto& promise = h.promise();
auto parent = h.promise().parent_;
if (parent) {
promise.root_->leaf_ = parent;
return std::coroutine_handle<promise_type>::from_promise(*parent);
}
return std::noop_coroutine();
}
void await_resume() noexcept {}
};
final_awaiter final_suspend() noexcept { return {}; }
std::suspend_always yield_value(Ref&& x) noexcept {
root_->value_ = std::addressof(x);
return {};
}
std::suspend_always yield_value(Ref& x) noexcept {
root_->value_ = std::addressof(x);
return {};
}
struct yield_sequence_awaiter {
generator gen_;
std::exception_ptr exception_;
explicit yield_sequence_awaiter(generator&& g) noexcept
// Taking ownership of the generator ensures frame are destroyed
// in the reverse order of their creation
: gen_(std::move(g))
{}
bool await_ready() noexcept {
return !gen_.coro_;
}
// set the parent, root and exceptions pointer and
// resume the nested coroutine
std::coroutine_handle<> await_suspend(
std::coroutine_handle<promise_type> h) noexcept {
auto& current = h.promise();
auto& nested = gen_.coro_.promise();
auto& root = current.root_;
nested.root_ = root;
root->leaf_ = &nested;
nested.parent_ = ¤t;
nested.exception_ = &exception_;
// Immediately resume the nested coroutine (nested generator)
return gen_.coro_;
}
void await_resume() {
if (exception_) {
std::rethrow_exception(std::move(exception_));
}
}
};
yield_sequence_awaiter yield_value(generator&& g) noexcept {
return yield_sequence_awaiter{std::move(g)};
}
void resume() {
std::coroutine_handle<promise_type>::from_promise(*leaf_).resume();
}
// Disable use of co_await within this coroutine.
void await_transform() = delete;
private:
friend generator;
// Technically UB, for demonstration purpose
union {
promise_type* root_;
promise_type* leaf_;
};
promise_type* parent_ = nullptr;
std::exception_ptr* exception_ = nullptr;
std::add_pointer_t<Ref> value_;
};
generator() noexcept = default;
generator(generator&& other) noexcept
: coro_(std::exchange(other.coro_, {}))
{}
~generator() noexcept {
if (coro_) {
coro_.destroy();
}
}
generator& operator=(generator g) noexcept {
swap(g);
return *this;
}
void swap(generator& other) noexcept {
std::swap(coro_, other.coro_);
}
struct sentinel {};
class iterator {
using coroutine_handle = std::coroutine_handle<promise_type>;
public:
using iterator_category = std::input_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = Value;
using reference = Ref;
using pointer = std::add_pointer_t<Ref>;
iterator() noexcept = default;
iterator(const iterator &) = delete;
iterator(iterator && o) {
std::swap(coro_, o.coro_);
}
iterator &operator=(iterator && o) {
std::swap(coro_, o.coro_);
return *this;
}
~iterator() {}
friend bool operator==(const iterator &it, sentinel) noexcept {
return !it.coro_ || it.coro_.done();
}
iterator &operator++() {
coro_.promise().resume();
return *this;
}
void operator++(int) {
(void)operator++();
}
reference operator*() const noexcept {
return static_cast<reference>(*coro_.promise().value_);
}
pointer operator->() const noexcept
requires std::is_reference_v<reference> {
return std::addressof(operator*());
}
private:
friend generator;
explicit iterator(coroutine_handle coro) noexcept
: coro_(coro) {}
coroutine_handle coro_;
};
iterator begin() {
if (coro_) {
coro_.resume();
}
return iterator{coro_};
}
sentinel end() noexcept {
return {};
}
private:
explicit generator(std::coroutine_handle<promise_type> coro) noexcept
: coro_(coro)
{}
std::coroutine_handle<promise_type> coro_;
};
#if __has_include(<ranges>)
template <typename T, typename U>
constexpr inline bool std::ranges::enable_view<generator<T, U>> = true;
#endif
#endif // COROUTINE_HPP
测试代码1:
#include <iostream>
#include <filesystem> // C++17
#include "coroutine.hpp"
using namespace std;
generator<filesystem::directory_entry> list_directory(filesystem::path path)
{
for(auto entry: filesystem::directory_iterator(path))
{
co_yield entry;
if(is_directory(entry))
co_yield list_directory(entry);
}
}
int main()
{
auto files = list_directory(filesystem::path("."));
for(auto f: files)
std::cout<<f.path()<<"\n";
}
构建:
g++10 -std=c++2a -g -fcoroutines -o main main.cpp -I/usr/local/gcc-10.1.0/include/c++/10.1.0 -Wl,-rpath=/usr/local/gcc-10.1.0/lib64
测试代码2:
#include <iostream>
#include <cstdio>
#include "coroutine.hpp"
struct some_resource {
int x;
explicit some_resource(int x) : x(x) {
std::printf("some_resource(%i)\n", x);
}
~some_resource() {
std::printf("~some_resource(%i)\n", x);
}
};
static generator<const uint64_t> fib(int max) {
some_resource r{2};
auto a = 0, b = 1 ;
for(auto n = 0; n < max; n ++) {
co_yield b;
const auto next = a + b;
a = b, b = next;
}
}
static generator<const uint64_t> test(int max) {
auto g = fib(max);
some_resource r{1};
co_yield std::move(g);
}
int main() {
setbuf(stdout, NULL);
#if __has_include(<ranges>)
auto v = test(10) | std::views::drop(9);
return *std::ranges::begin(v);
#else
uint64_t i = 0;
for(auto a : test(10)) {
i = a;
}
return i;
#endif
}
构建:
g++10 -std=c++2a -g -fcoroutines -o main main.cpp -I/usr/local/gcc-10.1.0/include/c++/10.1.0 -Wl,-rpath=/usr/local/gcc-10.1.0/lib64