October 15th, 2026
intriguing1 reaction

C++ futures part 4: What if I want an immediate on-thread std::async?

I noted last time that you are not allowed to ask std::async for an immediate on-thread calculation. The C++ library designers probably chose not to support it because there’s no point: An immediate on-thread calculation is just a function call, so call the function already and leave me out of it.

But maybe you want to do it because you are interfacing with a component that wants a std::future. How could you do it?

One way would be to implement the immediately synchronous case manually by setting a value into a promise.

template<typename F, typename... Args>
auto
    make_immediate_future(F&& f, Args&&... args)
-> std::future<
        std::invoke_result_t<std::decay_t<F>,
                             std::decay_t<Args>...>>
{
    std::promise<
        std::invoke_result_t<std::decay_t<F>,
                             std::decay_t<Args>...>> p;
    try {
        p.set_value(std::invoke(auto(f), auto(args)...));
    } catch (...) {
        p.set_exception(std::current_exception());
    }
    return p.get_future();
}

Or you could make packaged_task do the work for you:

template<typename F, typename... Args>
auto
    make_immediate_future(F&& f, Args&&... args)
-> std::future<
        std::invoke_result_t<std::decay_t<F>,
                             std::decay_t<Args>...>>
{
    using R = std::invoke_result_t<std::decay_t<F>,
                                   std::decay_t<Args>...>;

    std::packaged_task<R(Args...)> task(f);
    task(args...);
    return task.get_future();
}

Yet another way would be to ask for an on-demand in-thread execution, and then immediately demand it.

template<typename F, typename... Args>
auto
    make_immediate_future(F&& f, Args&&... args)
-> std::future<
        std::invoke_result_t<std::decay_t<F>,
                             std::decay_t<Args>...>>
{
    auto f = std::async(std::launch::deferred,
                        std::forward<F>(f),
                        std::forward<Args>(args)...);
    f.wait();
    return f;
}

Topics

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.

1 comment

Sort by :
  • LB · Edited

    Something I’d like to see is how to initialize an empty promise object, so that you can move shared state into it later. With stop_source there’s a dedicated nostopstate tagged constructor, but promise only has default constructor and move constructor, with the default ctor allocating shared state. Closest I could figure out is to make a sacrificial promise by moving from it, and then creating empty promises by move constructing from the sacrificial one, but given the way moved-from objects are specified I don’t think it’s reliable.