Можно ли скопировать сопрограммы C ++ 20? - PullRequest
4 голосов
/ 31 октября 2019

Я играл с сопрограммами 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();
}

Ответы [ 2 ]

2 голосов
/ 31 октября 2019

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

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

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

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

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

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

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

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

2 голосов
/ 31 октября 2019

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

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

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

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

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

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

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

...