Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 forwarded 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 tuples?

like image 721
KeyC0de Avatar asked Sep 21 '18 18:09

KeyC0de


People also ask

What is the use of std:: invoke?

The std::invoke function in the C++ standard library is usually used to call a functor with parameters.

What is STD invoke?

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.

Why do we need std :: function?

std::function can hold function objects (including lambdas), as well as function pointers with the correct signature. So it is more versatile.


2 Answers

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)
like image 143
Barry Avatar answered Oct 20 '22 00:10

Barry


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 invokeing a function that takes a tuple, and applying 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 tuples 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.

like image 45
Nicol Bolas Avatar answered Oct 19 '22 23:10

Nicol Bolas