Suppose I have an asynchronous functional map primitive which takes a std::vector as input and returns a std::future to a 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'd like to build an analogous 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>, while async_map returns std::future<Container>, and void is not a Container.
I can get something close to what I want by constructing a type which fulfills the Container requirements but ignores assignments to it (empty_container in my initial attempt), but a std::future of this type is still not std::future<void>.
I have the following constraints on my solution:
async_map, with the given function signature (i.e., no async_map<void> specialization)std::future created (i.e., no .then()-style continuation)I was hoping there is an efficient way to convert between std::futures of related types (or cast a std::future<T> to std::future<void>), but the answer to this question suggests it is not possible.
Random ideas:
async_for_each wrap its function in a clever way to solve this problem?Container act like void in async_for_each, but act like Container in async_map?My initial attempt is below. Is it possible to build what I want given these constraints?
#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;
}
I think you are using the wrong primitive.
Here I build everything up with a different primitive -- a sink.
A sink can consume data via operator()(T&&)&. It then returns some result via operator()()&&.
Here is a 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 an implementation of a sink that puts things into a container, then returns it:
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 sink that takes a function and a sink and composes 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 a sink that takes a 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 map is:
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 is:
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 C++14 features, because they made the code better. You can replace the move-into-container with a copy for a touch less efficiency, and write your own _t aliases.
The above code has not been tested or run, so there are probably bugs in it. There is one issue I'm uncertain of -- can a lambda returning void end with a return void_func() in that context? -- but as that uglyness is in one spot, it can be worked around even if it doesn't work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With