One of the real use of std::apply is tuple unpacking, potentially nested. Here's tested example:
#include <iostream>
#include <string>
#include <tuple>
#include <sstream>
// adapted from here: https://stackoverflow.com/a/48458312
template <typename> 
constexpr bool is_tuple_v = false;
template <typename ...T> 
constexpr bool is_tuple_v<std::tuple<T...>> = true;
template<typename Tval, typename ... T>
void linearize_tuple(std::stringstream &outbuf, const Tval& arg, const T& ... rest) noexcept {
    if constexpr (is_tuple_v<Tval>){
        outbuf << "{ ";
        std::apply([&outbuf](auto const&... packed_values) {
                linearize_tuple(outbuf, packed_values ...);
            }, arg
        );
        outbuf << " }";
    }
    else{
        outbuf << arg;
    }
    if constexpr(sizeof...(rest) > 0){
        outbuf << ' ';
        linearize_tuple(outbuf, rest ...);
    }
}
template<typename ... T>
std::string args_to_string(const T& ... args) noexcept {
    std::stringstream outbuf{};
    if constexpr(sizeof...(args) > 0){
        linearize_tuple(outbuf, args ...);
    }
    return outbuf.str();
}
int main(){
    std::cout << args_to_string(
        "test", 1, "2", 3.0, '0', std::tuple
        {
            "examination", 10, "20", 30.0, '1', std::tuple
            {
                "we need to go deeper", 100, "200", 300, '2'
            }
        }
    );
}
It will print:
test 1 2 3 0 { examination 10 20 30 1 { we need to go deeper 100 200 300 2 } }
and if you look at implementation of std::apply, it's probably using std::invoke like in example from cppreference. Key part:
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
    // This implementation is valid since C++20 (via P1065R2)
    // In C++17, a constexpr counterpart of std::invoke is actually needed here
    return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}