std::invoke - 完美转发函子

std::invoke - perfect forwarding functor

试图理解以下示例编译失败的原因:

#include <functional>

template <typename F>
void f1(F&& f)
{
  std::forward<F>(f)("hi");
}

template <typename F>
void f2(F&& f)
{
  std::invoke(f, "hi");  // works but can't perfect forward functor
}

template <typename F>
void f3(F&& f)
{
  std::invoke<F>(f, "hi");
}

int main()
{
  f1([](const char*) {});  // ok
  f2([](const char*) {});  // ok
  f3([](const char*) {});  // error
}

cppreference 关于 std::invoke 的说法如下:

  1. Invoke the Callable object f with the parameters args. As by INVOKE(std::forward<F>(f), std::forward<Args>(args)...). This overload participates in overload resolution only if std::is_invocable_v<F, Args...> is true.

那么为什么 f3 不等同于 f1

因为你需要std::forward<F>(f)std::invoke():

template <typename F>
void f3(F&& f)
{
  std::invoke<F>(std::forward<F>(f), "hi"); // works
}

考虑这两个调用的区别:

void f(const int&) { std::cout << "const int&" << std::endl; }
void f(int&&) { std::cout << "int&&" << std::endl; }

int main()
{
  std::cout << "first" << std::endl;
  int&& a = 3;
  f(a);
  std::cout << "second" << std::endl;
  int&& b = 4;
  f(std::forward<int>(b));
}

输出为

first
const int&
second
int&&

如果删除 const int& 重载,您甚至会在第一次调用时遇到编译器错误:

error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'

std::forward() 是将正确类型传递给 std::invoke() 所必需的。

我猜你遇到了这个错误:

note: template argument deduction/substitution failed:
note: cannot convert ‘f’ (type ‘main()::<lambda(const char*)>’) to type ‘main()::<lambda(const char*)>&&’

那是因为在函数 f3 中,f 是一个 L 值,而 invoke 需要一个 R 值。要使模板参数 deduction/substitution 起作用,类型必须完全匹配。

当您完美转发 finvoke 时,此问题已解决,因为您最初从 f3.

外部传递了一个 R 值

std::invoke 本身就是一个函数。在您的情况下,它的第一个参数是右值引用,而 f 是左值,因此会发生错误。

INVOKE(std::forward<F>(f), std::forward<Args>(args)...)在函数std::invoke被正确选择调用后执行。基本上,您的 lambda 函数按如下方式传递:

original lambda in main -> the parameter of f3 -> the parameter of std::invoke -> the parameter of INVOKE

所以最后一步使用了INVOKE(std::forward<F>(f), std::forward<Args>(args)...)中的std::forward,而中间一步需要转发lambda(f3的参数-> std::invoke)。我想这就是您的困惑所在。