std :: invoke和std :: apply之间有什么区别?

时间:2018-09-21 18:17:12

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

它们都用作调用函数,成员函数以及一般可调用对象的通用方法。从cppreference中,我看到的唯一真正的区别是,在std::invoke中,函数参数(无论数量多少)被forward赋给了函数,而在std::apply中,参数作为{ {1}}。这真的是唯一的区别吗?为什么他们会创建一个单独的函数来处理tuple

2 个答案:

答案 0 :(得分:11)

  

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

因为您确实需要这两种选择,因为它们会做不同的事情。考虑:

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)tuple之间的区别,apply(f, tup)不会解包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) 。有时您需要两者,这需要以某种方式表达。


您是正确的,通常 这些都是非常紧密相关的操作。确实,Matt Calabrese正在编写一个名为Argot的库,该库结合了这两种操作,您不是通过调用的函数而是通过修饰参数的方式来区分它们:

.container {
  height: 100vh;
  display: grid;
  grid-template-areas: "list content";
  grid-template-columns: 150px 1fr;
  grid-template-rows: auto;
  color: white;
}
.container .list {
  overflow: auto;
  grid-area: list;
  background-color: #131418;
  padding: 20px;
}
.container .list .items {
  margin-top: 15px;
  display: block;
}
.container .content {
  grid-area: content;
  background-color: #15161b;
  padding: 15px;
  position: sticky;
}

答案 1 :(得分:4)

您使用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<Params>,就可以使用std::apply来调用该函数。您invoke真的无法做到这一点。

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

4:您需要确定invoke占用tuple的函数与apply释放tuple的包之间的区别。毕竟,如果您要编写一个对用户指定的参数执行invoke的模板函数,那么如果用户恰巧提供了一个tuple作为参数,而您的{ {1}}函数将其解压缩。

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

但是super_invoke是满足简单需求的简单函数。 invoke也是如此。