std::function operator() 和 std::forward 中发生了什么?
what is going on in std::function operator() and std::forward?
我正在查看 std::function
实现及其调用 operator()
template<typename Ret, typename... ArgTypes>
Ret function< Ret (ArgTypes...)>::operator()(ArgTypes...args) const
{
// some stuff
return invoker(functor, std::forward<ArgTypes>(args)...);
}
我特别纳闷,这里为什么要用std::forward
?这与完美转发有什么关系吗?
因为只有当 operator()
是一个带有可变模板声明 template<typename... Args>
的模板时才能完成完美转发(事实并非如此,声明是 std::function[ 的部分特化=23=]).
这里使用std::forward的用意是什么?我很困惑:-)?
std::forward
只是将右值引用附加到类型,因此考虑到引用折叠规则,它有效地按原样传递引用参数并移动对象参数。
我想你对这里的很多事情感到困惑。
首先,完美转发与可变参数模板无关。您可以创建一个包装器 class,它具有一个接受一个参数并将其转发给包装对象的函数:
template<typename T>
struct Wrapper {
template<typename Arg>
decltype(auto) test(Arg&& arg) {
return t.test(std::forward<Arg>(arg));
}
T t;
};
注意这里使用了完美转发,没有任何可变参数模板。如果 t.test
需要一个仅移动类型作为参数,则没有 forward<Arg>(arg)
.
将无法调用它
这里发生的第二件事是参数后面没有跟&&
。将 &&
添加到 ArgTypes
将是一个错误,并且会导致某些情况无法编译。考虑这个简单的案例:
std::function<void(int)> f;
int i = 0;
f(i);
那将无法编译。如果将 &&
添加到 ArgTypes
,每个未引用的参数(例如 int
)将成为调用运算符的右值引用(在我们的例子中,int&&
) .由于所有参数类型都已在 std::function
参数列表中正确限定,因此您希望在调用运算符中接收的正是这些类型,而不是转换。
如果不使用 &&
为什么需要 std::forward
?因为即使您不需要推断值类别,您仍然不需要将每个参数都复制到包含的函数中。如果 std::function
的参数之一是 int&
,您不想移动它。但是如果其中一个参数是std::unique_ptr<int>
,你必须移动它!而这正是 std::forward
的用途。只搬该搬的。
你是对的,这不是典型的 "perfect forwarding" 场景。一个简短的例子可以帮助说明动机。假设一个类型 A
带有检测构造函数和析构函数:
#include "A.h"
#include <functional>
#include <iostream>
int
main()
{
A a1{1};
A a2{2};
std::function<void(A, A&)> f{[](A x, A& y){}};
f(a1, a2);
}
这将输出:
A(int state): 1
A(int state): 2
A(A const& a): 1
A(A&& a): 1
~A(1)
~A(-1)
~A(2)
~A(1)
解释:
a1
和a2
是在栈上构造的。然后当传递到 function
调用程序时,首先复制 a1
以绑定到第一个按值参数,然后 std::forward<A>
在 a1
上调用 将它从按值参数移动到lambda中。
相比之下,a2
不需要复制绑定到function
A&
参数,然后调用std::forward<A&>(a2)
,转发a2
作为左值而不是右值,这绑定到 lambda 的 A&
参数。
然后事情就毁了。 ~A(-1)
表示使用此检测 A
.
在移动构造自状态中破坏 A
总而言之,即使 ArgTypes
没有像通常的完美转发习语那样推导出来,我们仍然希望按值 ArgTypes
作为右值转发,而按引用 ArgTypes
作为左值。所以 std::forward
恰好做了我们在这里想要的。
我正在查看 std::function
实现及其调用 operator()
template<typename Ret, typename... ArgTypes>
Ret function< Ret (ArgTypes...)>::operator()(ArgTypes...args) const
{
// some stuff
return invoker(functor, std::forward<ArgTypes>(args)...);
}
我特别纳闷,这里为什么要用std::forward
?这与完美转发有什么关系吗?
因为只有当 operator()
是一个带有可变模板声明 template<typename... Args>
的模板时才能完成完美转发(事实并非如此,声明是 std::function[ 的部分特化=23=]).
这里使用std::forward的用意是什么?我很困惑:-)?
std::forward
只是将右值引用附加到类型,因此考虑到引用折叠规则,它有效地按原样传递引用参数并移动对象参数。
我想你对这里的很多事情感到困惑。
首先,完美转发与可变参数模板无关。您可以创建一个包装器 class,它具有一个接受一个参数并将其转发给包装对象的函数:
template<typename T>
struct Wrapper {
template<typename Arg>
decltype(auto) test(Arg&& arg) {
return t.test(std::forward<Arg>(arg));
}
T t;
};
注意这里使用了完美转发,没有任何可变参数模板。如果 t.test
需要一个仅移动类型作为参数,则没有 forward<Arg>(arg)
.
这里发生的第二件事是参数后面没有跟&&
。将 &&
添加到 ArgTypes
将是一个错误,并且会导致某些情况无法编译。考虑这个简单的案例:
std::function<void(int)> f;
int i = 0;
f(i);
那将无法编译。如果将 &&
添加到 ArgTypes
,每个未引用的参数(例如 int
)将成为调用运算符的右值引用(在我们的例子中,int&&
) .由于所有参数类型都已在 std::function
参数列表中正确限定,因此您希望在调用运算符中接收的正是这些类型,而不是转换。
如果不使用 &&
为什么需要 std::forward
?因为即使您不需要推断值类别,您仍然不需要将每个参数都复制到包含的函数中。如果 std::function
的参数之一是 int&
,您不想移动它。但是如果其中一个参数是std::unique_ptr<int>
,你必须移动它!而这正是 std::forward
的用途。只搬该搬的。
你是对的,这不是典型的 "perfect forwarding" 场景。一个简短的例子可以帮助说明动机。假设一个类型 A
带有检测构造函数和析构函数:
#include "A.h"
#include <functional>
#include <iostream>
int
main()
{
A a1{1};
A a2{2};
std::function<void(A, A&)> f{[](A x, A& y){}};
f(a1, a2);
}
这将输出:
A(int state): 1
A(int state): 2
A(A const& a): 1
A(A&& a): 1
~A(1)
~A(-1)
~A(2)
~A(1)
解释:
a1
和a2
是在栈上构造的。然后当传递到 function
调用程序时,首先复制 a1
以绑定到第一个按值参数,然后 std::forward<A>
在 a1
上调用 将它从按值参数移动到lambda中。
相比之下,a2
不需要复制绑定到function
A&
参数,然后调用std::forward<A&>(a2)
,转发a2
作为左值而不是右值,这绑定到 lambda 的 A&
参数。
然后事情就毁了。 ~A(-1)
表示使用此检测 A
.
A
总而言之,即使 ArgTypes
没有像通常的完美转发习语那样推导出来,我们仍然希望按值 ArgTypes
作为右值转发,而按引用 ArgTypes
作为左值。所以 std::forward
恰好做了我们在这里想要的。