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?
The std::invoke function in the C++ standard library is usually used to call a functor with parameters.
std::invoke( f, args... ) is a slight generalization of typing f(args...) that also handles a few additional cases. Something callable includes a function pointer or reference, a member function pointer, an object with an operator() , or a pointer to member data.
std::function can hold function objects (including lambdas), as well as function pointers with the correct signature. So it is more versatile.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With