Я писал библиотеку сопрограмм и столкнулся со специфической проблемой. В некоторых случаях создание объекта результата сопрограммы было упорядочено после вызова initial_suspend
.
Вопрос: является ли это упорядочение ошибкой со стороны компилятора?
Фон
В моем конкретном случае это вызывало ошибку sh, поскольку обещание генератора выполнялось в предположении, что у него нет владельца.
Соответствующий раздел стандарта C ++ 20 - это раздел 9.5.4.7, в котором говорится:
Выражение promise.get_return_object()
используется для инициализации результата glvalue или объекта результата prvalue вызова сопрограммы. . Вызов get_return_object
упорядочивается перед вызовом initial_suspend
и вызывается не более одного раза.
Когда я читал эту часть стандарта, я изначально интерпретировал это как означающее, что инициализация объект результата сопрограммы упорядочивается сразу после вызова promise.get_return_object()
: когда вы инициализируете что-то, инициализация происходит сразу после вычисления аргументов конструктора, а не как отложенный эффект.
К сожалению, это не то поведение, которое я наблюдал. Источник cra sh заключается в том, что инициализация объекта результата была упорядочена после начальной приостановки , несмотря на то, что вызов promise.get_return_object()
был упорядочен до начальной приостановки.
Давайте напишем очень простой тип, который может быть возвращен из сопрограммы:
template <class Promise>
struct coroutine {
std::coroutine_handle<Promise> handle;
using promise_type = Promise;
coroutine(std::coroutine_handle<Promise> p) : handle(p) {
std::cout << " Running coroutine(std::coroutine_handle<Promise> p)\n";
}
~coroutine() {
if (handle) {
handle.destroy();
}
}
};
Потому что coroutine<Promise>
может быть построенным из std::coroutine_handle<Promise>
, вызов promise.get_return_object()
может возвращать либо std::coroutine_handle<Promise>
, который используется для создания coroutine
, либо он может возвращать coroutine<Promise>
напрямую.
Давайте напишем два разные типы обещаний, по одному для каждой опции:
struct promise_base {
std::suspend_never initial_suspend() {
std::cout << " Running initial_suspend()\n";
return {};
}
std::suspend_always final_suspend() { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
struct promise_A : promise_base {
using handle = std::coroutine_handle<promise_A>;
handle get_return_object() {
std::cout << " Running get_return_object()\n";
return handle::from_promise(*this);
}
};
struct promise_B : promise_base {
using handle = std::coroutine_handle<promise_B>;
coroutine<promise_B> get_return_object() {
std::cout << " Running get_return_object()\n";
return {handle::from_promise(*this)};
}
};
Затем мы можем написать две идентичные сопрограммы на основе promise_A
и promise_B
:
coroutine<promise_A> run_A() {
std::cout << "Inside coroutine body\n";
co_return;
}
coroutine<promise_B> run_B() {
std::cout << "Inside coroutine body\n";
co_return;
}
В этом коде
promise_A
возвращает дескриптор из get_return_object
, и он используется для создания объекта результата типа coroutine<promise_A>
. promise_B
напрямую возвращает coroutine<promise_B>
, который возвращается как объект результата.
В случае promise_A
, построение объекта результата coroutine<promise_A>
упорядочивается после вызова initial_suspend ().
Мы можем написать следующий тестовый код, чтобы проверить это:
void test_promise_A() {
std::cout << "------ Testing A ------\n";
run_A();
std::cout << "\n\n";
}
void test_promise_B() {
std::cout << "------ Testing B ------\n";
run_B();
std::cout << "\n\n";
}
int main() {
test_promise_A();
test_promise_B();
}
В G CC 10.1 это дает следующий результат. Обратите внимание на разницу в последовательности между Testing A
и Testing B
.
------ Testing A ------
Running get_return_object()
Running initial_suspend()
Inside coroutine body
Running coroutine(std::coroutine_handle<Promise> p)
------ Testing B ------
Running get_return_object()
Running coroutine(std::coroutine_handle<Promise> p)
Running initial_suspend()
Inside coroutine body
Повторим вопрос: Является ли последовательность Testing A
ошибкой?