Сообщение Re: C++20 coroutines (co_await) от 09.01.2021 5:22
Изменено 09.01.2021 5:24 х
Re: C++20 coroutines (co_await)
Здравствуйте, okman, Вы писали:
O>Всем привет!
O>Изучаю C++20. В целом все понятно, но одна вещь вызывает наибольшие затруднения — корутины.
Всё просто, если немного упростить с деталями:
реализуешь шаблон coroutine_traits с внутренней структурой promise_type — это объект который будет использоваться в функции использующей корутины для управления работой.
По сути функция
преобразуется в:
То есть при первом же вызове оператора корутин формируется структура для работы с ними.
Далее когда есть вызов:
и пусть при этом
тогда co_await преобразуется в (понятно, что move расставлены не корректно, но они для демонстрации передачи владением объекта в другой код)
То есть вызывается вложенная функция и у результата этой функции спрашивается готов ли результат. Если он готов, то результат забирается через await_resume иначе просто остаток функции упаковывается в лямбду и через await_suspend говорится, что когда вложенная функция отработает надо вызвать нашу лямбду для продолжения выполнения нашей функции.
co_return ещё проще — он просто вызывает метод return_value для вашего promise_type.
Таким образом
преобразуется в что-то вида:
Собственно всё. Дальше Вы сами решаете как строить цепочки функций, как производить "просыпание" и т.п. В этом мощь. В отличии от C# вы тут можете выбирать ту модель которую считаете оптимальной.
Более детально можно посмотреть реализацию тут.
O>Всем привет!
O>Изучаю C++20. В целом все понятно, но одна вещь вызывает наибольшие затруднения — корутины.
Всё просто, если немного упростить с деталями:
реализуешь шаблон coroutine_traits с внутренней структурой promise_type — это объект который будет использоваться в функции использующей корутины для управления работой.
По сути функция
T f(T1 a1, T2 a2 ...) {
co_await ...
co_return ...
}
преобразуется в:
T f(T1 a1, T2 a2 ...) {
struct Arguments {
coroutine_traits<T, T1, T2...>::promise_type this_corutine;
T1 a1;
T2 a2;
};
auto args = std::make_unique<Arguments>(coroutine_traits::promise_type, std::move(a1), std::move(a2) ... );
...
return args->this_corutine.get_return_object();
}
То есть при первом же вызове оператора корутин формируется структура для работы с ними.
Далее когда есть вызов:
auto res = co_await fnested(...);
и пусть при этом
TResult fnested(...)
тогда co_await преобразуется в (понятно, что move расставлены не корректно, но они для демонстрации передачи владением объекта в другой код)
auto fnested_awaiter = operator co_await(fnested(...));
auto rest_code = [std::move(fnested_awaiter), std::move(args)]() {
auto res = fnested_awaiter.await_resume();
...//Rest code
};
if(!fnested_awaiter.await_ready()){
//Suspend
fnested_awaiter.await_suspend(std::move(rest_code));
return args->this_corutine.get_return_object();
} else {
rest_code();
}
... //Rest code
То есть вызывается вложенная функция и у результата этой функции спрашивается готов ли результат. Если он готов, то результат забирается через await_resume иначе просто остаток функции упаковывается в лямбду и через await_suspend говорится, что когда вложенная функция отработает надо вызвать нашу лямбду для продолжения выполнения нашей функции.
co_return ещё проще — он просто вызывает метод return_value для вашего promise_type.
Таким образом
T f(T1 a1, T2 a2 ...) {
auto res = co_await fnested(...);
co_return ...;
}
преобразуется в что-то вида:
T f(T1 a1, T2 a2 ...) {
struct Arguments {
coroutine_traits<T, T1, T2...>::promise_type this_corutine;
T1 a1;
T2 a2;
};
auto args = std::make_unique<Arguments>(coroutine_traits::promise_type, std::move(a1), std::move(a2) ... );
auto fnested_awaiter = operator co_await(fnested(...));
auto rest_code = [std::move(fnested_awaiter), args]() {
auto res = fnested_awaiter.await_resume();
args->this_corutine.return_value(...);//co_return ...;
};
if(!fnested_awaiter.await_ready()){
//Suspend
fnested_awaiter.await_suspend(std::move(rest_code));
} else {
rest_code();
}
return args->this_corutine.get_return_object();
}
Собственно всё. Дальше Вы сами решаете как строить цепочки функций, как производить "просыпание" и т.п. В этом мощь. В отличии от C# вы тут можете выбирать ту модель которую считаете оптимальной.
Более детально можно посмотреть реализацию тут.
Re: C++20 coroutines (co_await)
Здравствуйте, okman, Вы писали:
O>Всем привет!
O>Изучаю C++20. В целом все понятно, но одна вещь вызывает наибольшие затруднения — корутины.
Всё просто, если немного упростить с деталями:
реализуешь шаблон coroutine_traits с внутренней структурой promise_type — это объект который будет использоваться в функции использующей корутины для управления работой.
По сути функция
преобразуется в:
То есть при первом же вызове оператора корутин формируется структура для работы с ними.
Далее когда есть вызов:
и пусть при этом
тогда co_await преобразуется в (понятно, что move расставлены не корректно, но они для демонстрации передачи владением объекта в другой код)
То есть вызывается вложенная функция и у результата этой функции спрашивается готов ли результат. Если он готов, то результат забирается через await_resume иначе просто остаток функции упаковывается в лямбду и через await_suspend говорится, что когда вложенная функция отработает надо вызвать нашу лямбду для продолжения выполнения нашей функции.
co_return ещё проще — он просто вызывает метод return_value для вашего promise_type.
Таким образом
преобразуется в что-то вида:
Собственно всё. Дальше Вы сами решаете как строить цепочки функций, как производить "просыпание" и т.п. В этом мощь. В отличии от C# вы тут можете выбирать ту модель которую считаете оптимальной.
Более детально можно посмотреть реализацию тут.
O>Всем привет!
O>Изучаю C++20. В целом все понятно, но одна вещь вызывает наибольшие затруднения — корутины.
Всё просто, если немного упростить с деталями:
реализуешь шаблон coroutine_traits с внутренней структурой promise_type — это объект который будет использоваться в функции использующей корутины для управления работой.
По сути функция
T f(T1 a1, T2 a2 ...) {
co_await ...
co_return ...
}
преобразуется в:
T f(T1 a1, T2 a2 ...) {
struct Arguments {
coroutine_traits<T, T1, T2...>::promise_type this_corutine;
T1 a1;
T2 a2;
};
auto args = std::make_unique<Arguments>(coroutine_traits::promise_type, std::move(a1), std::move(a2) ... );
...
return args->this_corutine.get_return_object();
}
То есть при первом же вызове оператора корутин формируется структура для работы с ними.
Далее когда есть вызов:
auto res = co_await fnested(...);
и пусть при этом
TResult fnested(...)
тогда co_await преобразуется в (понятно, что move расставлены не корректно, но они для демонстрации передачи владением объекта в другой код)
auto fnested_awaiter = operator co_await(fnested(...));
auto rest_code = [std::move(fnested_awaiter), std::move(args)]() {
auto res = fnested_awaiter.await_resume();
...//Rest code
};
if(!fnested_awaiter.await_ready()){
//Suspend
fnested_awaiter.await_suspend(std::move(rest_code));
} else {
rest_code();
}
То есть вызывается вложенная функция и у результата этой функции спрашивается готов ли результат. Если он готов, то результат забирается через await_resume иначе просто остаток функции упаковывается в лямбду и через await_suspend говорится, что когда вложенная функция отработает надо вызвать нашу лямбду для продолжения выполнения нашей функции.
co_return ещё проще — он просто вызывает метод return_value для вашего promise_type.
Таким образом
T f(T1 a1, T2 a2 ...) {
auto res = co_await fnested(...);
co_return ...;
}
преобразуется в что-то вида:
T f(T1 a1, T2 a2 ...) {
struct Arguments {
coroutine_traits<T, T1, T2...>::promise_type this_corutine;
T1 a1;
T2 a2;
};
auto args = std::make_unique<Arguments>(coroutine_traits::promise_type, std::move(a1), std::move(a2) ... );
auto fnested_awaiter = operator co_await(fnested(...));
auto rest_code = [std::move(fnested_awaiter), args]() {
auto res = fnested_awaiter.await_resume();
args->this_corutine.return_value(...);//co_return ...;
};
if(!fnested_awaiter.await_ready()){
//Suspend
fnested_awaiter.await_suspend(std::move(rest_code));
} else {
rest_code();
}
return args->this_corutine.get_return_object();
}
Собственно всё. Дальше Вы сами решаете как строить цепочки функций, как производить "просыпание" и т.п. В этом мощь. В отличии от C# вы тут можете выбирать ту модель которую считаете оптимальной.
Более детально можно посмотреть реализацию тут.