通过c+制作python生成器+;20个合作项目

通过c+制作python生成器+;20个合作项目,python,c++,yield,c++20,c++-coroutine,Python,C++,Yield,C++20,C++ Coroutine,假设我有以下python代码: def双_输入() 尽管如此: x=收益率 收益率x*2 gen=双_输入() 下一代 打印(一般发送(1)) 它按预期打印“2”。 我可以用c++20制作这样的生成器: #包括 模板 结构生成器{ 结构承诺型; 使用coro_handle=std::coroutine_handle; 结构承诺类型{ T当前_值; 自动获取返回对象(){return generator{coro_handle::from_promise(*this)};} 自动初始化_susp

假设我有以下python代码:

def双_输入()
尽管如此:
x=收益率
收益率x*2
gen=双_输入()
下一代
打印(一般发送(1))
它按预期打印“2”。 我可以用c++20制作这样的生成器:

#包括
模板
结构生成器{
结构承诺型;
使用coro_handle=std::coroutine_handle;
结构承诺类型{
T当前_值;
自动获取返回对象(){return generator{coro_handle::from_promise(*this)};}
自动初始化_suspend(){return std::suspend_always{};}
自动结束_suspend(){return std::suspend_always{};}
无效未处理的_异常(){std::terminate();}
自动屈服值(T值){
当前_值=值;
返回std::suspend_always{};
}
};
bool next(){return coro?(coro.resume(),!coro.done()):false;}
T value(){return coro.promise().current_value;}
发电机(发电机常数和rhs)=删除;
发电机(发电机和右侧)
:coro(右侧coro)
{
rhs.coro=空PTR;
}
~generator(){
如果(coro)
coro.destroy();
}
私人:
生成器(coro_句柄h):coro(h){}
coro_handle coro;
};
生成器hello(){
//TODO:通过co_Wait将字符串发送到这里,但如何发送???
std::string word=“hello world”;
用于(自动和切换:word){
co_-ch;
}
}
int main(int,char**){
for(自动i=hello();i.next();){

如果你想这样做,你必须克服两个问题

P>第一个是C++是静态类型的语言。这意味着所有涉及的所有类型都需要在编译时知道。这就是为什么你的<代码>生成器< /C>类型需要模板的原因,这样用户就可以指定它从协同程序到调用方的类型。< /P> 因此,如果您想拥有这种双向接口,那么
hello
函数中的某些内容必须同时指定输出类型和输入类型

最简单的方法是创建一个对象,并将对该对象的非
常量
引用传递给生成器。每次它执行
cou yield
,调用方可以修改引用的对象,然后请求一个新值。协程可以从引用中读取并查看给定的数据

但是,如果您坚持将coroutine的future类型用作输出和输入,那么您需要同时解决第一个问题(通过使
生成器
模板采用
输出类型
输入类型
)以及第二个问题

请看,您的目标是获取协同路由的值。问题是该值的源(调用协同路由的函数)有一个未来对象。但是协同路由无法访问未来对象。它也无法访问未来引用的promise对象

或者至少,它不可能这么容易做到

在不同的用例中,有两种方法可以实现这一点。第一种方法是操纵协同程序机制,借壳进入承诺。第二种方法是操纵
co\u yield
的属性,基本上做相同的事情

使改变 协同程序的promise对象通常是隐藏的,并且不可从协同程序访问。promise创建的未来对象可以访问该对象,该对象充当承诺数据的接口。但在
co_wait
机器的某些部分期间,也可以访问该对象

具体来说,当您对协同程序中的任何表达式执行
co_wait
时,机器将查看您的承诺类型,以查看它是否具有名为
await_transform
的函数。如果是,它将对您所使用的每个表达式调用该承诺对象的
await_transform
(至少,在您直接编写的
co_wait
中,不是隐式等待,例如由
co_yield
创建的等待)

因此,我们需要做两件事:在promise类型上创建一个
await_transform
重载,并创建一个类型,其唯一目的是允许我们调用该
await_transform
函数

所以看起来是这样的:

struct generator_input {};

...

//Within the promise type:
auto await_transform(generator_input);
一个提示。像这样使用
await_transform
的缺点是,通过为我们的承诺指定此函数的一个重载,我们会影响任何使用此类型的协同程序中的每个
co_wait
。对于生成器协同程序来说,这不是很重要,因为没有太多理由
co_wait
你正在做一个类似这样的黑客。但是如果你正在创建一个更通用的机制,可以明确地等待任意的可等待项作为其生成的一部分,那么你就会遇到一个问题

好的,我们有了这个
await_transform
函数;这个函数需要做什么?它需要返回一个等待对象,因为
co_await
将在它上面等待。但是这个等待对象的目的是传递对输入类型的引用。幸运的是,
co_await
机制用于转换awaitable to a value是由waitiable的
wait\u resume
方法提供的。因此,我们只需返回一个
InputType&

//Within the `generator<OutputType, InputType>`:
    struct passthru_value
    {
        InputType &ret_;

        bool await_ready() {return true;}
        void await_suspend(coro_handle) {}
        InputType &await_resume() { return ret_; }
    };


//Within the promise type:
auto await_transform(generator_input)
{
    return passthru_value{input_value}; //Where `input_value` is the `InputType` object stored by the promise.
}
struct yield_thru
{
    InputType &ret_;

    bool await_ready() {return false;}
    void await_suspend(coro_handle) {}
    InputType &await_resume() { return ret_; }
};

...

//in the promise
auto yield_value(OutputType value) {
    current_value = value;
    return yield_thru{input_value};
}
这代表了一种不对称的传输机制。协程在自己选择的时间和地点检索值。因此,它没有真正的义务立即响应任何更改。这在某些方面是好的,因为它允许协程将自身与有害更改隔离开来。如果您使用基于范围的
for
循环到一个容器上,这个容器不能被外部世界直接修改(在大多数情况下),否则你的程序将显示UB。因此,如果协程以这种方式脆弱,它可以从用户那里复制数据,从而阻止用户修改它

总而言之,所需的代码并没有那么大
#include <coroutine>
#include <exception>
#include <string>
#include <iostream>

struct generator_input {};


template <typename OutputType, typename InputType>
struct generator {
    struct promise_type;
    using coro_handle = std::coroutine_handle<promise_type>;

    struct passthru_value
    {
        InputType &ret_;

        bool await_ready() {return true;}
        void await_suspend(coro_handle) {}
        InputType &await_resume() { return ret_; }
    };

    struct promise_type {
        OutputType current_value;
        InputType input_value;


        auto get_return_object() { return generator{coro_handle::from_promise(*this)}; }
        auto initial_suspend() { return std::suspend_always{}; }
        auto final_suspend() { return std::suspend_always{}; }
        void unhandled_exception() { std::terminate(); }
        auto yield_value(OutputType value) {
            current_value = value;
            return std::suspend_always{};
        }

        void return_void() {}

        auto await_transform(generator_input)
        {
            return passthru_value{input_value};
        }
    };

    bool next() { return coro ? (coro.resume(), !coro.done()) : false; }
    OutputType value() { return coro.promise().current_value; }

    void send(const InputType &input)
    {
        coro.promise().input_value = input;
    } 

    void send(InputType &&input)
    {
        coro.promise().input_value = std::move(input);
    } 

    generator(generator const & rhs) = delete;
    generator(generator &&rhs)
        :coro(rhs.coro)
    {
        rhs.coro = nullptr;
    }
    ~generator() {
        if (coro)
            coro.destroy();
    }
private:
    generator(coro_handle h) : coro(h) {}
    coro_handle coro;
};

generator<char, std::string> hello(){
    auto word = co_await generator_input{};

    for(auto &ch: word){
        co_yield ch;
    }
}

int main(int, char**)
{
    auto test = hello();
    test.send("hello world");

    while(test.next())
    {
        std::cout << test.value() << ' ';
    }
}
struct yield_thru
{
    InputType &ret_;

    bool await_ready() {return false;}
    void await_suspend(coro_handle) {}
    InputType &await_resume() { return ret_; }
};

...

//in the promise
auto yield_value(OutputType value) {
    current_value = value;
    return yield_thru{input_value};
}