Namespaces
Variants

Coroutines (C++20)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Сопрограмма — это функция, которая может приостановить выполнение для последующего возобновления. Сопрограммы бесстековые: они приостанавливают выполнение, возвращая управление вызывающей стороне, а данные, необходимые для возобновления выполнения, хранятся отдельно от стека. Это позволяет последовательному коду выполняться асинхронно (например, для обработки неблокирующего ввода-вывода без явных обратных вызовов), а также поддерживает алгоритмы для лениво вычисляемых бесконечных последовательностей и другие применения.

Функция является сопрограммой, если её определение содержит любое из следующего:

  • выражение co_await — приостанавливает выполнение до возобновления
task<> tcp_echo_server()
{
    char data[1024];
    while (true)
    {
        std::size_t n = co_await socket.async_read_some(buffer(data));
        co_await async_write(socket, buffer(data, n));
    }
}
  • выражение co_yield — для приостановки выполнения с возвратом значения
generator<unsigned int> iota(unsigned int n = 0)
{
    while (true)
        co_yield n++;
}
  • оператор co_return — для завершения выполнения с возвратом значения
lazy<int> f()
{
    co_return 7;
}

Каждая сопрограмма должна иметь тип возврата, удовлетворяющий ряду требований, указанных ниже.

Содержание

Ограничения

Сопрограммы не могут использовать вариативные аргументы , обычные операторы return , или типы возвращаемых значений-заполнители ( auto или Концепты ).

Consteval функции , constexpr функции , конструкторы , деструкторы , и функция main не могут быть корутинами.

Выполнение

Каждая сопрограмма связана с

  • объект promise , управляемый изнутри корутины. Корутина передает свой результат или исключение через этот объект. Объекты promise никак не связаны с std::promise .
  • дескриптор корутины , управляемый извне корутины. Это невладеющий дескриптор, используемый для возобновления выполнения корутины или уничтожения фрейма корутины.
  • состояние корутины , которое является внутренним, динамически выделяемым хранилищем (если выделение не оптимизировано), объектом, содержащим
  • объект promise
  • параметры (все копируются по значению)
  • некоторое представление текущей точки приостановки, чтобы resume знал, где продолжить, а destroy знал, какие локальные переменные были в области видимости
  • локальные переменные и временные объекты, время жизни которых охватывает текущую точку приостановки.

Когда сопрограмма начинает выполнение, она выполняет следующее:

  • выделяет объект состояния сопрограммы с помощью operator new .
  • копирует все параметры функции в состояние сопрограммы: параметры по значению перемещаются или копируются, параметры по ссылке остаются ссылками (таким образом, могут стать висячими, если сопрограмма возобновляется после завершения времени жизни объекта — см. примеры ниже).
  • вызывает конструктор для объекта promise. Если тип promise имеет конструктор, принимающий все параметры сопрограммы, вызывается этот конструктор с пост-копированными аргументами сопрограммы. В противном случае вызывается конструктор по умолчанию.
  • вызывает promise. get_return_object ( ) и сохраняет результат в локальной переменной. Результат этого вызова будет возвращен вызывающей стороне при первой приостановке сопрограммы. Любые исключения, выброшенные до и включая этот шаг, передаются обратно вызывающей стороне, а не помещаются в promise.
  • вызывает promise. initial_suspend ( ) и co_await его результат. Типичные типы Promise либо возвращают std::suspend_always для лениво запускаемых сопрограмм, либо std::suspend_never для активно запускаемых сопрограмм.
  • когда co_await promise. initial_suspend ( ) возобновляется, начинает выполнение тела сопрограммы.

Некоторые примеры, когда параметр становится висячим:

