【问题标题】:What is the difference between std::invoke and std::apply?std::invoke 和 std::apply 有什么区别?
【发布时间】:2019-02-26 04:25:39
【问题描述】:

它们都被用作调用函数、成员函数以及任何可调用的一般方法的通用方法。从 cppreference 我看到的唯一真正区别是,在 std::invoke 中,函数参数(无论它们有多少)是 forwarded 到函数,而在 std::apply 中,参数作为 tuple 传递。这真的是唯一的区别吗?他们为什么要创建一个单独的函数来处理tuples?

【问题讨论】:

  • 考虑到the possible implementationstd::apply 使用std::invoke,这似乎确实是唯一的区别。
  • 我自己没有使用过它们,但我想在模板中,将参数捆绑到某个元组 typename ArgsTupleT 中会很方便,而不是采用 typename... ArgsT 的可变参数方式并且必须使用模式扩展
  • 可能apply的实现者是标准ml的忠实拥护者,哈哈

标签: c++ function-pointers c++17 callable


【解决方案1】:

这真的是唯一的区别吗?他们为什么要创建一个单独的函数来处理元组?

因为你真的需要这两种选择,因为它们做不同的事情。考虑:

int f(int, int);
int g(tuple<int, int>);

tuple<int, int> tup(1, 2);

invoke(f, 1, 2); // calls f(1, 2)
invoke(g, tup);  // calls g(tup)
apply(f, tup);   // also calls f(1, 2)

特别考虑invoke(g, tup)apply(f, tup) 之间的区别,invoke(g, tup) 可以解压tuple,后者可以。你有时需要两者,这需要以某种方式表达。


你说得对,通常这些是非常密切相关的操作。事实上,Matt Calabrese 正在编写一个名为 Argot 的库,它结合了这两种操作,您可以通过调用的函数而不是如何装饰参数来区分它们:

call(f, 1, 2);         // f(1,2)
call(g, tup);          // g(tup)
call(f, unpack(tup));  // f(1, 2), similar to python's f(*tup)

【讨论】:

    【解决方案2】:

    你使用std::apply是因为:

    1:实现apply,即使您可以访问std::invoke 也是一个很大的痛苦。将元组转换为参数包并非易事。 apply 的实现看起来像这样(来自 cppref):

    namespace detail {
    template <class F, class Tuple, std::size_t... I>
    constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
    {
        return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
    }
    }  // namespace detail
    
    template <class F, class Tuple>
    constexpr decltype(auto) apply(F&& f, Tuple&& t)
    {
        return detail::apply_impl(
            std::forward<F>(f), std::forward<Tuple>(t),
            std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
    }
    

    当然,这不是世界上最难编写的代码,但也不是微不足道的。特别是如果您不使用 index_sequence 元编程技巧。

    2:因为通过解包tuple 的元素来调用函数非常有用。它支持的基本操作是能够打包一组参数,传递该组,然后使用这些参数调用一个函数。从技术上讲,我们已经有能力使用单个参数(通过传递值)来做到这一点,但是通过apply,您可以使用多个参数来做到这一点。

    它还允许您使用元编程技巧,例如以元编程方式在语言之间进行编组。您在这样的系统中注册了一个函数,该系统给出了函数的签名(以及函数本身)。该签名用于通过元编程来编组数据。

    当其他语言调用您的函数时,元程序生成的函数会遍历参数类型列表并根据这些类型从其他语言中提取值。它将它们提取成什么?某种保存值的数据结构。而且由于元编程不能(轻松)构建struct/class,因此您可以构建tuple(事实上,支持这样的元编程是tuple 存在的80%)。

    一旦构建了tuple&lt;Params&gt;,您就可以使用std::apply 调用该函数。 invoke 无法做到这一点。

    3:您不想让每个人都将参数粘贴到 tuple 中,只是为了能够执行 invoke 的等效操作。

    4:您需要确定invokeing 一个采用tuple 的函数与applying 解包tuple 之间的区别。毕竟,如果您正在编写一个模板函数,该函数对用户指定的参数执行invoke,那么如果用户碰巧提供了一个tuple 作为参数并且您的invoke 函数已解压缩,那就太糟糕了它。

    您可以使用其他方式来区分这些情况,但对于简单的情况,具有不同的功能是足够的解决方案。如果您正在编写一个更通用的apply 样式函数,除了传递其他参数或将多个元组解压缩到参数列表(或这些的组合)之外,您还希望能够解压缩tuples,您将希望有一个特殊的super_invoke 可以处理这个问题。

    但是invoke 是一个简单的函数,可以满足简单的需求。 apply 也是如此。

    【讨论】:

      猜你喜欢
      • 2017-01-16
      • 2014-07-22
      • 2023-04-09
      • 2020-10-16
      • 2020-09-30
      • 2021-10-16
      • 2015-07-18
      • 2013-02-07
      相关资源
      最近更新 更多