Можно ли скопировать сопрограммы C ++ 20?

Я играл с сопрограммами C ++ 20 и пытался переместить часть моей кодовой базы, чтобы использовать их. Однако я столкнулся с проблемой, поскольку не похоже, что новые сопрограммы можно скопировать. Объекты generator удалили конструкторы копирования и операторы присваивания копий, и ничто из того, что я изучал, похоже, не нашло выхода.

Можно ли это сделать?

Для справки, я написал небольшую тестовую программу с неудачной попыткой копирования сопрограмм C ++ 20, а также успешной попыткой сделать то же самое с boost::asio::coroutine. Используется Visual Studio 2019 версии 16.3.7.

#include <sdkddkver.h>
#include <string>
#include <algorithm>
#include <iterator>
#include <experimental\resumable>
#include <experimental\generator>
#include <cassert>

#include <boost\asio\yield.hpp>

namespace std_coroutines {
    auto letters() {
        for (auto c = 'a'; ; ++c)
            co_yield c;
    }

    void run() {
        auto gen = letters();
        std::string s1, s2;
        std::copy_n(gen.begin(), 3, std::back_inserter(s1)); // append "abc" to s1
        //auto gen_copy = gen; // doesn't compile

        std::copy_n(gen.begin(), 3, std::back_inserter(s1)); // append "def" to s1
        //std::copy_n(gen_copy.begin(), 3, std::back_inserter(s2)); // append "def" to s2

        assert(s1 == "abcdef");
        assert(s2 == "def"); // fails
    }
};// namespace std_coroutines

namespace boost_asio_coroutines {
    struct letters : boost::asio::coroutine {
        char c = 'a';
        char operator()() {
            reenter(this) for (;; ++c)
            {
                yield return c;
            }
        }
    };

    void run() {
        auto gen = letters();
        std::string s1, s2;
        std::generate_n(std::back_inserter(s1), 3, std::ref(gen)); // append "abc" to s1
        auto gen_copy = gen;
        std::generate_n(std::back_inserter(s1), 3, std::ref(gen)); // append "def" to s1
        std::generate_n(std::back_inserter(s2), 3, std::ref(gen_copy)); // append "def" to s2

        assert(s1 == "abcdef");
        assert(s2 == "def");
    }
} // namespace boost_asio_coroutines

int main() {
    boost_asio_coroutines::run();
    std_coroutines::run();
}

person Ayjay    schedule 31.10.2019    source источник
comment
Я не знаю о сопрограммах ускорения, но std::generator становится владельцем базовой сопрограммы; поэтому его можно только перемещать (поскольку базовая сопрограмма не может быть скопирована).   -  person Meowmere    schedule 31.10.2019
comment
Итак .. нет? Нет возможности скопировать сопрограммы c ++ 20?   -  person Ayjay    schedule 31.10.2019
comment
@KaenbyouRin Вы определенно можете скопировать буст-сопрограммы, пример кода, который я написал, демонстрирует это   -  person Ayjay    schedule 31.10.2019
comment
Пожалуйста, не используйте обратную косую черту в #include путях. Прямая косая черта отлично работает везде, поэтому просто используйте их по умолчанию.   -  person rubenvb    schedule 31.10.2019


Ответы (2)


TS явно не запрещает копирование. Как вы упомянули, объект обещания std::experimental::generator удалил операции копирования. Я думаю, что первоначальные реализации консервативны в отношении копий, потому что есть над чем подумать.

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

Неглубокая копия может быть выполнена с помощью реализации, если семантика владения дескриптором сопрограммы была установлена ​​в соответствии со строками shared_ptr и weak_ptr (аналогия немного разваливается, потому что только одна сопрограмма является фактическим владельцем состояния, в то время как все остальные являются наблюдатели).

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

Некоторые выводы, о которых я могу думать:

  • глубокое копирование локальных переменных функции произвольно дорого, и эти дополнительные накладные расходы на выделение памяти могут стать неожиданным сюрпризом для пользователя.
  • дескриптор скопированной сопрограммы всегда * должен находиться в куче, а это означает, что вы можете оказаться в ситуации, когда «исходная» сопрограмма имеет отличную производительность, а скопированная - плохая. Случайные копии могут незаметно уничтожить оптимизацию.

* Хранилище для сопрограмм получается путем вызова глобальной не-массивной new функции. Теоретически вы можете перегрузить эту функцию, чтобы она соответствовала вашей сопрограмме, но поскольку хранилище определяется реализацией, вам нужно знать свою платформу. Тем не менее, это позволит вам теоретически использовать глобальный распределитель арены, который зарезервировал часть стека, например

person AndyG    schedule 31.10.2019
comment
Неглубокая копия Неглубокая копия сопрограммы не имеет смысла. Особенно генератор; вы могли бы получать значения из потенциально двух разных источников, но все они были бы из одного и того же потока. Копировать сопрограмму имеет такой же смысл, как и копировать файловый поток, что мы также не разрешаем. - person Nicol Bolas; 31.10.2019

Когда я говорю о «копировании сопрограммы», я имею в виду, по сути, выполнение операции копирования над «будущим» объектом, возвращаемым функцией сопрограммы, которая приводит к чему-то, что пользователь может считать «копией» этого будущего.

Неглубокое копирование сопрограммы (скопированное будущее ссылается на тот же coroutine_handle и объект обещания) в некоторых случаях может иметь небольшую полезность. Для сценариев, не связанных с генератором, вы фактически создаете эквивалент std::shared_future: есть несколько мест, из которых могут быть извлечены обещанные значения.

Я не совсем уверен, насколько это полезно для сценариев с генераторами. Стало бы намного сложнее рассуждать о том, что делает генератор и где он находится в процессе выполнения. Это также в основном убьет любую надежду на исключение распределения, поскольку у вас будет несколько ссылок на обещание / дескриптор.

Глубокое копирование сопрограммы практически невозможно. Чтобы глубоко скопировать сопрограмму, вам нужно скопировать стек такой сопрограммы. Даже если мы предположим, что сопрограмма гарантированно не будет выполняться во время операции копирования, на самом деле это не сработает. Почему?

Потому что стек может содержать объекты, которые нельзя копировать. И сопрограммы не обязательно должны быть встроенными, поэтому компилятор, который должен скомпилировать операцию копирования, не обязательно имеет доступ к исходному коду самой сопрограммы. Таким образом, он не может определить, можно ли копировать стек сопрограмм.

Теперь, по крайней мере гипотетически, можно было бы провести какой-то тест во время выполнения, чтобы увидеть, является ли копирование возможной операцией для этой сопрограммы. Но это его собственная банка с червями.

Но любое глубокое копирование потребует изменений в механизме сопрограмм; неглубокое копирование - это то, что вы могли бы гипотетически реализовать в своем собственном будущем типе.

Причина, по которой ваш код имеет семантику глубокого копирования, заключается в том, что он на самом деле не использует механизм обещания / будущего сопрограммы. Ни обещание, ни выполняющаяся сопрограмма не содержат значения; ваш объект сопрограммы. Таким образом, копирование объекта сопрограммы создает копию этого значения. Настоящие сопрограммы C ++ 20 так не работают.

person Nicol Bolas    schedule 31.10.2019