本文授權(quán)自知乎用戶IceBear,點(diǎn)擊文末”閱讀原文“查看原文章。
C++20 給我們帶來(lái)了非常現(xiàn)代化的協(xié)程特性,但是主要的增加都集中于語(yǔ)核部分。由于庫(kù)特性尚未準(zhǔn)備充分,所以 C++20 標(biāo)準(zhǔn)庫(kù)中尚沒(méi)有多少現(xiàn)成的、組裝好的協(xié)程設(shè)施供我們使用。但是!僅僅通過(guò)使用 std::coroutine_handle
(這是一個(gè)編譯器為之開(kāi)洞的類(lèi))并在你的類(lèi)中定制好幾個(gè)規(guī)定的接口,我們就可以組合出五花八門(mén)的功能。你可以理解為,雖然我們沒(méi)有現(xiàn)成的飛機(jī)、火車(chē),但是我們有沙子和鐵礦石!完全可以從沙子和鐵礦石出發(fā),造出飛機(jī)、火車(chē)。我知道很多人詬病 C++ 的這個(gè)特點(diǎn),沒(méi)有現(xiàn)成的這個(gè)、現(xiàn)成的那個(gè),自己造很麻煩。但是這也是我比較喜歡 C++ 的一點(diǎn)——上限非常高,你可以為自己的飛機(jī)、火車(chē)做定制,加上你想要的功能或去掉你不想要的功能;除此以外,你甚至還可以造出之前還沒(méi)有問(wèn)世的東西,比如星艦!在其他語(yǔ)言中,語(yǔ)言和標(biāo)準(zhǔn)庫(kù)給你提供了什么就是什么了,你幾乎沒(méi)有超越的能力。
在 C++23 周期,LEWG (庫(kù)特性工作組) 在新冠肆虐的艱難背景下完成了大量的工作,使得 C++23 增添了不少很有益的設(shè)施(可參考 C++23特性總結(jié) - 上 - Mick235711的文章 - 知乎 https://zhuanlan.zhihu.com/p/562383157)。但是,對(duì)于協(xié)程方面的內(nèi)容還是舉棋不定。std::generator
和 std::lazy
在合并的計(jì)劃里進(jìn)進(jìn)出出,而最終只有 std::generator
達(dá)成目標(biāo)。對(duì)于更花花綠綠的協(xié)程庫(kù),像 task
等,則可憐的連提案都沒(méi)有。
另外,在可預(yù)見(jiàn)的將來(lái),哪怕標(biāo)準(zhǔn)庫(kù)收錄了一些基本的協(xié)程類(lèi),為了探索更加花花綠綠的協(xié)程高級(jí)功能,我們還是需要從最基本的協(xié)程設(shè)施出發(fā),也就是理解 std::coroutine_handle
和相應(yīng)的接口。
本文將向讀者展示如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的生成器 (generator) 和一個(gè)能支持協(xié)程內(nèi)外雙向信息傳遞的生成器。網(wǎng)上關(guān)于什么是協(xié)程的講解很多,哪怕是講 C++ 協(xié)程的中文材料也不少。且因筆者時(shí)間精力有限,本文不會(huì)詳細(xì)地介紹協(xié)程概念,而是更側(cè)重于展示怎么實(shí)現(xiàn)出一個(gè) generator,以填補(bǔ)相關(guān)參考資料的不足。
1
感性的了解一下協(xié)程
協(xié)程可以簡(jiǎn)單地理解為:一個(gè)執(zhí)行時(shí)可以主動(dòng)打斷,程序執(zhí)行流程可以在調(diào)用方和被調(diào)用方之間進(jìn)進(jìn)出出若干次的函數(shù)。
Python 是最早的一批支持協(xié)程的語(yǔ)言,我們不妨用 Python 來(lái)演示一下協(xié)程的神奇。(其實(shí)早在 19 年,那時(shí) C++ 編譯器還沒(méi)支持協(xié)程的時(shí)候,筆者就是利用 Python 來(lái)理解協(xié)程的)

從這個(gè)例子我們可以看出以下幾點(diǎn)怪異的現(xiàn)象:
1) myrange
函數(shù)中并沒(méi)有一句 return
語(yǔ)句,我們卻調(diào)用 myrange
函數(shù)得到了一個(gè) gen
對(duì)象(第 22 行)
2) 22 行調(diào)用 myrange
函數(shù)后,這個(gè)函數(shù)似乎并沒(méi)有立即開(kāi)始執(zhí)行(沒(méi)有執(zhí)行第 3 行的 print
語(yǔ)句,倒是執(zhí)行第 23 行的 print
語(yǔ)句了)
3) 調(diào)用 gen
對(duì)象的 __next__
方法后,myrange
函數(shù)開(kāi)始執(zhí)行。執(zhí)行到第 7 行時(shí),myrange
函數(shù) "yield" 了一個(gè)值,然后程序的執(zhí)行流程又切換到主函數(shù)的第 24 行。__next__
方法得到了剛剛 "yield" 的結(jié)果。
4) 26 行再次調(diào)用 __next__
時(shí),執(zhí)行流程回到了 myrange
中。而且并不是從 myrange
的開(kāi)頭重新開(kāi)始執(zhí)行,而是從上一次 "yield" 的地方,也就是第 7 行繼續(xù)執(zhí)行。i 的值似乎也沒(méi)受到影響。
如果你熟悉了協(xié)程的特點(diǎn),這無(wú)非可以概括為,協(xié)程執(zhí)行時(shí)可以主動(dòng)打斷(更學(xué)術(shù)一點(diǎn)叫掛起)自己,將控制權(quán)交還給調(diào)用方。協(xié)程掛起期間,協(xié)程的棧上信息都可以得到保留。協(xié)程恢復(fù)后,從上一次的掛起點(diǎn)繼續(xù)執(zhí)行。
經(jīng)過(guò)封裝后的 C++ 協(xié)程庫(kù),也可以向用戶展示出和 Python 中幾乎完全一致的用法。如下就是我們將要實(shí)現(xiàn)的 generator
所展示的應(yīng)用效果。

