Adapting a function that returns std :: future <T> to std :: future <U>

Suppose I have an asynchronous function map primitive that takes std::vector as input and returns std::future to the Container of my choice as output:

 template<class Container, class T, class Function> std::future<Container> async_map(const std::vector<T>& in, Function f) { return std::async([=] { Container result(in.size()); for(size_t i = 0; i < in.size(); ++i) { result[i] = f(in[i]); } return result; }); } 

I would like to build a similar async_for_each function by adapting async_map :

 template<class T, class Function> std::future<void> async_for_each(const std::vector<T>& in, Function f); 

The problem is that async_for_each returns std::future<void> , and async_map returns std::future<Container> , and void not a Container .

I can get something close to what I want by building a type that satisfies the Container requirements but ignores its purpose ( empty_container in my original attempt), but std::future this type is still not std::future<void> .

I have the following limitations on my decision:

  • Only one implementation of async_map with a given function signature must be implemented (i.e. without async_map<void> )
  • Only one std::future should be created (i.e. not a .then() -strong continuation)

I was hoping there was an effective way to convert between std::future sibling types (or discard std::future<T> to std::future<void> ), but the answer to this question suggests that this is not possible.

Random ideas:

  • Can async_for_each wrap my function in a smart way to solve this problem?
  • Can the type used for Container act like void in async_for_each , but act like Container in async_map ?

My initial attempt is given below. Is it possible to build what I want, given these limitations?

 #include <future> #include <vector> #include <iostream> template<class Container, class T, class Function> std::future<Container> async_map(const std::vector<T>& in, Function f) { return std::async([=] { Container result(in.size()); for(size_t i = 0; i < in.size(); ++i) { result[i] = f(in[i]); } return result; }); } struct empty_container { empty_container(size_t) {} struct empty { template<class T> empty operator=(const T&) const { return empty(); } }; empty operator[](size_t) { return empty(); } }; template<class Function> struct invoke_and_ignore_result { Function f; template<class T> empty_container::empty operator()(T&& x) const { f(std::forward<T>(x)); return empty_container::empty(); } }; template<class T, class Function> //std::future<void> async_for_each(const std::vector<T>& in, Function f) std::future<empty_container> async_for_each(const std::vector<T>& in, Function f) { invoke_and_ignore_result<Function> g{f}; std::future<empty_container> f1 = async_map<empty_container>(in, g); return f1; } int main() { std::vector<int> vec(5, 13); async_for_each(vec, [](int x) { std::cout << x << " "; }).wait(); std::cout << std::endl; return 0; } 
+5
source share
1 answer

I think you are using the wrong primitive.

Here I build everything with the help of another primitive - the sink.

A sink can consume data through operator()(T&&)& . Then it returns some result via operator()()&& .

Here is the async_sink function:

 template<class Container, class Sink> std::future<std::result_of_t<std::decay_t<Sink>()>> async_sink(Container&& c, Sink&& sink) { return std::async( [c=std::forward<Container>(c), sink=std::forward<Sink>(sink)] { for( auto&& x : std::move(c) ) { sink( x ); } return std::move(sink)(); }); } 

Here is a sink implementation that puts things in a container and then returns:

 template<class C> struct container_sink_t { C c; template<class T> void operator()( T&& t ){ c.emplace_back( std::forward<T>(t) ); } C operator()()&&{ return std::move(c); } }; 

Here is a shell that takes a function and a receiver and writes them:

 template<class F, class S> struct compose_sink_t { F f; S s; template<class T> void operator()(T&& t){ s( f(std::forward<T>(t)) ); } std::result_of_t<S()> operator()()&&{ return std::move(s)(); } }; template<class C, class F> compose_sink_t<std::decay_t<F>, container_sink_t<C>> transform_then_container_sink( F&& f ) { return {std::forward<F>(f)}; } 

Here is the receiver that takes the function, calls it and returns void :

 template<class F> struct void_sink_t { F f; template<class T> void operator()(T&& t) { f(std::forward<T>(t)); } void operator()() {} }; template<class F> void_sink_t<std::decay_t<F>> void_sink(F&&f){return {std::forward<F>(f)}; } 

now your card:

 template<class Container, class T, class Function> std::future<Container> async_map(const std::vector<T>& in, Function f) { return async_sink( in, transform_then_container_sink<Container>(std::forward<F>(f)) ); } 

and your for_each:

 template<class T, class Function> std::future<void> async_for_each(const std::vector<T>& in, Function f) { return async_sink( in, void_sink(std::forward<F>(f)) ); } 

I freely use the features of C ++ 14 because they made the code better. You can replace “move to container” with a copy for lower click efficiency and write your own _t aliases.

The above code has not been tested or run, so there are errors in it. There is one problem that I have no doubt about - can lambda return the end of the void with return void_func() in this context? - but since this ugliness is in one place, it can be circumvented even if it does not work.

+6
source