#include <coroutine>
#include <iostream>
struct promise;
struct coroutine : std::coroutine_handle<promise>
{
    using promise_type = ::promise;
};
struct promise
{
    coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
};
struct S
{
    int i;
    coroutine f()
    {
        std::cout << i;
        co_return;
    }
};
void bad1()
{
    coroutine h = S{0}.f();
    // S{0} уничтожен
    h.resume(); // возобновленная сопрограмма выполняет std::cout << i, использует S::i после освобождения
    h.destroy();
}
coroutine bad2()
{
    S s{0};
    return s.f(); // возвращенная сопрограмма не может быть возобновлена без использования после освобождения
}
void bad3()
{
    coroutine h = [i = 0]() -> coroutine // лямбда, которая также является сопрограммой
    {
        std::cout << i;
        co_return;
    }(); // немедленно вызывается
    // лямбда уничтожена
    h.resume(); // использует (анонимный тип лямбды)::i после освобождения
    h.destroy();
}
void good()
{
    coroutine h = [](int i) -> coroutine // сделать i параметром сопрограммы
    {
        std::cout << i;
        co_return;
    }(0);
    // лямбда уничтожена
    h.resume(); // нет проблем, i был скопирован в кадр сопрограммы
                // как параметр по значению
    h.destroy();
}

Когда сопрограмма достигает точки приостановки

  • ранее полученный объект возврата передаётся вызывающей стороне/возобновителю после неявного преобразования в тип возврата корутины, если это необходимо.

Когда сопрограмма достигает оператора co_return , она выполняет следующие действия:

  • вызывает promise. return_void ( ) для
  • co_return ;
  • co_return expr ; где expr имеет тип void
  • или вызывает promise. return_value ( expr ) для co_return expr ; где expr имеет тип, отличный от void
  • уничтожает все переменные с автоматической продолжительностью хранения в порядке, обратном их созданию.
  • вызывает promise. final_suspend ( ) и co_await ожидает результат.

Падение с конца сопрограммы эквивалентно co_return ; , за исключением того, что поведение не определено, если в области видимости Promise не найдено объявлений return_void . Функция без ключевых слов определения в теле функции не является сопрограммой, независимо от её возвращаемого типа, и падение с конца приводит к неопределённому поведению, если возвращаемый тип не является (возможно, cv-квалифицированным) void .

// предполагая, что task - это некоторый тип задачи корутины
task<void> f()
{
    // не корутина, неопределенное поведение
}
task<void> g()
{
    co_return;  // OK
}
task<void> h()
{
    co_await g();
    // OK, неявный co_return;
}

Если сопрограмма завершается неперехваченным исключением, она выполняет следующее:

  • перехватывает исключение и вызывает promise. unhandled_exception ( ) внутри блока catch
  • вызывает promise. final_suspend ( ) и co_await для результата (например, для возобновления продолжения или публикации результата). Возобновление сопрограммы с этой точки является неопределённым поведением.

Когда состояние сопрограммы уничтожается либо из-за завершения через co_return или неперехваченного исключения, либо потому что оно было уничтожено через свой handle, оно выполняет следующее:

  • вызывает деструктор объекта promise.
  • вызывает деструкторы копий параметров функции.
  • вызывает operator delete для освобождения памяти, используемой состоянием корутины.
  • возвращает выполнение вызывающему/возобновившему.

Динамическое выделение памяти

Состояние сопрограммы выделяется динамически с помощью не-массивного operator new .

Если тип Promise определяет замену на уровне класса, она будет использована, в противном случае будет использован глобальный operator new .

Если тип Promise определяет размещающую форму operator new , которая принимает дополнительные параметры, и они соответствуют списку аргументов, где первый аргумент — запрашиваемый размер (типа std::size_t ), а остальные — аргументы функции-корутины, эти аргументы будут переданы в operator new (это позволяет использовать конвенцию с ведущим аллокатором для корутин).

Вызов operator new может быть исключен оптимизацией (даже при использовании пользовательского аллокатора), если

  • Время жизни состояния сопрограммы строго вложено во время жизни вызывающей стороны, и
  • размер фрейма сопрограммы известен в точке вызова.

В этом случае состояние сопрограммы встроено в стековый фрейм вызывающей стороны (если вызывающая сторона является обычной функцией) или состояние сопрограммы (если вызывающая сторона является сопрограммой).

Если выделение памяти завершается неудачей, сопрограмма выбрасывает std::bad_alloc , за исключением случая, когда тип Promise определяет функцию-член Promise :: get_return_object_on_allocation_failure ( ) . Если эта функция-член определена, выделение памяти использует nothrow-форму operator new , и при неудачном выделении памяти сопрограмма немедленно возвращает объект, полученный из Promise :: get_return_object_on_allocation_failure ( ) вызывающей стороне, например:

