Итак, я пытаюсь понять эту новую и сложную концепцию сопрограмм. Взял Clang для этого, компиляция через clang++ -std=c++17 -fcoroutines-ts -stdlib=libc++
идет нормально.
Одним из наиболее полезных понятий является task<>
тип сопрограммы, он упоминается здесь и даже имеет пару интересных реализаций, от Gor Nishanov и в библиотеке cppcoro .
Ладно, выглядел хорошо, чтобы попробовать себя в простейшем случае. Итак, цель состоит в том, чтобы реализовать что-то, что должно работать следующим образом:
{
auto producer = []() -> task<int> {
co_return 1;
};
auto t = producer();
assert(!t.await_ready());
assert(t.result() == 1);
assert(t.await_ready());
}
Сам шаблон класса task<>
был сделан довольно просто:
#pragma once
#include <experimental/coroutine>
#include <optional>
namespace stdx = std::experimental;
template <typename T=void>
struct task
{
template<typename U>
struct task_promise;
using promise_type = task_promise<T>;
using handle_type = stdx::coroutine_handle<promise_type>;
mutable handle_type m_handle;
task(handle_type handle)
: m_handle(handle)
{}
task(task&& other) noexcept
: m_handle(other.m_handle)
{ other.m_handle = nullptr; };
bool await_ready()
{ return m_handle.done(); }
bool await_suspend(stdx::coroutine_handle<> handle)
{
if (!m_handle.done()) {
m_handle.resume();
}
return false;
}
auto await_resume()
{ return result(); }
T result() const
{
if (!m_handle.done())
m_handle.resume();
if (m_handle.promise().m_exception)
std::rethrow_exception(m_handle.promise().m_exception);
return *m_handle.promise().m_value;
}
~task()
{
if (m_handle)
m_handle.destroy();
}
template<typename U>
struct task_promise
{
std::optional<T> m_value {};
std::exception_ptr m_exception = nullptr;
auto initial_suspend()
{ return stdx::suspend_always{}; }
auto final_suspend()
{ return stdx::suspend_always{}; }
auto return_value(T t)
{
m_value = t;
return stdx::suspend_always{};
}
task<T> get_return_object()
{ return {handle_type::from_promise(*this)}; }
void unhandled_exception()
{ m_exception = std::current_exception(); }
void rethrow_if_unhandled_exception()
{
if (m_exception)
std::rethrow_exception(std::move(m_exception));
}
};
};
Не могу сделать небольшой фрагмент кода полным и компилируемым, извините. В любом случае это работало как-то, но все еще оставался случай task<void>
, его использование могло бы быть следующим:
{
int result = 0;
auto int_producer = []() -> task<int> {
co_return 1;
};
auto awaiter = [&]() -> task<> { // here problems begin
auto i1 = co_await int_producer();
auto i2 = co_await int_producer();
result = i1 + i2;
};
auto t = awaiter();
assert(!t.await_ready());
t.await_resume();
assert(result == 2);
}
Последнее совсем не казалось проблемой, похоже, что task_promise<U>
требует специализации для void
(это может быть не шаблонная структура без этого пустого регистра). Итак, я попробовал это:
template<>
struct task_promise<void>
{
std::exception_ptr m_exception;
void return_void() noexcept {}
task<void> get_return_object() noexcept
{ return {handle_type::from_promise(*this)}; }
void unhandled_exception()
{ m_exception = std::current_exception(); }
auto initial_suspend()
{ return stdx::suspend_always{}; }
auto final_suspend()
{ return stdx::suspend_always{}; }
};
Аккуратный и простой ... и он вызывает segfault без читаемой трассировки стека = (
Работает нормально, когда task<>
заменен на любой не пустой шаблон, например task<char>
.
Что не так с моей специализацией по шаблонам? Или мне не хватает какой-то хитрой концепции с этими сопрограммами?
Буду благодарен за любые идеи.