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.