В статье рассматривается заголовочный компонент execution_core::Task, предназначенный для использования как return object coroutine-функций C++20.
Coroutine-функция в C++20 — это функция, в теле которой используется co_await, co_yield или co_return [2]. Для coroutine-функции promise type определяется через возвращаемый тип функции и std::coroutine_traits. [1]
Рассматриваемая реализация:
задаёт
promise_typeдляTask<void>иTask<T>приT != void;задаёт две политики начальной приостановки:
StartSuspendedиStartImmediately;хранит
std::coroutine_handle<promise_type>;уничтожает coroutine state через
coroutine_handle::destroy();сохраняет исключение в
std::exception_ptr;для
Task<T>приT != voidсохраняет результат вstd::optional<T>.
Термин «модуль» далее используется в архитектурном смысле. Код является header-only компонентом. Это не C++20 module unit, так как в нём используется #pragma once, а не export module.
Почему выбран такой Task?
Цель этой реализации - показать универсальный шаблон Task<T, StartPolicy>, в котором выбор поведения задаётся параметрами шаблона, а не набором отдельных несвязанных классов.
Важно: вопросы
co_await Task<T>, ожидания завершения, cancellation/unregister-протокола и scheduler-а относятся к другому уровню реализации и в этот шаблон намеренно не включены.
Параметр T выбирает форму promise object: для T == void используется void_promise, для T != void используется value_promise<T>.
Параметр StartPolicy выбирает поведение initial_suspend(): StartSuspended даёт std::suspend_always, а StartImmediately даёт std::suspend_never.
Таким образом, одна шаблонная форма Task<T, StartPolicy> задаёт return object для нескольких случаев использования coroutine-функций без дублирования основной логики владения std::coroutine_handle.
C++20 coroutines задают языковой механизм приостановки и возобновления coroutine body, но не задают готовый универсальный тип результата для пользовательской coroutine-функции. Coroutine-функция должна иметь return type. Для этого return type компилятор через std::coroutine_traits определяет promise_type [2].
Следовательно, пользовательский тип Task<T, StartPolicy> нужен как тип результата coroutine-функции, через который задаются:
тип promise object;
объект, возвращаемый из coroutine-функции;
способ получить
std::coroutine_handle;способ управлять lifetime coroutine state;
место хранения результата или исключения;
начальное состояние coroutine body после вызова coroutine-функции.
Без такого return object coroutine-функция не получает пользовательского объекта управления. Языковой механизм создаёт coroutine state и promise object, но пользовательскому коду нужен объект, через который этот state будет доступен и уничтожен.
В данной реализации таким объектом является Task<T, StartPolicy>. Он связывает coroutine-функцию, promise object и coroutine handle в один объект владения.
В таком построении нет претензии на оригинальность: этот вариант наверняка не является уникальным и мог быть независимо реализован другими разработчиками. Здесь он рассматривается как один из возможных минимальных вариантов пользовательского return object для C++20 coroutine-функций. Материал может быть полезен тем, кто хочет явно увидеть, как связаны promise_type, std::coroutine_handle, initial_suspend(), final_suspend() и lifetime coroutine state.
Исходный код
Исходный код Task
#pragma once #include <coroutine> #include <exception> #include <optional> #include <type_traits> #include <utility> #include <cassert> namespace execution_core { struct StartImmediately {}; struct StartSuspended {}; template<typename T = void, typename StartPolicy = StartSuspended> class Task { static_assert( std::is_same_v<StartPolicy, StartImmediately> || std::is_same_v<StartPolicy, StartSuspended> ); private: template<typename Promise> struct base_promise { std::exception_ptr exception; Task get_return_object() noexcept { return Task{ std::coroutine_handle<Promise>::from_promise( static_cast<Promise&>(*this) ) }; } auto initial_suspend() noexcept { if constexpr (std::is_same_v<StartPolicy, StartImmediately>) { return std::suspend_never{}; } else { return std::suspend_always{}; } } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { exception = std::current_exception(); } }; struct void_promise final : base_promise<void_promise> { void return_void() noexcept {} }; template<typename U> struct value_promise final : base_promise<value_promise<U>> { std::optional<U> result; template<typename V> void return_value(V&& value) { result.emplace(std::forward<V>(value)); } }; public: using promise_type = std::conditional_t< std::is_void_v<T>, void_promise, value_promise<T> >; using handle_type = std::coroutine_handle<promise_type>; public: Task() noexcept = default; explicit Task(handle_type handle) noexcept : handle_(handle) {} Task(const Task&) = delete; Task& operator=(const Task&) = delete; Task(Task&& other) noexcept : handle_(std::exchange(other.handle_, {})) {} Task& operator=(Task&& other) noexcept { if (this != &other) { destroy(); handle_ = std::exchange(other.handle_, {}); } return *this; } ~Task() { destroy(); } void start() { if constexpr (std::is_same_v<StartPolicy, StartSuspended>) { if (handle_ && !handle_.done()) { handle_.resume(); } } } bool done() const noexcept { return !handle_ || handle_.done(); } void rethrow_if_exception() { if (handle_ && handle_.promise().exception) { std::rethrow_exception(handle_.promise().exception); } } handle_type native_handle() const noexcept { return handle_; } explicit operator bool() const noexcept { return static_cast<bool>(handle_); } template<typename U = T> requires (!std::is_void_v<U>) U& result() & { assert(handle_); assert(handle_.done()); rethrow_if_exception(); assert(handle_.promise().result.has_value()); return *handle_.promise().result; } template<typename U = T> requires (!std::is_void_v<U>) const U& result() const& { assert(handle_); assert(handle_.done()); if (handle_.promise().exception) { std::rethrow_exception(handle_.promise().exception); } assert(handle_.promise().result.has_value()); return *handle_.promise().result; } template<typename U = T> requires (!std::is_void_v<U>) U&& result() && { assert(handle_); assert(handle_.done()); rethrow_if_exception(); assert(handle_.promise().result.has_value()); return std::move(*handle_.promise().result); } private: void destroy() noexcept { if (handle_) { handle_.destroy(); handle_ = {}; } } private: handle_type handle_{}; }; } // namespace execution_core
Область ответственности Task
Task<T, StartPolicy> задаёт return object coroutine-функции.
Return object coroutine-функции создаётся через вызов promise.get_return_object(). Этот вызов предшествует вызову promise.initial_suspend() и выполняется не более одного раза [1].
В данной реализации Task не является scheduler, event loop или thread pool. Он не выбирает поток выполнения и не содержит очереди готовых coroutine handle.
Область ответственности Task состоит из следующих элементов:
определение
promise_type;получение
std::coroutine_handle<promise_type>из promise object;хранение coroutine handle;
уничтожение coroutine state;
доступ к результату для
Task<T>приT != void;хранение необработанного исключения через promise object.
Типы политики запуска
В коде определены два пустых типа: StartImmediately и StartSuspended. Они используются как значения параметра шаблона StartPolicy.
Ограничение допустимых типов задано через static_assert: StartPolicy должен быть либо StartImmediately, либо StartSuspended.
Тип по умолчанию — StartSuspended. Следовательно, Task<T> эквивалентен Task<T, StartSuspended>, а Task<> эквивалентен Task<void, StartSuspended>.
Поддерживаемые формы return type coroutine-функций:
для
void-результата:Task<>,Task<void>,Task<void, StartSuspended>,Task<void, StartImmediately>;для результата-значения при
T != void:Task<T>,Task<T, StartSuspended>,Task<T, StartImmediately>.
Почему разделены void- и value-варианты promise type
Обработка co_return определяется не самим типом Task, а правилами coroutine promise.
Для co_return; или co_return с operand типа void используется p.return_void().
Для co_return expr, где operand является braced-init-list или expression non-void type, используется p.return_value(expr-or-braced-init-list).
При этом если в scope promise type одновременно найдены имена return_void и return_value, программа является ill-formed [1].
Поэтому один общий promise type с обоими методами не соответствует этому ограничению.
В данной реализации выбор выполняется на этапе компиляции:
using promise_type = std::conditional_t< std::is_void_v<T>, void_promise, value_promise<T> >;
Следствие:
Task<void> -> void_promise Task<T>, T != void -> value_promise<T>
То есть для Task<void> существует promise type с return_void(), а для Task<T> при T != void существует promise type с return_value(...).
base_promise
void_promise и value_promise<T> различаются способом обработки co_return.
Общими для них остаются get_return_object(), initial_suspend(), final_suspend(), unhandled_exception() и поле exception.
Поэтому общая часть вынесена в base_promise<Promise>. Это устраняет дублирование одинаковых функций promise object, но сохраняет разные фактические promise types: void_promise и value_promise<T>.
base_promise<Promise> использует фактический тип promise через параметр Promise, потому что std::coroutine_handle<Promise>::from_promise(...) должен получить ссылку именно на фактический promise object [1][3].
В get_return_object() выполняется преобразование static_cast<Promise&>(*this), после чего создаётся coroutine handle через std::coroutine_handle<Promise>::from_promise(...).
Для from_promise стандарт задаёт постусловие addressof(h.promise()) == addressof(p), где p — promise object, из которого создан handle [1].
Последовательность создания coroutine return object
Для coroutine-функции с return type Task<T> при T != void используется Task<T>::promise_type, то есть value_promise<T>.
Для coroutine-функции с return type Task<void> используется Task<void>::promise_type, то есть void_promise.
Последовательность на уровне модели C++20:
вызывается coroutine-функция;
создаётся coroutine state;
в coroutine state создаётся promise object;
вызывается
promise.get_return_object();get_return_object()создаётTask;Taskполучаетstd::coroutine_handle<promise_type>;вызывается
promise.initial_suspend();дальнейшее поведение зависит от результата
initial_suspend().
Эта последовательность соответствует модели coroutine body, где после получения return object выполняется co_await promise.initial_suspend() [1][2].
StartPolicy
Начальное поведение coroutine body определяется результатом promise.initial_suspend().
В этой реализации рассматривается два режима:
StartSuspendedзадаётinitial_suspend() -> std::suspend_always;StartImmediatelyзадаётinitial_suspend() -> std::suspend_never.
std::suspend_always задаёт awaitable object, у которого await_ready() возвращает false [4]. Следовательно, для Task<T, StartSuspended> coroutine body после вызова coroutine-функции остаётся в начальной точке приостановки. Запуск выполняется явно через task.start().
Такой режим нужен, когда coroutine handle должен быть сначала получен, сохранён во внешней структуре управления или передан scheduler-у, и только после этого coroutine body должна начать выполнение.
std::suspend_never задаёт awaitable object, у которого await_ready() возвращает true [5]. Следовательно, для Task<T, StartImmediately> coroutine body не останавливается в начальной точке приостановки и начинает выполнение сразу после создания return object.
StartPolicy в этой реализации задаёт не runtime-флаг, а compile-time выбор результата initial_suspend().
start()
Функция start() имеет действие только для Task<T, StartSuspended>.
Для Task<T, StartImmediately> после подстановки if constexpr тело функции не содержит вызова handle_.resume().
Для StartSuspended выполняются проверки handle_ и !handle_.done(), после чего вызывается handle_.resume().
coroutine_handle::resume() возобновляет выполнение coroutine, на которую ссылается coroutine handle [1][3].
Важно: coroutine_handle::done() имеет precondition: handle должен ссылаться на suspended coroutine. Task::done() и проверка !handle_.done() в start() корректны только при условии, что coroutine в данный момент не выполняется, а находится в suspended state.
resume() допустим только для handle, который ссылается на suspended coroutine, причём coroutine не должна находиться в final suspend point.
Роль std::suspend_always в final_suspend() этой реализации
При завершении coroutine body выполняется co_await promise.final_suspend() [1][2].
В данной реализации final_suspend() всегда возвращает std::suspend_always.
После выполнения co_return результат сохраняется внутри promise object в handle_.promise().result. Исключение сохраняется внутри promise object в handle_.promise().exception.
Метод result() читает эти данные после завершения coroutine body. Поэтому coroutine state должен существовать после завершения coroutine body до момента, когда Task вызовет handle_.destroy().
Если coroutine state был бы уничтожен до вызова result(), доступ к promise object через handle_.promise() был бы невозможен.
Сохранение результата после co_return
Для Task<T> при T != void используется value_promise<T>, внутри которого хранится std::optional<T> result.
std::optional<T> представляет объект, который либо содержит значение типа T, либо не содержит значения [7].
До выполнения co_return value объект result не содержит значения. При выполнении co_return value вызывается promise.return_value(value). В данной реализации это приводит к вызову result.emplace(std::forward<V>(value)).
Следовательно, std::optional<T> используется как storage для результата, который появляется не при создании coroutine state, а при выполнении co_return.
Обработка void-результата
Для void используется void_promise, содержащий return_void().
Для coroutine-функции с return type Task<void> или Task<> при выполнении co_return; используется return_void() [1][2].
Результат-значение в этом случае не хранится.
Доступ к результату
Методы result() существуют только для Task<T> при T != void.
В реализации это задано через constraint:
template<typename U = T> requires (!std::is_void_v<U>)
Следовательно, для Task<void> методы result() не участвуют в overload resolution, а для Task<T> при T != void доступны три overload:
U& result() &; const U& result() const&; U&& result() &&;
Они различаются ref-qualifier-ом функции-члена:
для lvalue-объекта
Task<T> taskвыбираетсяU& result() &;для const lvalue-объекта
const Task<T> taskвыбираетсяconst U& result() const&;для rvalue-объекта
std::move(task).result()выбираетсяU&& result() &&.
Условия корректного вызова result() в данной реализации выражены проверками:
assert(handle_); assert(handle_.done()); assert(handle_.promise().result.has_value());
Следовательно, result() рассчитан на вызов после завершения coroutine body. Перед возвратом результата выполняется проверка сохранённого исключения через rethrow_if_exception().
Роль std::exception_ptr в этой реализации
Исключение, вышедшее из coroutine body, не выбрасывается наружу обычным способом в точке вызова coroutine-функции. Оно обрабатывается через promise.unhandled_exception() [1][2].
В данной реализации unhandled_exception() сохраняет исключение через exception = std::current_exception().
Позже result() вызывает rethrow_if_exception(), и сохранённое исключение повторно выбрасывается через std::rethrow_exception(...).
std::exception_ptr предназначен для хранения ссылки на объект исключения, который затем может быть повторно выброшен [8].
Для Task<T> при T != void сохранённое исключение повторно выбрасывается из result().
Для Task<void> метода result() нет; проверка сохранённого исключения выполняется явным вызовом rethrow_if_exception() после завершения coroutine body.
Владение coroutine handle
В классе хранится handle_type handle_, где handle_type — это std::coroutine_handle<promise_type>.
std::coroutine_handle является handle-типом, который ссылается на coroutine state, но сам по себе не задаёт RAII-владение этим состоянием [3].
Владение задаётся самим Task: копирование запрещено, перемещение разрешено, а деструктор вызывает destroy().
Копирование запрещено, потому что при разрешённом копировании два объекта Task могли бы хранить один и тот же coroutine handle и оба вызвать destroy() для одного coroutine state.
Перемещение разрешено, потому что при перемещении handle передаётся новому объекту, а исходный объект получает пустое значение через std::exchange(other.handle_, {}).
Следствие: в этой модели уничтожение coroutine state связано с одним объектом Task.
Условие для уничтожения coroutine state
destroy() вызывается только при наличии непустого handle. Внутри destroy() выполняется handle_.destroy(), после чего handle_ сбрасывается в пустое значение.
coroutine_handle::destroy() уничтожает coroutine state, на который ссылается handle [1][3].
Вызов handle_.destroy() в деструкторе Task имеет определённое поведение только при следующих условиях:
handle_не был уничтожен через копию, полученную изnative_handle();coroutine не выполняется конкурентно;
coroutine находится в suspended state.
В стандарте указано, что вызов destroy() для coroutine, которая не находится в suspended state, приводит к undefined behavior [1].
Следовательно, владение Task, вызовы resume() и использование handle, полученного через native_handle(), должны быть согласованы внешним управляющим кодом.
native_handle()
Task сам не является scheduler-ом.
Но внешний scheduler или event loop должен иметь возможность получить coroutine handle, чтобы сохранить его и позже вызвать resume().
Для этого предоставлен метод native_handle(). Он возвращает копию std::coroutine_handle<promise_type>, но не передаёт владение coroutine state.
Уничтожение coroutine state в данной модели остаётся обязанностью объекта Task, потому что именно Task вызывает handle_.destroy().
Границы текущей реализации
Данная реализация задаёт return object coroutine-функции и lifetime coroutine state, но не задаёт awaiter protocol.
В классе отсутствуют await_ready(), await_suspend(...), await_resume() и operator co_await().
В C++ coroutine await-expression использует awaiter protocol, включающий await_ready, await_suspend и await_resume [2].
Следовательно, этот класс задаёт return object coroutine-функции Task<T> f();, но не задаёт поведение выражения co_await f().
Для поддержки co_await Task<T> требуется отдельное определение awaiter protocol.
Ограничение для ссылочных и неподдерживаемых типов результата
Для результата используется std::optional<T> result.
std::optional<T> содержит значение типа T как contained value [7].
Следовательно, данная реализация value-варианта определена только для таких T, для которых std::optional<T> является well-formed и для которых выражение result.emplace(std::forward<V>(value)) well-formed для operand конкретного co_return.
Task<T&>, Task<T[]>, Task<function-type> и другие формы, для которых std::optional<T> не может содержать contained value типа T, этой реализацией не поддерживаются.
Сводка типов
Для void-результата:
Task<>эквивалентенTask<void, StartSuspended>;Task<void>эквивалентенTask<void, StartSuspended>;Task<void, StartSuspended>используетvoid_promise,std::suspend_always,return_void();Task<void, StartImmediately>используетvoid_promise,std::suspend_never,return_void().
Для результата-значения при T != void:
Task<T>эквивалентенTask<T, StartSuspended>;Task<T, StartSuspended>используетvalue_promise<T>,std::suspend_always,return_value(...);Task<T, StartImmediately>используетvalue_promise<T>,std::suspend_never,return_value(...).
Итоговая схема:
Task<> -> Task<void, StartSuspended> Task<void> -> Task<void, StartSuspended> Task<void, StartSuspended> -> void_promise, suspend_always, return_void() Task<void, StartImmediately> -> void_promise, suspend_never, return_void() Task<T> -> Task<T, StartSuspended>, T != void Task<T, StartSuspended> -> value_promise<T>, suspend_always, return_value(...) Task<T, StartImmediately> -> value_promise<T>, suspend_never, return_value(...)
Итоговая формулировка
execution_core::Task<T, StartPolicy> — это class template, который задаёт return object для C++20 coroutine-функций.
Параметр T определяет promise type: при T == void используется void_promise, при T != void используется value_promise<T>.
Параметр StartPolicy определяет результат initial_suspend(): StartSuspended даёт std::suspend_always, а StartImmediately даёт std::suspend_never.
Coroutine handle создаётся через std::coroutine_handle<Promise>::from_promise(...) и хранится внутри Task [1][3].
Coroutine state уничтожается деструктором Task через handle_.destroy() при наличии непустого handle [1][3].
В текущей реализации Task является владельцем coroutine handle и обеспечивает доступ к promise object после завершения coroutine body. Он не задаёт awaiter protocol для co_await Task<T>.
Корректное использование этой реализации требует, чтобы операции done(), resume() и destroy() вызывались только в состояниях coroutine, для которых эти операции имеют определённое поведение по стандарту.
Отличие от cppcoro
Этот Task не является заменой cppcoro.
cppcoro предоставляет набор coroutine-примитивов: task<T>, shared_task<T>, генераторы, async-generators, awaitable-типы и функции вроде sync_wait() и when_all().
Рассматриваемый здесь Task решает более узкую задачу: он показывает минимальную структуру пользовательского return object, который:
выбирает
promise_type;хранит
std::coroutine_handle;задаёт
initial_suspend()иfinal_suspend();сохраняет результат или исключение;
уничтожает coroutine state.
В этой реализации нет co_await Task<T>, нет sync_wait(), нет when_all(), нет scheduler-а и нет thread pool. Поэтому сравнивать её с cppcoro как с библиотекой нельзя; это минимальный строительный блок для собственного execution_core.
Источники
-
ISO/IEC 14882:2020(E), разделы:
Coroutine definitions;
Coroutine promise;
Coroutine handle;
Coroutine state lifetime.
cppreference: Coroutines https://en.cppreference.com/w/cpp/language/coroutines
cppreference:
std::coroutine_handlehttps://en.cppreference.com/w/cpp/coroutine/coroutine_handlecppreference:
std::suspend_alwayshttps://en.cppreference.com/w/cpp/coroutine/suspend_alwayscppreference:
std::suspend_neverhttps://en.cppreference.com/w/cpp/coroutine/suspend_nevercppreference:
std::conditionalhttps://en.cppreference.com/w/cpp/types/conditionalcppreference:
std::optionalhttps://en.cppreference.com/w/cpp/utility/optionalcppreference:
std::exception_ptrhttps://en.cppreference.com/w/cpp/error/exception_ptr
Комментарии (10)