當(dāng)然,C++ 畢竟是一個(gè)靜態(tài)類(lèi)型的語(yǔ)言,除了 range
函數(shù)要寫(xiě) “返回值” generator<int>
(當(dāng)然實(shí)際上 range
是一個(gè)協(xié)程,代碼 81 行只是借用了原來(lái)的函數(shù)和返回值語(yǔ)法了,嚴(yán)格來(lái)說(shuō)不能認(rèn)為這里聲明了一個(gè)返回 generator<int>
類(lèi)型的函數(shù)) ,以及 90 行需要聲明 gen
的類(lèi)型以外, 可以說(shuō)和 Python 沒(méi)什么兩樣了。
2
std::coroutine_handle
std::coroutine_handle
類(lèi)模板是為我們實(shí)現(xiàn)協(xié)程的各種“魔法”提供支持的最底層的設(shè)施,其主要負(fù)責(zé)存儲(chǔ)協(xié)程的句柄。它分為模板 std::coroutine_handle<Promise>
和模板特化 std::coroutine_handle<void>
,std::coroutine_handle<void>
可以簡(jiǎn)單理解為就是做了類(lèi)型擦除的 std::coroutine_handle<Promise>
。
打開(kāi) <coroutine>
頭文件我們不難看出,std::coroutine_handle<Promise>
中的不少方法是依靠 __builtin 內(nèi)建函數(shù),也就是編譯器開(kāi)洞實(shí)現(xiàn)的。


