std::async variant which works over a collection

2019-07-04 02:09发布

问题:

Using std::async I was wondering whether it is possible to have a helper function, which creates std::futures from a collection (one future for every collection element).

Often I have the following situation:

auto func = []( decltype(collection)::reference value ) {
  //Some async work
};

typedef std::result_of<decltype(func)>::type ResultType;
std::vector<std::future<ResultType>> futures;
futures.reserve(collection.size());

// Create all futures
for( auto& element : collection ) {
  futures.push_back(std::async(func, element));
}

// Wait till futures are done
for( auto& future : futures ) {
  future.wait();
}

To be able to easily reuse this I came up with the following partial code:

template< class Function, class CT, class... Args>
std::vector<std::future<typename std::result_of<Function(Args...)>::type>>
async_all( Function&& f, CT& col ) {

    typedef typename std::result_of<Function(Args...)>::type ResultType;
    std::vector<std::future<ResultType>> futures;
    futures.reserve(collection.size());

    for( auto& element : collection ) {
        futures.push_back(std::async(func, element));
    }
}
return futures;

Now I have to solve the Args problem, since in async_all, Args cannot be deduced anymore. The only thing I currently can think of is another functor, which converts an element in collection to Args. Is there any more elegant solution to this?

回答1:

You're almost there. The collection passed to async_all has all the information we need to uniquely determine the function argument type; the only question is how to extract this information. Using the auto keyword in the function signature, we can write the return type after the function arguments. This not only produces a cleaner signature, but also lets us use the argument values themselves together with decltype in deducing return types. For instance:

template<typename F, typename CT>
auto reduce(F f, CT coll) -> decltype(f(*begin(coll), *begin(coll));

Of course, there are other ways to determine argument types for provided functions (using function signature deduction with templates). However, these approaches can fail for cases involving overloaded functions and/or templated function objects.

The following code compiles and runs properly (prints "x=1" 10 times) under gcc 4.8 (earlier versions should work just fine). Notice how we don't even have to explicitly mention std::future: we can use decltype directly on the std::async statement to deduce it's type.

#include <future>
#include <vector>
#include <iostream>

template<class Function, class CT>
auto async_all(Function f, CT col)
    -> std::vector<decltype(std::async(f, *std::begin(col)))>
{
    std::vector<decltype(std::async(f, *std::begin(col)))> futures;
    futures.reserve(col.size());

    for (auto& element : col) {
        futures.push_back(std::async(f, element));
    }
    return futures;
}

int main()
{
    using namespace std;
    for (auto& f : async_all([](int x) { cout << "x = " << x << endl; }, 
                             vector<int>(10, 1)))
       f.get();
}

(async_all is evaluated just once here, as the spec guarantees for range expressions in range-based for loops)