是否可以将协程与``头文件中的模板结合使用?

huangapple go评论60阅读模式
英文:

Is it possible to combine coroutines and templates from `<algorithm>` header?

问题

我编写了许多基于TCP/IP的C++软件,并使用现代C++协程进行网络通信。现在让我们假设我有一个URL数组,并且我想找到哪个URL下载的文档包含“Hello”字符串:

vector<string> my_urls = { /* 在这里列出URL列表 */ };
auto hello_iterator = find_if(my_urls.begin(), my_urls.end(), [](const string &url) 
{ 
    string downloaded_data = download(url);
    return downloaded_data.find("Hello") != string::npos;
});

在这里,我们使用同步的download(const std::string& url)函数来下载每个URL的数据。

使用协程,我想做类似的事情:

vector<string> my_urls = { /* 在这里列出URL列表 */ };
auto hello_iterator = find_if(my_urls.begin(), my_urls.end(), [](const string &url) -> MyPromiseClass
{ 
    string downloaded_data = co_await async_download(url);
    return downloaded_data.find("Hello") != string::npos;
});

我有一个MyPromiseClass async_download(const std::string& url),它运行良好,我想使用它来异步下载数据。

但这样的代码无法编译。在Visual C++中,我遇到以下错误:

错误 C2451:类型为 'MyPromiseClass' 的条件表达式无效

原因是标准的find_if算法不了解协程,只是尝试将MyPromiseClass转换为bool

不过,我可以很容易地实现协程版本的find_if,或者任何其他标准算法,只需将其if语句更改为在调用谓词时使用co_await并返回承诺(promise)而不是迭代器,因此我希望C++标准库中也包含类似的算法?

请建议是否有C++标准库或Boost中支持协程的<algorithm>头文件的版本,或者是否有一种简单的方法可以轻松将<algorithm>头文件中的“旧”算法转换为支持协程,而无需手动重写它们或使用不雅的代码,首先使用协程预计算值,然后在Lambda表达式中等待数据而不是在这些预计算的值上使用算法。

英文:

I write a lot of TCP/IP based C++ software, and I use modern C++ coroutines for network communications. Now let suppose I have array of URLs, and I want to find which URL downloads document that contains "Hello" string:

vector&lt;string&gt; my_urls = { /* URLs list here */ };
auto hello_iterator = find_if(my_urls.begin(), my_urls.end(), [](const string &amp;url) 
{ 
    string downloaded_data = download(url);
    return downloaded_data.find(&quot;Hello&quot;) != string::npos;
});

Here we use synchronous download(const std::string&amp; url) function to download data for each URL.

With coroutines I want to do something similar:

vector&lt;string&gt; my_urls = { /* URLs list here */ };
auto hello_iterator = find_if(my_urls.begin(), my_urls.end(), [](const string &amp;url) -&gt; MyPromiseClass
{ 
    string downloaded_data = co_await async_download(url);
    return downloaded_data.find(&quot;Hello&quot;) != string::npos;
});

I have MyPromiseClass async_download(const std::string&amp; url) that works nice, and I want to use it to download data asynchronously.

But such code doesn't compile. In Visual C++ I have following error:

> error C2451: a conditional expression of type 'MyPromiseClass' is not
> valid

The reason is that standard find_if algorithm "doesn't know" about coroutines and simply tries to convert MyPromiseClass to bool.

I however can easily implement coroutine version of find_if and/or any other standard algorithm that will work by just changing its if statement to one that uses co_await when calls predicate and returns promise instead of iterator, so I hope that C++ standard should also contain similar algorithms?

Please advise are there any version of &lt;algorithm&gt; header in C++ standard or boost that supports coroutines, or are there any way to easily convert "old" algorithms from &lt;aglorithm&gt; header to support coroutines without manual rewriting them or ugly code that first precalculates values (with coroutines) and later uses algorithms on these precalculated values instead of just awaiting data in lambda expression?

答案1

得分: 3