std::coroutine_handle
中保存了協(xié)程的上下文。我們協(xié)程執(zhí)行到哪兒切出去了(協(xié)程切換回來(lái)后從哪兒開(kāi)始繼續(xù)執(zhí)行)?我們協(xié)程的棧上變量在協(xié)程切出去期間怎么能得到保留?這些問(wèn)題的解決都是歸功于 std::coroutine_handle
保存了協(xié)程的上下文。
std::coroutine_handle
中的方法不多,但是各個(gè)都至關(guān)重要。由于有些概念還沒(méi)有鋪墊,我們先只羅列三個(gè)比較容易理解的方法:
done
方法,可以查詢一個(gè)協(xié)程是否已經(jīng)結(jié)束;
resume
方法可以恢復(fù)一個(gè)協(xié)程的執(zhí)行;
destroy
方法可以銷(xiāo)毀一個(gè)協(xié)程。
std::coroutine_handle
只是一個(gè)很底層很底層的設(shè)施,沒(méi)有 RAII 包裹。它就像裸指針一樣(其實(shí)它內(nèi)部也就是一個(gè)裸指針),需要靠我們手動(dòng)創(chuàng)建、手動(dòng)銷(xiāo)毀。我們剛剛談到,std::coroutine_handle
保存了協(xié)程的上下文,其中就有棧上變量的信息。如果一個(gè) handle 被創(chuàng)建出來(lái),用完以后我們忘了對(duì)它調(diào)用 destroy 了,那么其中存儲(chǔ)的上下文信息當(dāng)然也就沒(méi)有被銷(xiāo)毀——也就是內(nèi)存泄漏了。如果不小心做了兩次 destroy,那么就可能會(huì)引發(fā) double free 錯(cuò)誤了。所以,我們得寫(xiě)一個(gè) RAII 類(lèi)將其包裝起來(lái),以解決忘記銷(xiāo)毀或者其他比如淺拷貝等問(wèn)題。
modules
、concepts
、ranges
等作了詳細(xì)介紹。全卷內(nèi)容質(zhì)量上乘,精雕細(xì)琢,非那些在歷史舊版本的基礎(chǔ)上草草加兩章節(jié)新內(nèi)容圈錢(qián)的書(shū)可比也!非常感謝清華大學(xué)出版社對(duì)這篇文章的贊助!本著對(duì)我的讀者負(fù)責(zé)的態(tài)度,我堅(jiān)持要求審讀完書(shū)的大部分內(nèi)容后才能做推薦,清華大學(xué)出版社的編輯對(duì)此給予了高度支持。且,從 8 月份聯(lián)系我開(kāi)始,到本文落筆,編輯非常寬容地給了我 4 個(gè)月的時(shí)間 —— 一段非常充足的時(shí)間閱讀了這兩本書(shū),之后才有了這里的精心推薦。再次表示致敬和謝意!

3
generator
我們的 generator
類(lèi)終于出場(chǎng)了。
首先,C++ 的協(xié)程要求 generator
中必須有 promise_type
這個(gè)類(lèi)型。你可以通過(guò) typedef
/using
的方式 alias 一個(gè)別名,也可以干脆就定義成 generator
的內(nèi)部類(lèi) —— 本文選擇后者。
template <typename T>
struct generator
{
struct promise_type
{
}
};
promise_type
中有這么幾個(gè)可定制的、會(huì)影響協(xié)程行為的重要接口,先介紹兩個(gè):
1) initial_suspend
—— 它回答的是協(xié)程一出生時(shí)是否就要馬上掛起的問(wèn)題;
2) final_suspend
—— 它回答的是協(xié)程最后一次是否要掛起的問(wèn)題;
對(duì)于一個(gè) generator
而言,這兩個(gè)問(wèn)題的回答是:
初始時(shí)始終都要掛起,最后一次始終都不掛起。
std::suspend_always
std::suspend_never
是標(biāo)準(zhǔn)庫(kù)里面已經(jīng)定義好的類(lèi)型,可以方便地回答是否要掛起的問(wèn)題。
struct promise_type
{
...
std::suspend_always initial_suspend() const
{
return {};
}
std::suspend_never final_suspend() const noexcept
// 這里注意一下,由于 final_suspend 在收尾階段工作,所以必須是 noexcept 的
{
return {};
}
}
在新協(xié)程創(chuàng)建完畢好后,C++ 會(huì)執(zhí)行 co_await promise.initial_suspend()
,同樣的, 在協(xié)程結(jié)束前也會(huì) co_await promise.final_suspend()
。當(dāng)然了,從名字中我們也能看出,co_await
一個(gè) std::suspend_always
時(shí),執(zhí)行流程永遠(yuǎn)都會(huì)無(wú)條件切出去,而對(duì)于 std::suspend_never
則是永遠(yuǎn)也不會(huì)切出。
還記得我們剛剛觀察的 Python 代碼中的現(xiàn)象嗎?主函數(shù)中調(diào)用 myrange
的時(shí)候,是不是立馬得到一個(gè) gen
對(duì)象的?是不是 myrange
里面沒(méi)有立即得到執(zhí)行的?在第一次調(diào)用 __next__
的時(shí)候才會(huì)去執(zhí)行的吧?這其實(shí)就是因?yàn)?nbsp;myrange
協(xié)程一創(chuàng)建好就掛起自己將程序流程切回到調(diào)用方了。
如果 initial_suspend
這里回答的是 suspend_never
,那么協(xié)程就會(huì)立刻開(kāi)始執(zhí)行。
建議等 generator
實(shí)現(xiàn)完成后讀者自己動(dòng)手實(shí)踐下,將 initial_suspend
和 final_suspend
的回答換換,看看結(jié)果會(huì)有什么改變。
3) promise_type
中第三個(gè)定制接口是 unhandled_exception
,它回答的是協(xié)程被其里頭的沒(méi)有捕獲的異常終止時(shí)做何處理的問(wèn)題。
我們這里只是簡(jiǎn)單處理一下,調(diào)用 exit
提前終止程序。當(dāng)然這樣的做法太簡(jiǎn)化了,實(shí)際應(yīng)用時(shí)可以考慮使用 std::exception_ptr
等設(shè)施做更嚴(yán)謹(jǐn)?shù)奶幚怼?/p>
struct promise_type
{
...
void unhandled_exception()
{
std::exit(EXIT_FAILURE);
}
}
4) promise_type
中第四個(gè)定制接口,也是最核心的一個(gè)是 get_return_object
。這個(gè)方法也涉及到了如何創(chuàng)建一個(gè) coroutine 的問(wèn)題 —— 答案就是使用 std::coroutine_handle<promise_type>::from_promise(*this)
,即從自己這個(gè) promise ( 也就是*this
) 創(chuàng)建一個(gè) coroutine (from_promise
得到的就是一個(gè) coroutine_handle
)。generator
中也需要配合,提供一個(gè)接受 coroutine_handle
類(lèi)型的構(gòu)造函數(shù),將剛剛構(gòu)造出的 coroutine_handle
保存。
現(xiàn)在,通過(guò)get_return_object
得到了 return_object
,就會(huì)開(kāi)始詢問(wèn)是否要做 initial_suspend
了 (剛剛介紹的 initial_suspend
還記得嗎?)
template <typename T>
struct generator
{
struct promise_type;
std::coroutine_handle<promise_type> handle;
struct promise_type
{
...
generator get_return_object()
{
return generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
};
generator(std::coroutine_handle<promise_type> handle) :
handle(handle)
{
}
...
};
我們之前也提到,coroutine_handle
是無(wú) RAII 的,因此 generator
中得根據(jù)三/五法則,做好 RAII。該析構(gòu)的析構(gòu),禁止拷貝,寫(xiě)好移動(dòng)構(gòu)造/移動(dòng)賦值。
template <typename T>
struct generator
{
struct promise_type;
std::coroutine_handle<promise_type> handle;
...
public:
generator() = default;
generator(const generator &) = delete;
private:
generator(std::coroutine_handle<promise_type> handle) :
handle(handle)
{
}
public:
generator(generator && src) :
handle(src.handle)
{
src.handle = nullptr;
}
generator& operator=(const generator &) = delete;
generator& operator=(generator && src)
{
if (!handle) {
handle.destroy();
}
handle = src.handle;
src.handle = nullptr;
}
~generator()
{
if (!handle) {
handle.destroy();
}
}
...
};
5) 定制 yield_value
接口
接下來(lái)的定制則對(duì)于 generator
來(lái)說(shuō)至關(guān)重要,我們馬上就可以讓我們的 generator
支持 yield 了!
還是以此舉例,co_yield
關(guān)鍵字實(shí)際上只是一個(gè)語(yǔ)法糖,這一行會(huì)被編譯器替換為 co_await promise.yield_value(i)
,在有了 initial_suspend
和 final_suspend
的經(jīng)驗(yàn)后,我們這次也就能很容易地猜測(cè)出,我們要在 promise_type
中實(shí)現(xiàn)一個(gè) yield_value
方法,而返回值負(fù)責(zé)回答要不要切出的問(wèn)題。顯然,每次 yield 時(shí)總是要掛起協(xié)程,所以,yield_value
方法的返回值類(lèi)型應(yīng)當(dāng)是 suspend_always
。你猜對(duì)了嗎?
struct promise_type
{
....
std::optional<T> opt;
template <typename Arg>
std::suspend_always yield_value(Arg && arg)
{
opt.emplace(std::forward<Arg>(arg));
return {};
}
};
在 promise
中,我們還增加了一個(gè) optional
,用以存放 yield 的結(jié)果。注意,很多例子,甚至包括 cppreference 在內(nèi),promise
內(nèi)部都是用的 T
類(lèi)型的裸值來(lái)存放 yield 的結(jié)果的。在模板編程中這樣做兼容性不太好,我們需要考慮能照顧到不可默認(rèn)構(gòu)造的類(lèi)型。除此以外,我們使用萬(wàn)能引用和完美轉(zhuǎn)發(fā)以提升構(gòu)造值時(shí)的性能。
而這個(gè) opt
,當(dāng)然是在等 generator
來(lái)取它的。
template <typename T>
struct generator
{
...
T & next()
{
handle.resume();
if (handle.done()) {
throw generator_done("generator done");
}
return *(handle.promise().opt);
}
};
generator<int> range(int n)
{
for(int i = 0; i < n; ++i) {
co_yield i;
}
}
int main()
{
generator<int> gen = range(4);
for (int i = 0; i < 4; ++i) {
std::cout << gen.next() << std::endl;
}
}
這里需要結(jié)合前文介紹過(guò)的內(nèi)容梳理下。由于 initial_suspend
的返回值是 suspend_always
,所以協(xié)程剛創(chuàng)建好后就切出,執(zhí)行流程到了 gen = range(4)
。
再下面,每次對(duì) gen
調(diào)用 next
方法時(shí),會(huì)執(zhí)行 handle.resume()
恢復(fù)協(xié)程。
協(xié)程首次恢復(fù)運(yùn)行,當(dāng)然是從 range
函數(shù)的開(kāi)頭開(kāi)始執(zhí)行 (如果不是首次恢復(fù)運(yùn)行,當(dāng)然就是從上一次 yield 出去的地方恢復(fù)運(yùn)行),直到碰上了 co_yield
。這時(shí), 調(diào)用 promise.yield_value(i)
,根據(jù) co_yield
后面值 (也就是 i
) 構(gòu)造好了值保存在 opt
中。隨后,由于 promise.yield_value(i)
的結(jié)果是 suspend_always
,所以協(xié)程切出, 執(zhí)行流程回到了 handle.resume()
之后。正常情況下 (協(xié)程沒(méi)有執(zhí)行完畢),next
方法就會(huì)從 promise
里的那個(gè) optional
中取出 yield 的結(jié)果,返回給主函數(shù)中以供輸出。如果檢測(cè)到已經(jīng)是最后一次 yield 后再調(diào)用 next
的 (即 resume 后檢測(cè)到 done 的話),則拋出 generator_done
異常。
完整的 generator
代碼如下:
#include <coroutine>
#include <optional>
#include <iostream>
#include <stdexcept>
#include <cstdlib>
struct generator_done :
std::logic_error
{
private:
typedef std::logic_error super;
public:
using super::super;
};
template <typename T>
struct generator
{
struct promise_type;
std::coroutine_handle<promise_type> handle;
struct promise_type
{
std::optional<T> opt;
std::suspend_always initial_suspend() const
{
return {};
}
std::suspend_never final_suspend() const noexcept
{
return {};
}
void unhandled_exception()
{
std::exit(EXIT_FAILURE);
}
generator get_return_object()
{
return generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
template <typename Arg>
std::suspend_always yield_value(Arg && arg)
{
opt.emplace(std::forward<Arg>(arg));
return {};
}
};
public:
generator() = default;
generator(const generator &) = delete;
private:
generator(std::coroutine_handle<promise_type> handle) :
handle(handle)
{
}
public:
generator(generator && src) :
handle(src.handle)
{
src.handle = nullptr;
}
generator& operator=(const generator &) = delete;
generator& operator=(generator && src)
{
if (!handle) {
handle.destroy();
}
handle = src.handle;
src.handle = nullptr;
}
~generator()
{
if (!handle) {
handle.destroy();
}
}
T & next()
{
handle.resume();
if (handle.done()) {
throw generator_done("generator done");
}
return *(handle.promise().opt);
}
};
generator<int> range(int n)
{
for(int i = 0; i < n; ++i) {
co_yield i;
}
}
int main ()
{
generator<int> gen = range(5);
for (int i = 0; i < 5; ++i) {
std::cout << gen.next() << std::endl;
}
}
4
能雙向傳遞信息的
bigenerator
我們目前完成的 generator
只能做到協(xié)程內(nèi)部向外部 yield 一個(gè)值,傳遞出來(lái)信息。能不能做到外部也向協(xié)程內(nèi)部回復(fù)一個(gè)值,將信息由外部傳遞到協(xié)程內(nèi)部呢?C++ 的協(xié)程機(jī)制也是允許的。
其實(shí),co_yield
表達(dá)式,當(dāng)然,我們上面也知道了, 其實(shí)就是 co_await promise.yield_value()
這個(gè)表達(dá)式,其實(shí)也是有計(jì)算結(jié)果的,只不過(guò),我們之前 generator
中的例子,計(jì)算結(jié)果為 void
類(lèi)型 —— 沒(méi)有返回值罷了。
要想整個(gè)表達(dá)式有返回值,當(dāng)然我們得從 promise.yield_value()
的返回值入手。我們以前用的是 std::suspend_always
,現(xiàn)在得自己配置了。
先上效果:

再上源碼:
#include <coroutine>
#include <variant>
#include <iostream>
#include <stdexcept>
#include <cstdlib>
struct generator_done :
std::logic_error
{
private:
typedef std::logic_error super;
public:
using super::super;
};
template <typename T, typename U>
struct bigenerator
{
struct promise_type;
std::coroutine_handle<promise_type> handle;
struct awaitable : public std::suspend_always
{
std::variant<T, U> * ref;
U & await_resume() const
{
return std::get<U>(*ref);
}
};
struct promise_type
{
std::variant<T, U> var;
std::suspend_always initial_suspend() const
{
return {};
}
std::suspend_never final_suspend() const noexcept
{
return {};
}
void unhandled_exception()
{
std::exit(EXIT_FAILURE);
}
bigenerator get_return_object()
{
return bigenerator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
template <typename Arg>
awaitable yield_value(Arg && arg)
{
var.template emplace<T>(std::forward<Arg>(arg));
return awaitable{.ref = &var};
}
};
public:
bigenerator() = default;
bigenerator(const bigenerator &) = delete;
private:
bigenerator(std::coroutine_handle<promise_type> handle) :
handle(handle)
{
}
public:
bigenerator(bigenerator && src) :
handle(src.handle)
{
src.handle = nullptr;
}
bigenerator& operator=(const bigenerator &) = delete;
bigenerator& operator=(bigenerator && src)
{
if (!handle) {
handle.destroy();
}
handle = src.handle;
src.handle = nullptr;
}
~bigenerator()
{
if (!handle) {
handle.destroy();
}
}
template <typename ... Args>
T & next(Args&& ... args)
{
handle.promise().var.template emplace<U>(std::forward<Args>(args)...);
handle.resume();
if (handle.done()) {
throw generator_done("generator done");
}
return std::get<T>(handle.promise().var);
}
};
bigenerator<int, std::string> range(int n)
{
for(int i = 0; i < n; ++i) {
std::string sunk = co_yield i;
std::cout << sunk << std::endl;
}
}
int main ()
{
bigenerator gen = range(10);
for (int i = 0; i < 5; ++i) {
std::cout << gen.next(i + 1, 'a') << std::endl;
}
}
然后講解:
主要變動(dòng)就是一個(gè)新的內(nèi)部類(lèi):awaitable
,在 bigenerator
中,yield_value
接口返回的就是這個(gè)類(lèi)型。它繼承自 std::suspend_always
,表明我們確實(shí)還是需要每次 yield 時(shí)都要掛起,但是,這里我們重寫(xiě)了 await_resume
方法,使得協(xié)程在恢復(fù)時(shí)調(diào)用這個(gè)方法,從 promise 中取出外界傳遞進(jìn)去的結(jié)果。
struct awaitable : public std::suspend_always
{
std::variant<T, U> * ref;
U & await_resume() const
{
return std::get<U>(*ref);
}
};
下面的代碼片段展示了 yield_value
中怎么構(gòu)造 awaitable
。其實(shí)只要告知 promise 中的 variant
的地址就可以了。bigenerator
中改用了 variant
,主要是考慮到 yield 出去的值和 resume 時(shí)傳遞進(jìn)來(lái)的值不會(huì)在同一時(shí)刻存在,使用 variant
有助于節(jié)省空間。
struct promise_type
{
...
std::variant<T, U> var;
template <typename Arg>
awaitable yield_value(Arg && arg)
{
var.template emplace<T>(std::forward<Arg>(arg));
return awaitable{.ref = &var};
}
};
還有 bigenerator
中 next
的變化,其實(shí)也就是恢復(fù)協(xié)程前,在 promise 的 variant
中構(gòu)造好傳進(jìn)去的對(duì)象就好了。
template <typename T, typename U>
struct bigenerator
{
...
template <typename ... Args>
T & next(Args&& ... args)
{
handle.promise().var.template emplace<U>(std::forward<Args>(args)...);
handle.resume();
if (handle.done()) {
throw generator_done("generator done");
}
return std::get<T>(handle.promise().var);
}
};
當(dāng)然,我們這里沒(méi)有考慮到 bigenerator<T, T>
這種傳出來(lái)和傳進(jìn)去的消息類(lèi)型都一樣的情況,但其實(shí)只要做一下偏特化就可以了。由于不是協(xié)程部分的技術(shù)難點(diǎn),就不再多介紹了。
全文完。