std::forward 如何工作

How does std::forward work?

本文关键字:工作 何工作 forward std      更新时间:2023-10-16

可能的重复项:
使用前向的优点

我知道它的作用以及何时使用它,但我仍然无法理解它是如何工作的。请尽可能详细,并解释如果允许使用模板参数推导std::forward何时不正确。

我的部分困惑是这样的:"如果它有一个名字,那就是一个左值"——如果是这样的话,为什么当我通过thing&& xthing& x时,std::forward的行为会有所不同?

我认为std::forward解释为static_cast<T&&>令人困惑。我们对强制转换的直觉是它将类型转换为其他类型 - 在这种情况下,它将转换为右值引用。不是!所以我们用另一个神秘的东西来解释一件神秘的事情。这个特殊的演员表由Xeo的答案中的表格定义。但问题是:为什么?所以我的理解是:

假设我想通过一个std::vector<T> v,您应该将其作为数据成员存储在数据结构中_v。天真(且安全(的解决方案是始终将矢量复制到其最终目的地。因此,如果您通过中介函数(方法(执行此操作,则应将该函数声明为接受引用。(如果您将其声明为按值获取向量,您将执行额外的完全不必要的复制。

void set(const std::vector<T> & v) { _v = v; }

如果你手里有一个左值,这一切都很好,但是右值呢?假设向量是调用函数 makeAndFillVector() 的结果。如果您执行了直接分配:

_v = makeAndFillVector();

编译器将移动向量而不是复制它。但是,如果你引入一个中介,set(),关于你的论点的正确性质的信息将丢失,并且会复制。

set(makeAndFillVector()); // set will still make a copy

为了避免这种复制,你需要"完美转发",这将导致每次都产生最佳代码。如果给你一个左值,你希望你的函数把它当作左值并制作一个副本。如果给你一个右值,你希望你的函数把它当作右值并移动它。

通常,您将通过分别重载左值和右值的函数set()来实现:

set(const std::vector<T> & lv) { _v = v; }
set(std::vector<T> && rv) { _v = std::move(rv); }

但是现在假设您正在编写一个模板函数,该函数接受T并使用该T调用set()(不要担心我们的set()仅为向量定义的事实(。诀窍在于,您希望此模板在使用 lvalue 实例化模板函数时调用第一个版本的 set(),在使用 rvalue 初始化时调用第二个版本。

首先,这个函数的签名应该是什么?答案是这样的:

template<class T>
void perfectSet(T && t);

根据您调用此模板函数的方式,T的类型会以不同的方式神奇地推导。如果使用左值调用它:

std::vector<T> v;
perfectSet(v);

向量v将通过引用传递。但是,如果您使用右值调用它:

perfectSet(makeAndFillVector());

(匿名(向量将通过右值引用传递。因此,C++11魔术是有目的地设置的,如果可能的话,可以保留参数的正确性质。

现在,在 perfectSet 中,您希望将参数完美地传递给 set() 的正确重载。这是需要std::forward的地方:

template<class T>
void perfectSet(T && t) {
    set(std::forward<T>(t));
}

如果没有 std::forward,编译器将不得不假设我们希望通过引用传递 t。要说服自己这是真的,请比较以下代码:

void perfectSet(T && t) {
    set(t);
    set(t); // t still unchanged
}

对此:

void perfectSet(T && t) {
    set(std::forward<T>(t));
    set(t); // t is now empty
}

如果你没有显式转发t,编译器必须防御性地假设你可能会再次访问t,并选择set的左值引用版本。但是如果你转发t,编译器将保留它的右值性,并将调用set()的右值引用版本。此版本移动了t的内容,这意味着原始版本变为空。

这个答案比我最初假设的要长得多;-(

首先,我们来看看std::forward根据标准做了什么:

§20.2.3 [forward] p2

回报: static_cast<T&&>(t)

(其中T是显式指定的模板参数,t是传递的参数。

现在记住引用折叠规则:

TR   R
T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

(从这个答案中无耻地窃取。

然后让我们看一个想要采用完美转发的类:

template<class T>
struct some_struct{
  T _v;
  template<class U>
  some_struct(U&& v)
    : _v(static_cast<U&&>(v)) {} // perfect forwarding here
                                 // std::forward is just syntactic sugar for this
};

现在一个示例调用:

int main(){
  some_struct<int> s1(5);
  // in ctor: '5' is rvalue (int&&), so 'U' is deduced as 'int', giving 'int&&'
  // ctor after deduction: 'some_struct(int&& v)' ('U' == 'int')
  // with rvalue reference 'v' bound to rvalue '5'
  // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int&&>(v)'
  // this just turns 'v' back into an rvalue
  // (named rvalue references, 'v' in this case, are lvalues)
  // huzzah, we forwarded an rvalue to the constructor of '_v'!
  // attention, real magic happens here
  int i = 5;
  some_struct<int> s2(i);
  // in ctor: 'i' is an lvalue ('int&'), so 'U' is deduced as 'int&', giving 'int& &&'
  // applying the reference collapsing rules yields 'int&' (& + && -> &)
  // ctor after deduction and collapsing: 'some_struct(int& v)' ('U' == 'int&')
  // with lvalue reference 'v' bound to lvalue 'i'
  // now we 'static_cast' 'v' to 'U&&', giving 'static_cast<int& &&>(v)'
  // after collapsing rules: 'static_cast<int&>(v)'
  // this is a no-op, 'v' is already 'int&'
  // huzzah, we forwarded an lvalue to the constructor of '_v'!
}

我希望这个循序渐进的答案能帮助你和其他人了解std::forward是如何工作的。

它之所以有效,是因为当调用完美转发时,类型 T 不是值类型,它也可能是引用类型。

例如:

template<typename T> void f(T&&);
int main() {
    std::string s;
    f(s); // T is std::string&
    const std::string s2;
    f(s2); // T is a const std::string&
}

因此,forward可以简单地查看显式类型 T 以查看您真正传递了它的内容。当然,如果我记得的话,这样做的确切实现是非竞争性的,但这就是信息所在。

当您引用命名的右值引用时,这确实是一个左值。但是,forward通过上述方法检测到它实际上是一个右值,并正确返回要转发的右值。