mayorovp
05.05.2026 11:36Теперь претензии к коду. Ваш класс Task не поддерживает оператор co_await. Более того, он не предоставляет вообще никакой возможности дождаться окончания сопрограммы. Но при этом окончание сопрограммы требуется для вызова любого из его методов, включая деструктор!
Получается, этот код в принципе невозможно безопасно использовать. Нет, таких реализация нам не надо! Даже в качестве примера.

olegiv2019 Автор
05.05.2026 11:36Да, первая часть замечания верная: текущий
Taskне является awaitable object и не предоставляет самостоятельного механизма ожидания завершения. Данный класс — не аналогcppcoro::task, а минимальный return object и RAII-владелецstd::coroutine_handle.Но утверждение, что для деструктора требуется именно окончание coroutine, неверно. Для
coroutine_handle::destroy()требуется не final suspend, а suspended state. То есть coroutine может быть уничтожена и до завершения, например когда она находится вinitial_suspend()или в другой точке приостановки. Завершение требуется дляresult(), но не для самогоdestroy().
mayorovp
05.05.2026 11:36Только вот уничтожение
coroutine_handleв то время когда его позаимствовал чужой код, обрабатывающийco_await, приведёт к use after free. Уничтожатьcoroutine_handleв промежуточных точках можно только в тех случаях, когда все используемыеawaitableмогут корректно обработать такое уничтожение. А они не могут, потому что связатьawaitableс промисом можно только через методawait_transform, которого у вас тоже нет.
olegiv2019 Автор
05.05.2026 11:36Описываемый Вами сценарий не является режимом использования показанного
Task<T, StartPolicy>.В показанной реализации механизм удаления такой копии из внешних структур ожидания не задан. В моих проектах такой механизм выносится в scheduler/awaiter-слой; это архитектурное решение, и в другой реализации оно может быть организовано иначе. В этой статье этот слой не реализуется и не рассматривается.
По
await_transformуточню отдельно: связь awaitable с promise не обязательно делается только черезawait_transform.await_suspendможет приниматьstd::coroutine_handle<P>и через него обращаться кpromise().Для показанного
Task<T, StartPolicy>сам по себеawait_transformне нужен. Он потребуется только если нужно централизованно преобразовывать всеco_await exprвнутри coroutine body: например, привязывать awaitable к scheduler-у, добавлять cancellation state, execution context или tracing.P.S. Добавил пояснение в подраздел «Почему выбран такой Task?».

