August 23rd, 2023

On writing loops in PPL and continuation-passing style, part 2

Last time, we came up with task-based while loop that involved creating a custom callable that passed copies of itself to the next iteration.

This time, we’ll implement the function in terms of a more traditional recursion.

template<typename Callable>
task<void> do_while_task(
    std::shared_ptr<Callable> const& f)
{
    return (*f)().then([f](bool loop) {
        return loop ? do_while_task(f) :
                      task_from_result();
    });
}

template<typename Callable, typename =
    std::enable_if_t<std::is_invocable_v<Callable>>>
task<void> do_while_task(Callable&& callable)
{
    using Decayed = std::decay_t<Callable>;
    return do_while_task(
        std::make_shared<Decayed>(
            std::forward<Callable>(callable)));
}

The real work happens in the first overload, which takes a ready-made shared_ptr. The second overload is a convenience method that lets you pass a callable, and it will wrap it in a shared_ptr for you.

However, if you think about it, in PPL-style programming, the lambda callable itself usually holds a pointer to shared state, so that the various task fragments can share information with each other. Let’s look again at my original example.

do_while_task([i = 0, widgets]() mutable
{
    if (i >= 3) return task_from_result(false);
    return create_widget().then([index = i++, widgets](auto widget)
    {
        widgets[index] = widget;
        return true;
    });
}).then([] {
    printf("Done!\n");
});

I cheated here and incremented the i variable inside the first lambda, but capturing the unincremented value as index for the second lambda. More realistically, the two lambdas need to share state.

struct lambda_state                                  
{                                                    
    lambda_state(Widgets* w) : widgets(w) {}         
    Widgets* widgets;                                
    int i = 0;                                       
};                                                   
                                                     
auto state = std::make_shared<lambda_state>(widgets);

do_while_task([state]()
{
    if (state->i >= 3) return task_from_result(false);
    return create_widget().then([state](auto widget)
    {
        state->widgets[state->i] = widget;
        state->i++;
        return true;
    }
}).then([] {
    printf("Done!\n");
});

This means that our do_while_task creates a shared_ptr that itself holds another shared_ptr, which seems kind of silly.

We’ll address this next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.