std::invoke 和 std::apply 有什么区别?

What is the difference between std::invoke and std::apply?

提问人:KeyC0de 提问时间:9/22/2018 更新时间:9/22/2018 访问量:15213

问:

它们都用作调用函数、成员函数以及通常任何可调用内容的通用方法。从 cppreference 中,我看到的唯一真正的区别是,在函数中,参数(无论它们有多少)被 ed 到函数中,而在参数中作为 .这真的是唯一的区别吗?为什么他们要创建一个单独的函数来处理 s?std::invokeforwardstd::applytupletuple

C++ 函数指针 C++17 可调用

评论

3赞 Some programmer dude 9/22/2018
考虑到 std::apply 的可能实现使用 std::invoke,这似乎确实是唯一的区别。
0赞 alter_igel 9/22/2018
我自己没有使用过它们,但我想在模板中,将参数捆绑到某个元组中会很方便,而不是采用可变方式并不得不使用模式扩展typename ArgsTupleTtypename... ArgsT
0赞 Chen Li 11/6/2018
也许 apply 的实现者是标准 ml 的忠实粉丝,哈哈

答:

33赞 Barry 9/22/2018 #1

这真的是唯一的区别吗?为什么他们要创建一个单独的函数来处理元组?

因为你真的需要这两种选择,因为它们做不同的事情。考虑:

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)tupleapply(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)
15赞 Nicol Bolas 9/22/2018 #2

您之所以使用,是因为:std::apply

1:实现起来,即使你有访问权限,也是一个很大的痛苦。将元组转换为参数包并非易事。的实现将如下所示(来自 cppref):applystd::invokeapply

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 的元素来调用函数是相当有用的。它支持的基本操作是能够打包一组参数,传递该参数集,然后使用这些参数调用函数。从技术上讲,我们已经有能力使用单个参数(通过传递值)来做到这一点,但是通过 ,您可以获得使用多个参数来做到这一点的能力。tupleapply

它还允许您执行元编程技巧,例如以元编程方式在语言之间进行编组。你向这样的系统注册一个函数,它被赋予函数的签名(和函数本身)。该签名用于通过元编程对数据进行封送处理。

当其他语言调用您的函数时,元程序生成的函数将遍历参数类型列表,并根据这些类型从其他语言中提取值。它把它们提取成什么?保存值的某种数据结构。而且由于元编程不能(轻易地)构建一个 ,因此您可以构建一个(事实上,支持这样的元编程是存在的 80% 原因)。struct/classtupletuple

生成后,您可以使用调用该函数。你真的不能用 .tuple<Params>std::applyinvoke

3:你不想让每个人都把参数粘成一个只是为了能够执行等效的 .tupleinvoke

4:你需要确定一个函数的区别,一个函数需要 ,而ing来解压缩。毕竟,如果你正在编写一个对用户指定的参数执行的模板函数,那么如果用户碰巧提供了一个参数作为参数,而你的函数解压缩了它,那就太糟糕了。invoketupleapplytupleinvoketupleinvoke

您可以使用其他方法来区分情况,但对于简单情况,具有不同的功能是适当的解决方案。如果你正在编写一个更通用的 -style 函数,你希望除了传递其他参数之外还能够解压缩 s,或者将多个元组解压缩到参数列表(或这些参数的组合)中,你会希望有一个可以处理它的特殊元组。applytuplesuper_invoke

但是,对于简单的需求来说,这是一个简单的功能。这同样适用于.invokeapply