惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
V
Visual Studio Blog
C
Check Point Blog
Google DeepMind News
Google DeepMind News
S
SegmentFault 最新的问题
博客园 - 聂微东
量子位
T
Tailwind CSS Blog
罗磊的独立博客
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Y
Y Combinator Blog
L
LangChain Blog
小众软件
小众软件
Engineering at Meta
Engineering at Meta
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
M
MIT News - Artificial intelligence
Know Your Adversary
Know Your Adversary
MongoDB | Blog
MongoDB | Blog
Google DeepMind News
Google DeepMind News
大猫的无限游戏
大猫的无限游戏
H
Help Net Security
爱范儿
爱范儿
T
The Exploit Database - CXSecurity.com
有赞技术团队
有赞技术团队
V
Vulnerabilities – Threatpost
Martin Fowler
Martin Fowler
A
Arctic Wolf
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
Cyberwarzone
Cyberwarzone
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
GbyAI
GbyAI
Latest news
Latest news
云风的 BLOG
云风的 BLOG
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
腾讯CDC
AWS News Blog
AWS News Blog
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
博客园 - Franky
S
Securelist
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
美团技术团队

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Универсальный Task для C++20 coroutines: тип результата, политика запуска и владение coroutine state
olegiv2019 · 2026-05-05 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение14 мин

Охват и читатели1.2K

Туториал

В статье рассматривается заголовочный компонент 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][2].

Рассматриваемая реализация:

  • задаёт 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?

C++20 coroutines задают языковой механизм приостановки и возобновления coroutine body, но не задают готовый универсальный тип результата для пользовательской coroutine-функции. Coroutine-функция должна иметь return type. Для этого return type компилятор через std::coroutine_traits определяет promise_type [1][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:

  1. вызывается coroutine-функция;

  2. создаётся coroutine state;

  3. в coroutine state создаётся promise object;

  4. вызывается promise.get_return_object();

  5. get_return_object() создаёт Task;

  6. Task получает std::coroutine_handle<promise_type>;

  7. вызывается promise.initial_suspend();

  8. дальнейшее поведение зависит от результата 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, для которых эти операции имеют определённое поведение по стандарту.

Источники

  1. ISO/IEC 14882:2020(E), разделы:

    • Coroutine definitions;

    • Coroutine promise;

    • Coroutine handle;

    • Coroutine state lifetime.

  2. cppreference: Coroutines https://en.cppreference.com/w/cpp/language/coroutines

  3. cppreference: std::coroutine_handle https://en.cppreference.com/w/cpp/coroutine/coroutine_handle

  4. cppreference: std::suspend_always https://en.cppreference.com/w/cpp/coroutine/suspend_always

  5. cppreference: std::suspend_never https://en.cppreference.com/w/cpp/coroutine/suspend_never

  6. cppreference: std::conditional https://en.cppreference.com/w/cpp/types/conditional

  7. cppreference: std::optional https://en.cppreference.com/w/cpp/utility/optional

  8. cppreference: std::exception_ptr https://en.cppreference.com/w/cpp/error/exception_ptr