std::function operator() 和 std::forward 中发生了什么?

what is going on in std::function operator() and std::forward?

本文关键字:std 发生了 什么 forward function operator      更新时间:2023-10-16

我正在查看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 的部分特化)。 在这里使用 std::forward 的目的是什么?我很困惑:-)?

您是正确的,这不是典型的"完美转发"场景。 一个简短的例子可以帮助说明动机。 假设具有检测构造函数和析构函数的类型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)

解释:

a1a2是在堆栈上构造的。 然后,当传递到function调用程序时,首先复制a1以绑定到第一个 by-value 参数,然后在a1上调用std::forward<A>,这会将其从 by-value 参数移动到lambda 中。

相比之下,无需复制a2即可绑定到functionA&参数,然后调用std::forward<A&>(a2),这会将a2作为左值而不是右值转发,这会绑定到 lambda 的A&参数。

然后事情就被破坏了。~A(-1)表示使用此检测A销毁处于移动构造自状态的A

总之,即使ArgTypes不像通常的完美转发习惯用法那样推导,我们仍然希望将按值ArgTypes作为右值转发,将按引用ArgTypes作为左值转发。 所以std::forward恰好在这里做我们想做的事。

我想你对这里的很多事情感到困惑。

首先,完美转发与可变参数模板无关。您可以创建一个包装类,该类具有一个函数,该函数接受一个参数并将其转发到包装的对象:

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的目的。只移动应该移动的内容。

std::forward只是将右值引用附加到类型,因此考虑到引用折叠规则,它有效地按原样传递引用参数并移动对象参数。