如果你想这样做,你基本上有两个问题需要克服。
首先,C++是一种静态类型语言。这意味着在编译时需要知道所涉及的所有内容的类型。这就是为什么您的generator类型需要是模板,以便用户可以指定它从协程到调用者的引导类型。
因此,如果您想拥有这个双向接口,那么您的函数中的某些内容hello必须指定输出类型和输入类型。
最简单的方法是创建一个对象并将const对该对象的非引用传递给生成器。每次执行a时co_yield,调用者都可以修改引用的对象,然后请求新值。协程可以从引用中读取并查看给定的数据。
但是,如果您坚持使用协程的未来类型作为输出和输入,那么您需要解决第一个问题(通过使模板generator采用OutputType和InputType)以及第二个问题。
或者至少,它不能那么容易做到。
有两种方法可以根据不同的用例来实现此目的。第一个操纵协程机制,通过后门进入Promise。第二个操作的属性co_yield来执行基本相同的操作。
转换
协程的Promise对象通常是隐藏的并且无法从协程访问。它可以被Promise创建的future对象访问,并充当Promise数据的接口。但在机器的某些部分也可以访问它co_await。
具体来说,当您对协程中的任何表达式执行a时co_await,机器会查看您的Promise类型以查看它是否具有名为的函数await_transform。await_transform如果是这样,它将在您使用的每个表达式上调用该Promise对象co_await(至少在co_await您直接编写的表达式中,而不是隐式等待,例如由所创建的表达式co_yield)。
因此,我们需要做两件事:创建Promise类型的重载await_transform,并创建一个其唯一目的是允许我们调用该await_transform函数的类型。
所以看起来像这样:
structgenerator_input{};
...
//Withinthepromisetype:
autoawait_transform(generator_input);
快速说明一下。await_transform像这样使用的缺点是,通过为我们的Promise指定该函数的一个重载,我们会影响使用该类型的任何协程中的每个co_await重载。co_await对于生成器协程来说,这并不是很重要,因为除非您进行这样的黑客攻击,否则没有太多理由这样做。但是,如果您正在创建一个更通用的机制,可以在其生成过程中明确等待任意可等待项,那么您就会遇到问题。
OK,这样我们就有了这个await_transform功能;这个函数需要做什么?它需要返回一个可等待的对象,因为co_await它将等待它。但这个可等待对象的目的是传递对输入类型的引用。co_await幸运的是,用于将可等待转换为值的机制是由可等待的await_resume方法提供的。所以我们可以只返回一个InputType&:
//Withinthe`generator
structpassthru_value
{
InputType&ret_;
boolawait_ready(){returntrue;}
voidawait_suspend(coro_handle){}
InputType&await_resume(){returnret_;}
};
autoawait_transform(generator_input)
returnpassthru_value{input_value};//Where`input_value`isthe`InputType`objectstoredbythepromise.
}
这使协程可以通过调用来访问该值co_awaitgenerator_input{};。请注意,这将返回对该对象的引用。
该generator类型可以轻松修改,以允许修改InputType存储在Promise中的对象。只需添加一对send函数即可覆盖输入值:
voidsend(constInputType&input)
coro.promise().input_value=input;
voidsend(InputType&&input)
coro.promise().input_value=std::move(input);
总而言之,所需的代码并没有那么大。下面是经过这些修改的代码的可运行示例:
#include
#include
#include
#include
template
structgenerator{
structpromise_type;
usingcoro_handle=std::coroutine_handle
structpromise_type{
OutputTypecurrent_value;
InputTypeinput_value;
autoget_return_object(){returngenerator{coro_handle::from_promise(*this)};}
autoinitial_suspend(){returnstd::suspend_always{};}
autofinal_suspend(){returnstd::suspend_always{};}
voidunhandled_exception(){std::terminate();}
autoyield_value(OutputTypevalue){
current_value=value;
returnstd::suspend_always{};
voidreturn_void(){}
returnpassthru_value{input_value};
boolnext(){returncoro(coro.resume(),!coro.done()):false;}
OutputTypevalue(){returncoro.promise().current_value;}
generator(generatorconst&rhs)=delete;
generator(generator&&rhs)
:coro(rhs.coro)
rhs.coro=nullptr;
~generator(){
if(coro)
coro.destroy();
private:
generator(coro_handleh):coro(h){}
coro_handlecoro;
generator
autoword=co_awaitgenerator_input{};
for(auto&ch:word){
co_yieldch;
intmain(int,char**)
autotest=hello();
test.send("helloworld");
while(test.next())
变得更有收获
使用显式的替代方法co_await是利用的属性co_yield。即,co_yield是一个表达式,因此它有一个值。具体来说,它(大部分)相当于co_awaitp.yield_value(e)Promisep对象(哦!),并且e是我们要产生的对象。
幸运的是,我们已经有了一个yield_value函数;它返回std::suspend_always。但它也可以返回一个始终挂起的对象,但也可以将其co_await解包为InputType&:
structyield_thru
boolawait_ready(){returnfalse;}
//inthepromise
returnyield_thru{input_value};
这是一种对称传输机制;对于您产生的每一个值,您都会收到一个值(可能与以前的值相同)。与显式方法不同,在开始生成它们之前co_await您无法接收值。这对于某些接口可能很有用。