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
的说法如下:
- 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 起作用,类型必须完全匹配。
当您完美转发 f
到 invoke
时,此问题已解决,因为您最初从 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
)。我想这就是您的困惑所在。
试图理解以下示例编译失败的原因:
#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
的说法如下:
- Invoke the Callable object
f
with the parameters args. As byINVOKE(std::forward<F>(f), std::forward<Args>(args)...)
. This overload participates in overload resolution only ifstd::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 起作用,类型必须完全匹配。
当您完美转发 f
到 invoke
时,此问题已解决,因为您最初从 f3
.
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
)。我想这就是您的困惑所在。