mayorovp
05.05.2026 11:36Да, согласен, await_suspend может обращаться к promise напрямую.
Однако, в вашем promise всё равно нет и одного публичного метода, которые можно было бы использовать для предотвращения проблем.
В моих проектах такой механизм выносится в scheduler/awaiter-слой; это архитектурное решение, и в другой реализации оно может быть организовано иначе.
У вас в вашем Task недостаточно возможностей чтобы этот scheduler/awaiter-слой сделал хоть что-то.

olegiv2019 Автор
05.05.2026 11:36В показанном
Taskдействительно нет общего публичного интерфейса promise для cancellation/unregister-протокола, потому что такой протокол не входит в рассматриваемый уровень реализации.При этом scheduler/awaiter-слой не обязан строиться только через публичные методы promise. Он может быть реализован через конкретные awaitable-типы: awaiter хранит ссылку на scheduler и registration id, а при уничтожении coroutine state его деструктор удаляет сохранённый handle из внешней структуры ожидания.
То есть такой механизм может находиться на уровне конкретного awaiter-а и scheduler-а, а не обязательно в самом
Taskили promise API.

TarkWight
05.05.2026 11:36Очередная gpt пародия на статью с таким же gpt генеративными ответами в комментариях... Когда нибудь такое будет подвергаться цензуре или запретам публикации?

