Почему тип возврата сопрограммы должен быть перемещаемым? - PullRequest
7 голосов
/ 17 июня 2020

Рассмотрим следующий код, который определяет класс invoker - минимальный тип возвращаемого значения для сопрограммы. Мы явно удаляем конструкторы копирования и перемещения класса invoker.

#include <coroutine>
#include <cstdlib>
class invoker {
public:
    class invoker_promise {
    public:
        invoker get_return_object() { return invoker{}; }
        auto initial_suspend() { return std::suspend_never{}; }
        auto final_suspend() { return std::suspend_never{}; }
        void return_void() {}
        void unhandled_exception() { std::abort(); }
    };
    using promise_type = invoker_promise;
    invoker() {}
    invoker(const invoker&) = delete;
    invoker& operator=(const invoker&) = delete;
    invoker(invoker&&) = delete;
    invoker& operator=(invoker&&) = delete;
};

invoker f() {
    co_return;
}

Код не компилируется на последней версии G CC (10.1), которая должна иметь полная поддержка сопрограмм C ++ 20.

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

<source>: In function 'invoker f()':
<source>:23:1: error: use of deleted function 'invoker::invoker(invoker&&)'
   23 | }
      | ^
<source>:17:5: note: declared here
   17 |     invoker(invoker&&) = delete;
      |     ^~~~~~~

Почему это так?

Объект invoker создается путем вызова get_return_object() из invoker_promise, к нему нельзя получить доступ, кроме как от вызывающего f(). При гарантированном исключении копирования C ++ 17 invoker, возвращаемое get_return_object(), является prvalue и, следовательно, не должно материализоваться до тех пор, пока оно не будет возвращено из f().

Поскольку возвращенный объект не может быть доступ изнутри сопрограммы, я не вижу ситуации, когда нам может потребоваться материализовать объект перед его возвратом. Я что-то упустил?

Примечание: я знаю этот вопрос , но он:

  • задавали два года go,
  • относится к версии сопрограмм TS,
  • относится к реализации VC ++,
  • остается без ответа, а
  • содержит комментарии, которые в основном говорят о гарантированном исключении копий.

1 Ответ

3 голосов
/ 17 июня 2020

При гарантированном исключении копирования C ++ 17 invoker, возвращаемое get_return_object(), является prvalue и, следовательно, не должно материализоваться до тех пор, пока оно не будет возвращено из f().

Это было бы правдой, только если бы вызов функции сопрограммы гарантированно генерировал свое возвращаемое значение с помощью вызова, эквивалентного созданию группы объектов в отдельном стеке, а затем вызову get_return_object() одного из них. То есть вопрос в том, использует ли путь от get_return_object() до самого вызова функции только prvalues.

Давайте посмотрим на , что говорит стандарт :

Выражение promise.get_­return_­object() используется для инициализации результата glvalue или объекта результата prvalue вызова сопрограммы. Вызов get_­return_­object упорядочивается перед вызовом initial_­suspend и вызывается не более одного раза.

Обратите внимание, что в нем говорится, что он инициализирует «объект результата prvalue». Это тот же язык, который используется в определении поведения оператора return :

оператор return инициализирует результат glvalue или объект результата prvalue (явного или неявный) вызов функции путем копирования-инициализации из операнда.

Единственное сомнение, которое я хотел бы сказать, что стандарт явно требует гарантированного исключения между get_return_object и вызывающий сопрограмму является последней частью около initial_suspend. Поскольку что-то происходит между инициализацией «объекта результата prvalue» и возвратом управления вызывающей стороне, возможно, должен быть посредник, из которого необходимо скопировать / переместить.

Но тот факт, что он использует тот же язык, что и return, предполагает, что он должен обеспечивать точно такое же поведение .

При работе в сопрограмме MSV C реализация, ваш код (с небольшими изменениями для различий в том, где определены определенные типы) работает нормально . В сочетании с приведенными выше доказательствами я бы сказал, что это говорит о том, что это ошибка компилятора.

...