`co_yield` 可以在协程恢复时向调用者返回一个值吗?

7

C++20引入了协程,可以用来创建生成器和其他类似的功能:

generator<int> counter(int max) {
    for(int i = 0; i < max; i++) {
        co_yield i;
    }
}

有没有办法创建一个协程,以便调用者可以提供响应,并在恢复协程时由co_yield返回?我们将其称为通道而不是生成器。

下面是我想要做的示例:

channel<int, int> accumulator(int initial) {
    while(true) {
        // Can channel be written so co_yield provides a response?
        int response = co_yield initial;
        initial += response;
    }
}

在这里,每当调用方恢复协程时,都会提供一个值,然后该值会从co_yield中返回一次协程恢复,就像下面这样:

std::vector<int> accumulate(std::vector<int> values) {
    channel<int, int> acc = accumulator(0);

    std::vector<int> summed_values;

    for(int v : values) {
        // Get whatever value was yielded by the accumulator
        int sum = acc.recieve();
        // Do something with the value
        summed_values.push_back(sum);
        // Resume the accumulator, returning this value from co_yield:
        acc.send(v); 
    }
    return summed_values;
}

基于评论进行编辑

有没有人能够提供一些关于如何实现这个功能的指导或示例呢?对我来说协程仍然很新。我有一个最基本的channel类实现,但我不确定yield_value应该返回什么才能实现这个功能。

在注释中标记了两个问题位置,分别为(A)(B)

template <class Out, class In>
struct channel {
    struct promise_type {
        Out current_value;
        auto yield_value(Out value) {
            current_value = value;
            // (A) What do I return here?
        }
        channel get_return_object() {
            return {std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        // We run up until the first value is ready
        auto initial_suspend() noexcept { return std::suspend_never(); }
        auto final_suspend() noexcept { return std::suspend_always(); }
        void unhandled_exception() noexcept { std::terminate(); }
    };


    Out receive() {
        return handle.promise().current_value;
    }
    void send(In response) {
        // (B) What do I do here?
    }
    // Constructors, destructor and move assignment operator omitted for brevity
   private:
    std::coroutine_handle<promise_type> handle = nullptr;
};

2
是的。co_yield v 的返回值是 co_await p.yield_value(v) 的返回值,其中 p 是协程 promise。因此,调用者可以调用 promise 上的自定义方法(您称之为 send)将值反馈回协程,而 yield_value() 方法返回一个 awaiter,该 awaiter 输出该值。 - Raymond Chen
非常感谢您的回复。您能给我一些实现方面的指导吗?我已经更新了问题,包括我目前的进展!只有两个位置我标记为A和B,我不确定如何填写。 - Alecto Irene Perez
send 可以使用 handle.promise() 访问 promise_type 并将该值放入一个新的 promise 成员变量中。yield_value 然后返回一个产生该值的等待器(awaiter)。 - Raymond Chen
1个回答

8
关键在于await_resume,它被调用在awaiter(yield_value的结果)上,以获得co_yield的结果。
您还需要在某个位置存储响应。正如Raymond Chen在评论中建议的那样,可以将值放入promise_type的新数据成员中。
所以更改内容为:
  1. Add a data member to promise_type.

    In response;
    
  2. Define a customized awaiter to return that data member.

    struct awaiter : std::suspend_always {
        friend promise_type;
        constexpr In await_resume() const { return m_p->response; }
    
    private:
        constexpr awaiter(promise_type* p) : m_p(p) {}
        promise_type* m_p;
    };
    
  3. In (A), return the customized awaiter.

    return awaiter(this);
    
  4. In (B), set the data member, then resume the coroutine.

    handle.promise().response = response;
    handle.resume();
    

1
非常好的回答!谢谢! - Alecto Irene Perez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接