假设我有以下python代码:
def double_inputs():
while True:
x = yield
yield x * 2
gen = double_inputs()
next(gen)
print(gen.send(1))
它按预期打印“2”。 我可以用c++20制作这样的生成器:
#include <coroutine>
template <class T>
struct generator {
struct promise_type;
using coro_handle = std::coroutine_handle<promise_type>;
struct promise_type {
T current_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(T value) {
current_value = value;
return std::suspend_always{};
}
};
bool next() { return coro ? (coro.resume(), !coro.done()) : false; }
T value() { return coro.promise().current_value; }
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> hello(){
//TODO:send string here via co_await, but HOW???
std::string word = "hello world";
for(auto &ch:word){
co_yield ch;
}
}
int main(int, char**) {
for (auto i = hello(); i.next(); ) {
std::cout << i.value() << ' ';
}
}
这个生成器只是一个字母一个字母地生成一个字符串,但是字符串是硬编码的。在python中,不仅可以从生成器中生成某些内容,还可以向其生成某些内容。我相信它可以通过C++中的COEAWAIT来完成。p>
我需要它像这样工作:
generator<char> hello(){
std::string word = co_await producer; // Wait string from producer somehow
for(auto &ch:word){
co_yield ch;
}
}
int main(int, char**) {
auto gen = hello(); //make consumer
producer("hello world"); //produce string
for (; gen.next(); ) {
std::cout << gen.value() << ' '; //consume string letter by letter
}
}
我怎样才能做到这一点?如何使用c++20协同程序制作这个“生产者”
如果你想这样做,你必须克服两个问题
首先,C++是一种静态类型的语言。这意味着需要在编译时知道所涉及的所有内容的类型。这就是为什么您的generator
类型需要是一个模板,以便用户可以指定它从协同路由传递给调用方的类型因此,如果您想拥有这个双向接口,那么
hello
函数上的something必须同时指定输出类型和输入类型最简单的方法是创建一个对象,并将对该对象的非
const
引用传递给生成器。每次执行co_yield
时,调用方都可以修改引用的对象,然后请求一个新值。协同程序可以从引用中读取并查看给定的数据但是,如果您坚持使用coroutine的future类型作为输出和输入,那么您需要同时解决第一个问题(通过使
generator
模板采用OutputType
和InputType
)以及第二个问题看,您的目标是为协同程序获取一个值。问题是该值的源(调用协同程序的函数)有一个未来的对象。但是协同程序无法访问未来对象。它也不能访问将来引用的promise对象
或者至少,它不可能这么容易做到
有两种方法可以实现这一点,使用不同的用例。第一个操纵协同程序机器以后门方式进入承诺。第二个操作
co_yield
的属性来做基本相同的事情转化
协同程序的promise对象通常是隐藏的,并且无法从协同程序访问。它可以被promise创建的未来对象访问,该对象充当承诺数据的接口。但在{}机器的某些部分也可以访问它
具体地说,当您对协同例程中的任何表达式执行
co_await
时,机器将查看您的承诺类型,以查看它是否具有名为await_transform
的函数。如果是这样,它将调用promise对象的await_transform
on每个co_await
表达式(至少,在直接编写的co_await
中,不是隐式等待,例如由co_yield
创建的表达式)因此,我们需要做两件事:在promise类型上创建一个
await_transform
重载,并创建一个类型,其唯一目的是允许我们调用该await_transform
函数所以看起来是这样的:
一个简短的提示。像这样使用
await_transform
的缺点是,通过为我们的承诺指定此函数的一个重载,我们会影响使用此类型的任何协同例程中的每个co_await
。对于生成器协同程序来说,这不是很重要,因为没有太多理由co_await
,除非您正在进行这样的hack。但是,如果您正在创建一个更通用的机制,可以在生成过程中明确地等待任意的可等待项,那么您将遇到一个问题好的,我们有这个
await_transform
函数;这个函数需要做什么?它需要返回一个可等待的对象,因为co_await
将等待它。但是这个等待对象的目的是传递对输入类型的引用。幸运的是,co_await
用于将等待值转换为值的机制是由等待值的await_resume
方法提供的。所以我们可以返回一个InputType&
:这通过调用
co_await generator_input{};
使协同程序能够访问该值。请注意,这将返回对对象的引用可以很容易地修改
generator
类型,以允许修改promise中存储的InputType
对象。只需添加一对send
函数即可覆盖输入值:这代表了一种不对称的传输机制。协同程序在自己选择的时间和地点检索值。因此,它不属于真正的义务您必须立即响应任何更改。这在某些方面是好的,因为它允许协同程序将自己与有害的变化隔离开来。如果您在容器上使用基于范围的
for
循环,那么外部世界无法直接修改该容器(在大多数情况下),否则您的程序将显示UB。因此,如果协同路由以这种方式脆弱,它可以从用户复制数据,从而防止用户修改数据总之,所需的代码没有那么大。这里有一个run-able example of your code和这些修改:
更加屈服
使用显式
co_await
的替代方法是利用co_yield
的属性。也就是说,co_yield
是一个表达式,因此它有一个值。具体地说,它(大部分)相当于co_await p.yield_value(e)
,其中p
是承诺对象(ohh!),而e
是我们正在得到的幸运的是,我们已经有了一个
yield_value
函数;它返回std::suspend_always
。但是它也可以返回一个总是挂起的对象,但是也,它co_await
可以解压成InputType&
:这是一种对称的传输机制;对于您产生的每个值,您都会收到一个值(可能与以前相同)。与显式
co_await
方法不同,在开始生成值之前,不能接收值。这对于某些接口可能很有用当然,你可以根据自己的喜好把它们组合起来
相关问题 更多 >
编程相关推荐