【问题标题】:Is there a name for this convenience wrapper around std::future::then?是否有围绕 std::future::then 的便利包装器的名称?
【发布时间】:2023-12-13 12:00:02
【问题描述】:

jQuery version of "Deferreds"(C++ 称之为“期货”)中,.then() 方法将其签名不包含任何期货的函数作为其参数。而为 C++17 (see N3721) 提议的 .then() 采用了一个在其签名中带有 future 的函数。

即,如果我想“在后台”计算f(g(x)),N3721 想让我写

extern int f(int);
extern int g(int);

auto async_fg(int x) {
    return std::async(f, x).then([](std::future<int> mid) {
        return g(mid.get());
    });
}

我很想写一个包装器,像这样:

template<class F>
auto futurize(F&& f) {
    return [](auto mid) {
        return std::forward<F>(f)(mid.get());
    };
}

auto async_fg(int x) {
    return std::async(f, x).then(futurize(g));
}

不过,这两种解决方案似乎都很尴尬;最重要的是,我不知道我在这里称之为“未来化”的操作的正确名称。如果以前有人提出过这种操作,人们叫它什么?


如果future&lt;T&gt; 有一个具有语义的新成员函数,显然会更好的接口

template<class T, class F>
auto future<T>::then_futurize(F&& f)
{
    return this->then(futurize(std::forward<F>(f)));
}

auto async_fg(int x) {
    return std::async(f, x).then_futurize(g);
}

this 结构是否有任何(现有)名称? (我只是编了个名字then_futurize,我不喜欢。)


我将接受来自任何现有语言或库(Javascript、Python、Ruby、C++ Boost、C++ Folly...)的答案;它们不必是专门的 C++ 标准提案,但显然那是最好的。

我已经注意到 Folly's version of .then() 接受一个带有签名的函数 either X(future&lt;Y&gt;) or X(Y);但我想这在某些极端情况下最终会变得模棱两可。此外,未来的.then() 和“便利包装”.then() 似乎是完全不同的操作,我更喜欢为它们取不同的名称。

【问题讨论】:

  • 我会从单子的角度来考虑这个问题。但首先我必须学习 Haskell 和 monads。我相信 .then 是一种 monad 操作,而 unpack-and-call-.then 是另一种 haskell monad 操作,也许&gt;&gt;=?哪个有英文名字,我想bind?与std::bind 冲突。

标签: c++ future naming


【解决方案1】:

啊哈! N3865 与我称为 .then_futurize() 的原语相同; N3865 称之为.next()

auto async_fg(int x) {
    return std::async(f, x).next(g);
}

N3865 还提供了对应的 .recover(),允许 Javascript 样式的延续构造:

std::future<std::string> f2 = f1.next(
    [](int v) {
        return v.to_string();
    }
).recover(
    [](exception_ptr ex) {
        return "nan";
    }
);

它还提供了许多其他很酷的便捷包装器,例如.has_value().value_or()

(我没有看到我的futurize() 的任何名称,但.then_futurize() 肯定被命名为.next()。)

【讨论】:

  • 我正要问nextthen 之间的区别是什么。很难在线查找...您解释了比较代码示例。
【解决方案2】:

do_next怎么样?

template<class F>
auto do_next( F&& f )
{
  return [f = std::forward<F>(f)]( auto&& fut ) {
    return f( decltype(fut)(fut).get() );
  };
}

auto async_fg(int x) {
  return std::async(f, x).then(do_next(g));
}

如上所述,将future 视为Haskell monad,那么我认为then(do_next&gt;&gt;= 又名bind,但这不切实际。

您还可以从命名运营商那里获取页面:

namespace named_operator {
  template<class D>struct make_operator{make_operator(){}};

  template<class T, char, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
      return {std::forward<Lhs>(lhs)};
  }

  template<class Lhs, class Op, class Rhs>
  auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
  -> decltype( invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
  {
      return invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

namespace my_operator {
  struct do_next_t{};
  static const named_operator<do_next_t> do_next;

  template<class T, class F,
    class R = std::result_of_t<std::decay_t<F>(T)>
  >
  auto invoke( std::future<T>&& lhs, do_next_t, F&& f )
  -> std::future< R >
  {
    return lhs.then( [f = std::forward<F>(f)](std::future<T> fut)mutable->R {
      return std::move(f)(std::forward<F>(fut.get()));
    });
  };
}
using my_operator::do_next;

给你:

auto async_fg(int x) {
  return std::async(f, x) *do_next* g;
}

或其他名称,例如*then_do*

live example(调用 .then 失败)。

...

解码 Haskell,我认为 .then 在函子上是 fmap.then(futurize 在单子上是 &gt;&gt;=bind,这意味着 futurize 在函子上是 lift

&gt;&gt;=bind 通常被认为比fmap/lift 更易于使用;通常将两者组合成一个概念。

【讨论】:

  • 元编程实现很聪明,但无关紧要。 ;) do_next 的建议与futurize 一样有意义...但它背后有任何现有技术吗?世界上现在有没有人打电话给这个东西do_next?我正在专门寻找现有技术。 (我会尝试阅读 Haskell 和 &gt;&gt;= / bind,尽管我自己的 Haskell 技能为零,并且您已经指出了与 std::bind 可能存在的混淆。)
  • @Quuxplusone 啊哈! futurizefuture 的函子上的 Haskell lift -- thenfmap methinks!