简体   繁体   中英

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 . Is this really the only difference? Why would they create a separate function just to handle tuple s?

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. 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:

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:

1: Implementing apply , even if you have access to std::invoke is a big pain. Turning a tuple into a parameter pack is not a trivial operation. apply 's implementation would look something like this (from 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.

2: Because calling a function by unpacking the elements of a tuple is rather useful. 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.

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).

Once the tuple<Params> is built, you use std::apply to call the function. You can't really do that with invoke .

3: You don't want to make everyone stick parameters into a tuple just to be able to perform the equivalent of invoke .

4: You need to establish the difference between invoke ing a function that takes a tuple , and apply ing to unpack the 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.

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.

But invoke is a simple function for simple needs. The same goes for apply .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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