只有协程可以调用co_await,但标准算法不是协程(人们也可以争论它们不应该是协程)。这意味着你不能将协程传递给标准算法并期望它等待其结果。

如果标准算法是协程,你不能简单地调用它们并获取它们的结果 - 相反,它们会返回未来值或协程类型,你必须在继续之前等待它们,类似于你的async_download函数不直接返回std::string,而是返回某种自定义的未来。因此,标准算法在除协程之外的任何地方使用将会非常困难。这是因为传递给标准算法的任何协程都可能自动暂停,这反过来意味着算法本身必须能够自动暂停,使其成为协程。

注意,协程的“暂停”意味着协程将其状态保存到协程框架,然后返回。如果你需要这个工作跨越调用栈的多个级别,那么调用栈中的每个函数都必须知道这个并且能够在沿着决定暂停的协程的某个地方返回时提前返回。协程可以通过co_await轻松实现这一点,你也可以手动编写代码来实现这一点,比如返回一个未来。

由于标准算法返回普通值而不是未来值,它们不能提前返回,因此不支持暂停。因此,它们不能调用协程。

相反,你可以先下载数据,然后搜索字符串:

std::vector<std::string> urls = ...;
std::vector<MyPromiseClass> downloads;

// 并行开始下载所有内容
std::transform(urls.begin(), urls.end(),
               std::back_insert_iterator(downloads), async_download);

std::vector<std::string> data;

// 等待所有下载完成
for (auto& promise: downloads) {
    data.push_back(co_await promise);
}

auto hello_iterator = std::find_if(data.begin(), data.end(), ...);

如果你愿意,你可以创建一个帮助函数(模板化协程),它co_await多个可等待对象并返回它们的结果。

英文:

Only coroutines can call co_await, but the standard algorithms aren't coroutines (and one can argue that they shouldn't be). This means that you can't pass a coroutine into a standard algorithm and expect it to wait for its result.

If the standard algorithms were coroutines, you couldn't just call them and get their result - instead, they'd all return futures or coroutine types that you'd have to wait on before proceeding, similar to how your async_download function doesn't return a std::string directly, but rather some kind of custom future. As a result, the standard algorithms would be really difficult to use in anything but a coroutine. This would be necessary because any coroutines passed into a standard algorithm could suspend themselves, which in turn means that the algorithm itself would have to be able to suspend itself, making it a coroutine.

Note that a coroutine "suspending" means that the coroutine saves its state to its coroutine frame and then returns. If you need this to work across multiple levels of the call stack, every function in that part of the call stack has to be in on the joke and be able to return early when a coroutine somewhere down the line decides to suspend. Coroutines can trivially do this via co_await, and you can also write code that does this manually by i.e. returning a future.

Since the standard algorithms return plain values, and not futures, they can't return early and therefore don't support suspension. As a result, they can't call coroutines.

What you could do instead is to download the data first, and then search for the string:

std::vector&lt;std::string&gt; urls = ...;
std::vector&lt;MyPromiseClass&gt; downloads;

//Start downloading everything in parallel
std::transform(urls.begin(), urls.end(),
               std::back_insert_iterator(downloads), async_download);

std::vector&lt;std::string&gt; data;

//Wait for all downloads
for (auto&amp; promise: downloads) {
    data.push_back(co_await promise);
}

auto hello_iterator = std::find_if(data.begin(), data.end(), ...);

If you wanted to, you could create a helper function (templated coroutine) that co_awaits multiple awaitable objects and returns their results.

答案2

得分: 0

直接从嵌套函数调用中挂起是所谓的“有栈协程”的特性,而C++20协程是“无栈”的(意味着只能在函数体内挂起)。

Boost.Coroutine2 提供有栈协程,可能还有其他库。

英文:

Suspending directly from a nested function call is a property of so-called "stackful coroutines", while the C++20 coroutines are "stackless" (meaning, you can only suspend in the function body).

Boost.Coroutine2 provides stackful coroutines, and there might be other libraries too.

huangapple
  • 本文由 发表于 2023年2月8日 23:03:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/75387686.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定