struct Coroutine::promise_type
{
    /* ... */
    // обеспечить использование непорождающего исключения operator-new
    static Coroutine get_return_object_on_allocation_failure()
    {
        std::cerr << __func__ << '\n';
        throw std::bad_alloc(); // или return Coroutine(nullptr);
    }
    // пользовательская непорождающая исключения перегрузка new
    void* operator new(std::size_t n) noexcept
    {
        if (void* mem = std::malloc(n))
            return mem;
        return nullptr; // ошибка выделения памяти
    }
};

Промис

Тип Promise определяется компилятором из возвращаемого типа корутины с использованием std::coroutine_traits .

Формально, пусть

  • R и Args... обозначают тип возвращаемого значения и список типов параметров корутины соответственно,
  • ClassT обозначает тип класса, к которому принадлежит корутина, если она определена как нестатическая функция-член,
  • cv обозначает cv-квалификацию, объявленную в объявлении функции если она определена как нестатическая функция-член,

его Promise тип определяется следующим образом:

  • std:: coroutine_traits < R, Args... > :: promise_type , если сопрограмма не определена как неявная функция-член объекта ,
  • std:: coroutine_traits < R, cv ClassT & , Args... > :: promise_type , если сопрограмма определена как неявная функция-член объекта без квалификатора ссылки на rvalue,
  • std:: coroutine_traits < R, cv ClassT && , Args... > :: promise_type , если сопрограмма определена как неявная функция-член объекта с квалификатором ссылки на rvalue.

Например:

Если корутина определена как ... тогда её тип Promise это ...
task < void > foo ( int x ) ; std:: coroutine_traits < task < void > , int > :: promise_type
task < void > Bar :: foo ( int x ) const ; std:: coroutine_traits < task < void > , const Bar & , int > :: promise_type
task < void > Bar :: foo ( int x ) && ; std:: coroutine_traits < task < void > , Bar && , int > :: promise_type

co_await

Унарный оператор co_await приостанавливает сопрограмму и возвращает управление вызывающей стороне.

co_await выражение

Выражение co_await может появляться только в потенциально вычисляемом выражении внутри обычного тела функции (включая тело функции лямбда-выражения ), и не может появляться

Выражение co_await не может быть потенциально вычисляемым подвыражением предиката контрактного утверждения .

(since C++26)

Сначала expr преобразуется в awaitable следующим образом:

  • если expr получено из точки начальной приостановки, точки конечной приостановки или выражения yield, то awaitable объект представляет собой expr в исходном виде.
  • в противном случае, если тип Promise текущей корутины имеет функцию-член await_transform , то awaitable объект представляет собой promise. await_transform ( expr ) .
  • в противном случае, awaitable объект представляет собой expr в исходном виде.

Затем объект awaiter получается следующим образом:

  • если разрешение перегрузки для operator co_await дает единственную наилучшую перегрузку, то объект ожидания является результатом этого вызова:
  • awaitable. operator co_await ( ) для перегрузки члена,
  • operator co_await ( static_cast < Awaitable && > ( awaitable ) ) для перегрузки не члена.
  • в противном случае, если разрешение перегрузки не находит оператор co_await , объект ожидания используется как есть.
  • в противном случае, если разрешение перегрузки неоднозначно, программа является некорректной.

Если приведённое выше выражение является prvalue , объект awaiter представляет собой временный объект, материализованный из него. В противном случае, если приведённое выше выражение является glvalue , объект awaiter является объектом, на который оно ссылается.

Затем вызывается awaiter. await_ready ( ) (это сокращение позволяет избежать затрат на приостановку, если известно, что результат готов или может быть завершён синхронно). Если его результат, контекстно преобразованный в bool , равен false , тогда

