将返回std::future<T>的函数适配成返回std::future<U>

5

假设我有一个异步函数映射原语,它以 std::vector 作为输入,并返回一个 std::future 到我选择的 Container 作为输出:

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;
  });
} 

我想通过改编async_map函数来构建类似的async_for_each函数:
template<class T, class Function>
std::future<void> async_for_each(const std::vector<T>& in, Function f);

问题在于async_for_each返回std::future<void>,而async_map返回std::future<Container>,而void不是一个Container
通过构造满足Container要求但忽略对其赋值的类型(即我最初尝试的empty_container),我可以获得接近我想要的内容,但是这种类型的std::future仍然不是std::future<void>
我对解决方案有以下限制:
  • 只能有一个async_map的实现,具有给定的函数签名(即没有async_map<void>专业化)
  • 只能创建一个std::future(即没有类似.then()的续集)
我希望有一种有效的方法可以在相关类型的std::future之间进行转换(或将std::future<T>强制转换为std::future<void>),但此问题的答案表明这是不可能的。
随机想法:
  • 可以async_for_each以巧妙的方式包装其函数以解决此问题吗?
  • 用于Container的类型是否可以在async_for_each中像void一样起作用,但在async_map中则像Container一样起作用?
我的初始尝试如下。在这些限制条件下,有可能构建我想要的吗?
#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;
}
1个回答

6

我认为您正在使用错误的原语。

这里我用另一种原语来构建所有内容——一个 sink。

一个 sink 可以通过 operator()(T&&)& 消费数据。 然后通过 operator()()&& 返回一些结果。

下面是一个 async_sink 函数:

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)();
  });
} 

这里是一个将物品放入容器并返回的sink实现代码示例:
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);
  }
};

这里有一个接受函数和汇聚的接口的汇流方式:
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)};
}

这里有一个接受函数并调用它并返回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)}; }

现在您的地图是:

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))
  );
}

你的 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))
  );
}

我自由地使用C++14特性,因为这使得代码更好。你可以将move-into-container替换为copy以获得更高的效率,并编写自己的_t别名。

上面的代码尚未经过测试或运行,所以可能存在错误。有一个问题我不确定--在该上下文中返回void_func()的lambda是否以void结尾?--但是由于这个丑陋的东西只出现在一个地方,即使它不起作用,也可以解决。


“返回 void 的 lambda 表达式可以以 ... 结束吗?” 可以。请参见我的答案中的第一个示例。 - Barry
谢谢您的建议! - Jared Hoberock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接