简体   繁体   English

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

[英]What is the difference between std::invoke and std::apply?

They are both used as a generic method of calling functions, member functions and generally anything that is callable. 它们都被用作调用函数,成员函数和通常可调用的任何东西的通用方法。 From cppreference the only real difference I see is that in std::invoke the function parameters (however many they are) are forward ed to the function, whereas in std::apply the parameters are passed as a tuple . 从cppreference我看到的唯一真正的区别是,在std::invoke ,函数参数(无论它们是多少)都forward给函数,而在std::apply ,参数作为tuple传递。 Is this really the only difference? 这真的是唯一的区别吗? Why would they create a separate function just to handle tuple s? 为什么他们会创建一个单独的函数来处理tuple

Is this really the only difference? 这真的是唯一的区别吗? Why would they create a separate function just to handle tuples? 为什么他们会创建一个单独的函数来处理元组?

Because you really need both options, since they do different things. 因为你真的需要两个选项,因为他们做了不同的事情。 Consider: 考虑:

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)

Consider especially the difference between invoke(g, tup) , which does not unpack the tuple , and apply(f, tup) , which does. 考虑特别是间差invoke(g, tup)解压tuple ,和apply(f, tup) ,这确实。 You sometimes need both, that needs to be expressed somehow. 你有时需要两者,需要以某种方式表达。


You're right that generally these are very closely related operations. 你是对的, 通常这些是非常密切相关的操作。 Indeed, Matt Calabrese is writing a library named Argot that combines both operations and you differentiate them not by the function you call but rather by how you decorate the arguments: 实际上,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)

You use std::apply because: 你使用std::apply因为:

1: Implementing apply , even if you have access to std::invoke is a big pain. 1:实现apply ,即使你有权访问std::invoke也是一件很痛苦的事。 Turning a tuple into a parameter pack is not a trivial operation. 将元组转换为参数包并非易事。 apply 's implementation would look something like this (from cppref): 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>>>{});
}

Sure, it's not the hardest code in the world to write, but it's not exactly trivial either. 当然,这不是世界上最难编写的代码,但它也不是一件容易的事。 Especially if you don't the index_sequence metaprogramming trick. 特别是如果你没有index_sequence元编程技巧。

2: Because calling a function by unpacking the elements of a tuple is rather useful. 2:因为通过解包tuple的元素来调用函数是非常有用的。 The basic operation it exists to support is the ability to package up a set of arguments, pass that set around, and then call a function with those parameters. 它支持的基本操作是能够打包一组参数,传递该集合,然后使用这些参数调用函数。 We already technically have the ability to do that with a single parameter (by passing the value around), but through apply , you gain the ability to do it with multiple parameters. 我们在技术上已经能够使用单个参数(通过传递值)来执行此操作,但通过apply ,您可以使用多个参数执行此操作。

It also allows you to do metaprogramming tricks, like meta-programmatically doing marshalling between languages. 它还允许您执行元编程技巧,例如以编程方式在语言之间进行编组。 You register a function with such a system, which is given the function's signature (and the function itself). 您可以在这样的系统中注册一个函数,该函数具有函数的签名(以及函数本身)。 That signature is used to marshal the data through metaprogramming. 该签名用于通过元编程来编组数据。

When the other language calls your function, the metaprogram-generated function walks the list of parameter types and extracts value(s) from the other language based on those types. 当另一种语言调用您的函数时,元程序生成的函数遍历参数类型列表,并根据这些类型从另一种语言中提取值。 What does it extract them into? 它将它们提取出来的是什么? Some kind of data structure that holds the values. 某种保存值的数据结构。 And since metaprogramming cannot (easily) build a struct/class , you instead build a tuple (indeed, supporting metaprogramming like this is 80% of why tuple exists). 而且由于元编程不能(轻松地)构建struct/class ,而是构建一个tuple (实际上,支持这样的元编程是为什么tuple存在的80%)。

Once the tuple<Params> is built, you use std::apply to call the function. 一旦构建了tuple<Params> ,就可以使用std::apply来调用该函数。 You can't really do that with invoke . 你不能真的用invoke做到这一点。

3: You don't want to make everyone stick parameters into a tuple just to be able to perform the equivalent of invoke . 3:您不希望每个人都将参数粘贴到tuple ,以便能够执行相当于invoke

4: You need to establish the difference between invoke ing a function that takes a tuple , and apply ing to unpack the tuple . 4:你需要在invoke一个带tuple的函数和apply解包tuple之间建立区别。 After all, if you're writing a template function that performs invoke on parameters the user specifies, it'd be terrible if the user just so happened to provide a single tuple as an argument and your invoke function unpacked it. 毕竟,如果你正在编写一个模板函数来执行用户指定的参数invoke ,那么如果用户恰好提供了一个tuple作为参数并且你的invoke函数解压缩它就会很糟糕。

You could use other means to differentiate the cases, but having different functions is an adequate solution for the simple case. 您可以使用其他方法来区分案例,但具有不同的功能对于简单案例来说是一个合适的解决方案。 If you were writing a more generalized apply -style function, where you want to be able to unpack tuple s in addition to passing other arguments or unpack multiple tuples into the argument lists (or a combination of these), you would want to have a special super_invoke that could handle that. 如果你正在写一个更广义的apply风格的功能,要能够解开tuple除了s到传递其他参数或解开多个元组到参数列表(或它们的组合),你会希望有一个特殊的super_invoke可以处理它。

But invoke is a simple function for simple needs. 但是invoke是一个简单需要的简单函数。 The same goes for apply . apply同样apply

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM