1 回答

TA貢獻(xiàn)1772條經(jīng)驗(yàn) 獲得超6個(gè)贊
如果你想這樣做,你基本上有兩個(gè)問題需要克服。
首先,C++ 是一種靜態(tài)類型語言。這意味著在編譯時(shí)需要知道所涉及的所有內(nèi)容的類型。這就是為什么您的generator類型需要是模板,以便用戶可以指定它從協(xié)程到調(diào)用者的引導(dǎo)類型。
因此,如果您想擁有這個(gè)雙向接口,那么您的函數(shù)中的某些內(nèi)容hello必須指定輸出類型和輸入類型。
最簡(jiǎn)單的方法是創(chuàng)建一個(gè)對(duì)象并將const對(duì)該對(duì)象的非引用傳遞給生成器。每次執(zhí)行 a 時(shí)co_yield,調(diào)用者都可以修改引用的對(duì)象,然后請(qǐng)求新值。協(xié)程可以從引用中讀取并查看給定的數(shù)據(jù)。
但是,如果您堅(jiān)持使用協(xié)程的未來類型作為輸出和輸入,那么您需要解決第一個(gè)問題(通過使模板generator采用OutputType和InputType)以及第二個(gè)問題。
看,您的目標(biāo)是為協(xié)程獲取值。問題在于該值的來源(調(diào)用協(xié)程的函數(shù))有一個(gè) future 對(duì)象。但協(xié)程無法訪問future 對(duì)象。它也無法訪問 future 引用的 Promise 對(duì)象。
或者至少,它不能那么容易做到。
有兩種方法可以根據(jù)不同的用例來實(shí)現(xiàn)此目的。第一個(gè)操縱協(xié)程機(jī)制,通過后門進(jìn)入 Promise。第二個(gè)操作 的屬性co_yield來執(zhí)行基本相同的操作。
轉(zhuǎn)換
協(xié)程的 Promise 對(duì)象通常是隱藏的并且無法從協(xié)程訪問。它可以被 Promise 創(chuàng)建的 future 對(duì)象訪問,并充當(dāng) Promise 數(shù)據(jù)的接口。但在機(jī)器的某些部分也可以訪問它c(diǎn)o_await。
具體來說,當(dāng)您對(duì)協(xié)程中的任何表達(dá)式執(zhí)行 a 時(shí)co_await,機(jī)器會(huì)查看您的 Promise 類型以查看它是否具有名為 的函數(shù)await_transform。await_transform如果是這樣,它將在您使用的每個(gè)表達(dá)式上調(diào)用該 Promise 對(duì)象co_await(至少在co_await您直接編寫的表達(dá)式中,而不是隱式等待,例如由 所創(chuàng)建的表達(dá)式co_yield)。
因此,我們需要做兩件事:創(chuàng)建 Promise 類型的重載await_transform,并創(chuàng)建一個(gè)其唯一目的是允許我們調(diào)用該await_transform函數(shù)的類型。
所以看起來像這樣:
struct generator_input {};
...
//Within the promise type:
auto await_transform(generator_input);
快速說明一下。await_transform像這樣使用的缺點(diǎn)是,通過為我們的 Promise 指定該函數(shù)的一個(gè)重載,我們會(huì)影響使用該類型的任何協(xié)程中的每個(gè) co_await重載。co_await對(duì)于生成器協(xié)程來說,這并不是很重要,因?yàn)槌悄M(jìn)行這樣的黑客攻擊,否則沒有太多理由這樣做。但是,如果您正在創(chuàng)建一個(gè)更通用的機(jī)制,可以在其生成過程中明確等待任意可等待項(xiàng),那么您就會(huì)遇到問題。
OK,這樣我們就有了這個(gè)await_transform功能;這個(gè)函數(shù)需要做什么?它需要返回一個(gè)可等待的對(duì)象,因?yàn)閏o_await它將等待它。但這個(gè)可等待對(duì)象的目的是傳遞對(duì)輸入類型的引用。co_await幸運(yùn)的是,用于將可等待轉(zhuǎn)換為值的機(jī)制是由可等待的await_resume方法提供的。所以我們可以只返回一個(gè)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.
}
這使協(xié)程可以通過調(diào)用來訪問該值co_await generator_input{};。請(qǐng)注意,這將返回對(duì)該對(duì)象的引用。
該generator類型可以輕松修改,以允許修改InputType存儲(chǔ)在 Promise 中的對(duì)象。只需添加一對(duì)send函數(shù)即可覆蓋輸入值:
void send(const InputType &input)
{
coro.promise().input_value = input;
}
void send(InputType &&input)
{
coro.promise().input_value = std::move(input);
}
這代表了一種不對(duì)稱的傳輸機(jī)制。協(xié)程在自己選擇的地點(diǎn)和時(shí)間檢索值。因此,它沒有真正的義務(wù)立即響應(yīng)任何變化。這在某些方面是好的,因?yàn)樗试S協(xié)程將自身與有害的更改隔離開來。如果您在容器上使用基于范圍的for循環(huán),則該容器不能被外界直接修改(以大多數(shù)方式),否則您的程序?qū)⒊霈F(xiàn) UB。因此,如果協(xié)程在這種情況下很脆弱,它可以從用戶那里復(fù)制數(shù)據(jù),從而阻止用戶修改它。
總而言之,所需的代碼并沒有那么大。下面是經(jīng)過這些修改的代碼的可運(yùn)行示例:
#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() << ' ';
}
}
變得更有收獲
使用顯式的替代方法co_await是利用 的屬性co_yield。即,co_yield是一個(gè)表達(dá)式,因此它有一個(gè)值。具體來說,它(大部分)相當(dāng)于co_await p.yield_value(e)Promisep對(duì)象(哦?。?,并且e是我們要產(chǎn)生的對(duì)象。
幸運(yùn)的是,我們已經(jīng)有了一個(gè)yield_value函數(shù);它返回std::suspend_always。但它也可以返回一個(gè)始終掛起的對(duì)象,但也可以將其co_await解包為InputType&:
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};
}
這是一種對(duì)稱傳輸機(jī)制;對(duì)于您產(chǎn)生的每一個(gè)值,您都會(huì)收到一個(gè)值(可能與以前的值相同)。與顯式方法不同,在開始生成它們之前co_await您無法接收值。這對(duì)于某些接口可能很有用。
當(dāng)然,您可以根據(jù)需要將它們組合起來。
添加回答
舉報(bào)