你使用std::apply是因为:
1:实现apply,即使您可以访问std::invoke 也是一个很大的痛苦。将元组转换为参数包并非易事。 apply 的实现看起来像这样(来自 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>>>{});
}
当然,这不是世界上最难编写的代码,但也不是微不足道的。特别是如果您不使用 index_sequence 元编程技巧。
2:因为通过解包tuple 的元素来调用函数非常有用。它支持的基本操作是能够打包一组参数,传递该组,然后使用这些参数调用一个函数。从技术上讲,我们已经有能力使用单个参数(通过传递值)来做到这一点,但是通过apply,您可以使用多个参数来做到这一点。
它还允许您使用元编程技巧,例如以元编程方式在语言之间进行编组。您在这样的系统中注册了一个函数,该系统给出了函数的签名(以及函数本身)。该签名用于通过元编程来编组数据。
当其他语言调用您的函数时,元程序生成的函数会遍历参数类型列表并根据这些类型从其他语言中提取值。它将它们提取成什么?某种保存值的数据结构。而且由于元编程不能(轻松)构建struct/class,因此您可以构建tuple(事实上,支持这样的元编程是tuple 存在的80%)。
一旦构建了tuple<Params>,您就可以使用std::apply 调用该函数。 invoke 无法做到这一点。
3:您不想让每个人都将参数粘贴到 tuple 中,只是为了能够执行 invoke 的等效操作。
4:您需要确定invokeing 一个采用tuple 的函数与applying 解包tuple 之间的区别。毕竟,如果您正在编写一个模板函数,该函数对用户指定的参数执行invoke,那么如果用户碰巧提供了一个tuple 作为参数并且您的invoke 函数已解压缩,那就太糟糕了它。
您可以使用其他方式来区分这些情况,但对于简单的情况,具有不同的功能是足够的解决方案。如果您正在编写一个更通用的apply 样式函数,除了传递其他参数或将多个元组解压缩到参数列表(或这些的组合)之外,您还希望能够解压缩tuples,您将希望有一个特殊的super_invoke 可以处理这个问题。
但是invoke 是一个简单的函数,可以满足简单的需求。 apply 也是如此。