mayorovp
05.05.2026 11:36Не, нейронки отвечают либо умнее, либо невпопад. Тут типичная протечка студенческой работы в интернет, когда студент выполнил семестровое задание и почему-то решил, что оно будет интересно кому-то кроме преподавателя. Ещё возможен вариант что это преподаватель тут пишет, в таком случае мне жаль его студентов.
mayorovp
Налито много воды. Вода мокрая. Таким образом, в статье мокро, потому что налито много мокрой воды. На всякий случай напишу ещё раз, что воды много, вдруг кто не понял.
Для кого вообще эта статья? Для кого расписаны все эти “
Task<T>эквивалентенTask<T, StartSuspended>”, и почему банальныя фраза “значения по умолчанию для типов-параметров - void и StartSuspended” вообще нуждается в дальнейшей расшифровке?Зачем тут в статье вообще очевидные детали реализации, вроде того что метод
return_voidнаписан потому что компилятор его требует?Где примены использования вашего модуля? Где сравнение с сществующими реализациями (как минимум, с cppcoro)?
olegiv2019 Автор
Да, замечание принимаю. Статья получилась слишком подробной в местах, где достаточно одной строки, особенно про default template arguments и очевидные элементы promise type. Статья ориентирована не на пользователей готовых coroutine-библиотек, а на тех, кто пишет собственный минимальный coroutine return object под свой scheduler/event loop и хочет явно контролировать
promise_type,std::coroutine_handle,initial_suspend(),final_suspend()и уничтожение coroutine state.