Сопрограмма приостанавливается (её состояние заполняется локальными переменными и текущей точкой приостановки).
awaiter. await_suspend ( handle ) вызывается, где handle - это дескриптор сопрограммы, представляющий текущую сопрограмму. Внутри этой функции приостановленное состояние сопрограммы доступно для наблюдения через этот дескриптор, и эта функция отвечает за планирование её возобновления в некотором исполнителе или уничтожения (возврат false считается планированием)
  • если await_suspend возвращает void , управление немедленно возвращается вызывающему/возобновителю текущей сопрограммы (эта сопрограмма остаётся приостановленной), иначе
  • если await_suspend возвращает bool ,
  • значение true возвращает управление вызывающему/возобновителю текущей сопрограммы
  • значение false возобновляет текущую сопрограмму.
  • если await_suspend возвращает дескриптор сопрограммы для другой сопрограммы, этот дескриптор возобновляется (путем вызова handle. resume ( ) ) (примечание: это может вызвать цепочку, которая в конечном итоге приведёт к возобновлению текущей сопрограммы).
  • если await_suspend выбрасывает исключение, исключение перехватывается, сопрограмма возобновляется, и исключение немедленно перебрасывается.

Наконец, awaiter. await_resume ( ) вызывается (независимо от того, была ли корутина приостановлена), и его результат становится результатом всего выражения co_await expr .

Если сопрограмма была приостановлена в выражении co_await и позже возобновлена, точка возобновления находится непосредственно перед вызовом awaiter. await_resume ( ) .

Обратите внимание, что корутина полностью приостанавливается перед входом в awaiter. await_suspend ( ) . Её дескриптор может быть передан в другой поток и возобновлён до того, как await_suspend ( ) функция завершится. (Учтите, что стандартные правила безопасности памяти остаются в силе, поэтому если дескриптор корутины передаётся между потоками без блокировки, ожидатель должен использовать как минимум релиз-семантику , а возобновляющий — как минимум аквир-семантику .) Например, дескриптор корутины может быть помещён в колбэк, запланированный на выполнение в пуле потоков при завершении асинхронной операции ввода-вывода. В этом случае, поскольку текущая корутина могла быть возобновлена и, следовательно, выполнила деструктор объекта ожидателя, всё это может происходить параллельно с выполнением await_suspend ( ) в текущем потоке. await_suspend ( ) должен рассматривать * this как уничтоженный и не обращаться к нему после публикации дескриптора в другие потоки.

