std :: forward如何工作?

Dav*_*vid 106 c++ c++11

可能重复:
使用forward的优点

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

我的一部分困惑是:"如果它有一个名字,它就是一个左值" - 如果是这样的话,为什么std::forward当我通过thing&& xvs 时表现不同thing& x

Bar*_*ski 156

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

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

void set(const std::vector<T> & v) { _v = v; }
Run Code Online (Sandbox Code Playgroud)

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

_v = makeAndFillVector();
Run Code Online (Sandbox Code Playgroud)

编译器会移动向量而不是复制它.但是如果你介绍一个中间人,set()那么关于你论证的右值性质的信息就会丢失,并且会复制一份.

set(makeAndFillVector()); // set will still make a copy
Run Code Online (Sandbox Code Playgroud)

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

通常你会通过set()为lvalues和rvalues分别重载函数来做到这一点:

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

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

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

template<class T>
void perfectSet(T && t);
Run Code Online (Sandbox Code Playgroud)

根据您调用此模板函数的方式,类型T将在某种程度上神奇地推断出不同的类型.如果你用左值调用它:

std::vector<T> v;
perfectSet(v);
Run Code Online (Sandbox Code Playgroud)

矢量v将通过引用传递.但如果你用右值调用它:

perfectSet(makeAndFillVector());
Run Code Online (Sandbox Code Playgroud)

(匿名)向量将通过右值引用传递.所以C++ 11魔术是有目的地设置的,以便在可能的情况下保留参数的右值性质.

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

template<class T>
void perfectSet(T && t) {
    set(std::forward<T>(t));
}
Run Code Online (Sandbox Code Playgroud)

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

void perfectSet(T && t) {
    set(t);
    set(t); // t still unchanged
}
Run Code Online (Sandbox Code Playgroud)

对此:

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

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

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

  • `void set(**const**std :: vector&v){_ v = v; 不要让它变得比它需要的更复杂. (4认同)
  • "在这种情况下,它将转换为右值参考.它不是!" - 是的!在你的`perfectSet`里面,`t`已经*是一个左值.使用`static_cast`(或`std :: forward`),我们将其更改为rvalue. (3认同)
  • @Xeo:除非您使用向量引用调用perfectSet.如:vector v; 矢量&vr; perfectSet(VR); 当您将右值引用转换为右值引用时,结果仍然是左值引用.我正是这个意思. (2认同)

Xeo*_*Xeo 145

首先,让我们来看看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)
Run Code Online (Sandbox Code Playgroud)

(从这个答案中无耻地偷走了.)

然后让我们来看一个想要使用完美转发的课程:

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
};
Run Code Online (Sandbox Code Playgroud)

现在是一个示例调用:

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'!
}
Run Code Online (Sandbox Code Playgroud)

我希望这个循序渐进的答案可以帮助您和其他人了解std::forward工作方式.

  • "(从这个答案中无耻地偷走了.)"不要再想一想.他们从这里偷了它:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#References%20to%20References :-) (51认同)
  • 我真正的困惑是为什么std :: forward不允许使用模板参数演绎,但我不想用这些语言问它,因为我已经尝试了一次而没有我能理解的结果.我想我现在已经弄明白了(http://stackoverflow.com/a/8862379/369872) (3认同)
  • 我想我永远不会知道,但是...为什么要投票?我错过了什么?我说错什么了吗? (2认同)
  • 我会给你一篮花和一大块巧克力。谢谢! (2认同)