std::invoke 和 std::apply 有什么区别?
What is the difference between std::invoke and std::apply?
它们都被用作调用函数、成员函数和通常任何可调用的东西的通用方法。从 cppreference 我看到的唯一真正的区别是,在 std::invoke
中,函数参数(无论它们有多少)被 forward
ed 到函数,而在 std::apply
中,参数作为 tuple
。这真的是唯一的区别吗?他们为什么要创建一个单独的函数来处理 tuple
s?
Is this really the only difference? Why would they create a separate function just to handle tuples?
因为您确实需要这两种选择,因为它们做不同的事情。考虑:
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)
请特别考虑invoke(g, tup)
和apply(f, tup)
之间的区别,前者 不 解压tuple
,后者解压apply(f, tup)
。有时你需要两者,需要以某种方式表达。
你是对的,通常这些是非常密切相关的操作。实际上,Matt Calabrese 正在编写一个名为 Argot 的库,它结合了这两种操作,您不是通过调用的函数来区分它们,而是通过修饰参数的方式来区分它们:
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)
您使用 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:因为通过解包 a tuple
的元素来调用函数是相当有用的。它支持的基本操作是能够打包一组参数,传递该组参数,然后使用这些参数调用函数。从技术上讲,我们已经能够使用单个参数(通过传递值)来做到这一点,但是通过 apply
,您可以获得使用多个参数来做到这一点的能力。
它还允许您执行元编程技巧,例如以元编程方式在语言之间进行编组。您向这样的系统注册一个函数,该系统被赋予函数的签名(以及函数本身)。该签名用于通过元编程编组数据。
当其他语言调用您的函数时,元程序生成的函数遍历参数类型列表并根据这些类型从其他语言中提取值。它把它们提取成什么?某种保存值的数据结构。由于元编程无法(轻松)构建 struct/class
,因此您需要构建 tuple
(实际上,支持这样的元编程是 tuple
存在的 80%)。
一旦 tuple<Params>
构建完成,您就可以使用 std::apply
来调用该函数。你不能用 invoke
.
真正做到这一点
3:您不想让每个人都将参数粘贴到 tuple
中只是为了能够执行相当于 invoke
.
4:您需要确定 invoke
接受 tuple
的函数与 apply
解压 tuple
之间的区别。毕竟,如果您正在编写一个对用户指定的参数执行 invoke
的模板函数,那么如果用户恰好提供一个 tuple
作为参数并且您的 invoke
函数解压它。
您可以使用其他方式来区分案例,但具有不同的功能对于简单的案例来说是一个足够的解决方案。如果您正在编写一个更通用的 apply
风格的函数,除了传递其他参数或将多个元组解压缩到参数列表(或这些的组合)之外,您还希望能够解压缩 tuple
s ), 你会想要一个特殊的 super_invoke
来处理这个问题。
但是invoke
是满足简单需求的简单函数。 apply
.
也是如此
它们都被用作调用函数、成员函数和通常任何可调用的东西的通用方法。从 cppreference 我看到的唯一真正的区别是,在 std::invoke
中,函数参数(无论它们有多少)被 forward
ed 到函数,而在 std::apply
中,参数作为 tuple
。这真的是唯一的区别吗?他们为什么要创建一个单独的函数来处理 tuple
s?
Is this really the only difference? Why would they create a separate function just to handle tuples?
因为您确实需要这两种选择,因为它们做不同的事情。考虑:
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)
请特别考虑invoke(g, tup)
和apply(f, tup)
之间的区别,前者 不 解压tuple
,后者解压apply(f, tup)
。有时你需要两者,需要以某种方式表达。
你是对的,通常这些是非常密切相关的操作。实际上,Matt Calabrese 正在编写一个名为 Argot 的库,它结合了这两种操作,您不是通过调用的函数来区分它们,而是通过修饰参数的方式来区分它们:
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)
您使用 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:因为通过解包 a tuple
的元素来调用函数是相当有用的。它支持的基本操作是能够打包一组参数,传递该组参数,然后使用这些参数调用函数。从技术上讲,我们已经能够使用单个参数(通过传递值)来做到这一点,但是通过 apply
,您可以获得使用多个参数来做到这一点的能力。
它还允许您执行元编程技巧,例如以元编程方式在语言之间进行编组。您向这样的系统注册一个函数,该系统被赋予函数的签名(以及函数本身)。该签名用于通过元编程编组数据。
当其他语言调用您的函数时,元程序生成的函数遍历参数类型列表并根据这些类型从其他语言中提取值。它把它们提取成什么?某种保存值的数据结构。由于元编程无法(轻松)构建 struct/class
,因此您需要构建 tuple
(实际上,支持这样的元编程是 tuple
存在的 80%)。
一旦 tuple<Params>
构建完成,您就可以使用 std::apply
来调用该函数。你不能用 invoke
.
3:您不想让每个人都将参数粘贴到 tuple
中只是为了能够执行相当于 invoke
.
4:您需要确定 invoke
接受 tuple
的函数与 apply
解压 tuple
之间的区别。毕竟,如果您正在编写一个对用户指定的参数执行 invoke
的模板函数,那么如果用户恰好提供一个 tuple
作为参数并且您的 invoke
函数解压它。
您可以使用其他方式来区分案例,但具有不同的功能对于简单的案例来说是一个足够的解决方案。如果您正在编写一个更通用的 apply
风格的函数,除了传递其他参数或将多个元组解压缩到参数列表(或这些的组合)之外,您还希望能够解压缩 tuple
s ), 你会想要一个特殊的 super_invoke
来处理这个问题。
但是invoke
是满足简单需求的简单函数。 apply
.