mercredi 13 mai 2015

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:

  • There must be only one implementation of async_map, with the given function signature (i.e., no async_map<void> specialization)
  • There must be only one 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:

  • Can async_for_each wrap its function in a clever 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 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;
}

Aucun commentaire:

Enregistrer un commentaire