Пример

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
auto switch_to_new_thread(std::jthread& out)
{
    struct awaitable
    {
        std::jthread* p_out;
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<> h)
        {
            std::jthread& out = *p_out;
            if (out.joinable())
                throw std::runtime_error("Output jthread parameter not empty");
            out = std::jthread([h] { h.resume(); });
            // Potential undefined behavior: accessing potentially destroyed *this
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
        }
        void await_resume() {}
    };
    return awaitable{&out};
{
struct task
{
    struct promise_type
    {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};
task resuming_on_new_thread(std::jthread& out)
{
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

Возможный вывод:

Coroutine started on thread: 139972277602112
New thread ID: 139972267284224
Coroutine resumed on thread: 139972267284224

Примечание: объект awaiter является частью состояния сопрограммы (как временный объект, время жизни которого пересекает точку приостановки) и уничтожается до завершения выражения co_await . Он может использоваться для поддержания состояния операции, требуемого некоторыми асинхронными I/O API, без прибегания к дополнительным динамическим выделениям памяти.

Стандартная библиотека определяет два тривиальных объекта ожидания: std::suspend_always и std::suspend_never .

Демонстрация promise_type :: await_transform и предоставленного программой объекта ожидания

Пример

#include <cassert>
#include <coroutine>
#include <iostream>
struct tunable_coro
{
    // Ожидатель, «готовность» которого определяется через параметр конструктора.
    class tunable_awaiter
    {
        bool ready_;
    public:
        explicit(false) tunable_awaiter(bool ready) : ready_{ready} {}
        // Три стандартные функции интерфейса awaiter:
        bool await_ready() const noexcept { return ready_; }
        static void await_suspend(std::coroutine_handle<>) noexcept {}
        static void await_resume() noexcept {}
    };
    struct promise_type
    {
        using coro_handle = std::coroutine_handle<promise_type>;
        auto get_return_object() { return coro_handle::from_promise(*this); }
        static auto initial_suspend() { return std::suspend_always(); }
        static auto final_suspend() noexcept { return std::suspend_always(); }
        static void return_void() {}
        static void unhandled_exception() { std::terminate(); }
        // Пользовательская функция преобразования, возвращающая пользовательский awaiter:
        auto await_transform(std::suspend_always) { return tunable_awaiter(!ready_); }
        void disable_suspension() { ready_ = false; }
    private:
        bool ready_{true};
    };
    tunable_coro(promise_type::coro_handle h) : handle_(h) { assert(h); }
    // Для простоты объявите эти 4 специальные функции как удаленные:
    tunable_coro(tunable_coro const&) = delete;
    tunable_coro(tunable_coro&&) = delete;
    tunable_coro& operator=(tunable_coro const&) = delete;
    tunable_coro& operator=(tunable_coro&&) = delete;
    ~tunable_coro()
    {
        если (handle_)
            handle_.destroy();
    }
    void disable_suspension() const
    {
        если (handle_.done())
            return;
        handle_.promise().disable_suspension();
        handle_();
    }
    bool operator()()
    {
        если (!handle_.done())
            handle_();
        return !handle_.done();
    }
private:
    promise_type::coro_handle handle_;
};
tunable_coro generate(int n)
{
    for (int i{}; i != n; ++i)
    {
        std::cout << i << ' ';
        // The awaiter passed to co_await goes to promise_type::await_transform which
        // issues tunable_awaiter, который изначально вызывает приостановку (возврат обратно в
        // main на каждой итерации), но после вызова disable_suspension приостановка не выполняется
        // происходит, и цикл выполняется до своего завершения без возврата в main().
        co_await std::suspend_always{};
    }
}
int main()
{
    auto coro = generate(8);
    coro(); // испускает только первый элемент равный 0
    for (int k{}; k < 4; ++k)
    {
        coro(); // выводит 1 2 3 4, по одному на каждой итерации
        std::cout << ": ";
    }
    coro.disable_suspension();
    coro(); // выводит конечные числа 5 6 7 все сразу
}

Вывод:

0 1 : 2 : 3 : 4 : 5 6 7
**Примечание:** В данном случае весь текст внутри тега `
` представляет собой числовую последовательность с разделителями, которая не требует перевода, так как:
1. Числа являются универсальными символами
2. Двоеточия используются как синтаксические разделители
3. Согласно условиям, содержимое тегов `
` не переводится
Исходный HTML-код полностью сохранен без изменений.

co_yield

co_yield выражение возвращает значение вызывающей стороне и приостанавливает текущую корутину: это основной строительный блок возобновляемых функций-генераторов.

co_yield выражение
co_yield список-инициализации-в-фигурных-скобках

Это эквивалентно

co_await promise.yield_value(expr)

Типичный генератор yield_value сохраняет (копирует/перемещает или просто сохраняет адрес, так как время жизни аргумента пересекает точку приостановки внутри co_await ) свой аргумент в объект генератора и возвращает std::suspend_always , передавая управление вызывающему/возобновляющему коду.

#include <coroutine>
#include <cstdint>
#include <exception>
#include <iostream>
template<typename T>
struct Generator
{
    // Имя класса 'Generator' является нашим выбором и не требуется для корутины
    // магия. Компилятор распознает сопрограмму по наличию ключевого слова 'co_yield'.
    // Вы можете использовать имя 'MyGenerator' (или любое другое имя) вместо этого, при условии что вы включите
    // вложенная структура promise_type с методом 'MyGenerator get_return_object()'.
    // (Примечание: Необходимо скорректировать объявления конструкторов и деструкторов
    //  при переименовании.)
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type // обязательный
    {
        T value_;
        std::exception_ptr exception_;
        Generator get_return_object()
        {
            return Generator(handle_type::from_promise(*this));
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { exception_ = std::current_exception(); } // сохранение
                                                                              // исключение
        template<std::convertible_to<T> From> // C++20 концепт
        std::suspend_always yield_value(From&& from)
        {
            value_ = std::forward<From>(from); // кэширование результата в promise
            return {};
        }
        void return_void() {}
    };
    handle_type h_;
    Generator(handle_type h) : h_(h) {}
    ~Generator() { h_.destroy(); }
    explicit operator bool()
    {
        fill(); // Единственный надежный способ определить, завершилась ли корутина или нет,
                // будет ли сгенерировано следующее значение (co_yield)
                // в сопрограмме через C++ геттер (operator () ниже) предназначен для выполнения/возобновления
                // корутина до следующей точки co_yield (или до завершения при выходе из функции).
                // Затем мы сохраняем/кэшируем результат в promise, чтобы позволить геттеру (operator() ниже)
                // чтобы захватить его без выполнения сопрограммы).
        return !h_.done();
    }
    T operator()()
    {
        fill();
        full_ = false; // мы собираемся удалить ранее закэшированные
                       // результат, чтобы сделать promise снова пустым
        return std::move(h_.promise().value_);
    }
private:
    bool full_ = false;
    void fill()
    {
        if (!full_)
        {
            h_();
            if (h_.promise().exception_)
                std::rethrow_exception(h_.promise().exception_);
            // propagate coroutine exception in called context
            full_ = true;
        }
    }
};
Generator<std::uint64_t>
fibonacci_sequence(unsigned n)
{
    если (n == 0)
        co_return;
    if (n > 94)
        throw std::runtime_error("Слишком большая последовательность Фибоначчи. Элементы выйдут за пределы диапазона.");
    co_yield 0;
    if (n == 1)
        co_return;
    co_yield 1;
    if (n == 2)
        co_return;
    std::uint64_t a = 0;
    std::uint64_t b = 1;
    for (unsigned i = 2; i < n; ++i)
    {
        std::uint64_t s = a + b;
        co_yield s;
        a = b;
        b = s;
    }
}
int main()
{
    try
    {
        auto gen = fibonacci_sequence(10); // максимум 94 до переполнения uint64_t
        for (int j = 0; gen; ++j)
            std::cout << "fib(" << j << ")=" << gen() << '\n';
    }
    catch (const std::exception& ex)
    {
        std::cerr
(Примечание: В данном случае переводить нечего, так как элемент содержит только HTML-разметку и C++ специфичный термин `std::cerr`, который согласно инструкциям не подлежит переводу. Весь текст уже находится внутри HTML-тегов и представляет собой код C++.) << "Исключение: " << ex.что() << '\n';
    }
    catch (...)
    {
        std::cerr << "Неизвестное исключение.\n";
    }
}

Вывод:

fib(0)=0
fib(1)=1
fib(2)=1
fib(3)=2
fib(4)=3
fib(5)=5
fib(6)=8
fib(7)=13
fib(8)=21
fib(9)=34

Примечания

Макрос тестирования возможностей Значение Стандарт Возможность
__cpp_impl_coroutine 201902L (C++20) Coroutines (поддержка компилятора)
__cpp_lib_coroutine 201902L (C++20) Coroutines (поддержка библиотеки)
__cpp_lib_generator 202207L (C++23) std::generator : синхронный генератор корутин для диапазонов

Ключевые слова

co_await , co_return , co_yield

Поддержка библиотек

Библиотека поддержки корутин определяет несколько типов, предоставляющих поддержку корутин на этапах компиляции и выполнения.

Отчёты о дефектах

Следующие отчеты об изменениях в поведении, содержащие описания дефектов, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Applied to Behavior as published Correct behavior
CWG 2556 C++20 invalid return_void made the behavior of
falling off the end of the coroutine undefined
the program is ill-
formed in this case
CWG 2668 C++20 co_await could not appear in lambda expressions allowed
CWG 2754 C++23 * this was taken when constructing the promise
object for explicit object member functions
* this is not
taken in this case

Смотрите также

(C++23)
view представляющий синхронный coroutine генератор
(шаблон класса)

Внешние ссылки

1. Льюис Бейкер, 2017-2022 - Asymmetric Transfer.
2. Дэвид Мазьерс, 2021 - Tutorial on C++20 coroutines.
3. Сюаньци Сю & Юй Ци & Яо Хань, 2021 - C++20 Principles and Applications of Coroutine. (Chinese)
4. Саймон Татхэм, 2023 - Writing custom C++20 coroutine systems.