std :: forward如何工作?

可能重复:
使用forward的优点

我知道它做什么,什么时候使用它,但我仍然无法围绕它如何工作。 请尽可能详细地说明,如果允许使用模板参数推导, std::forward将不正确。

我的一部分混乱是这样的:“如果它有一个名字,这是一个左值” – 如果这是为什么std::forward行为不同,当我通过thing&& xthing& x

首先,我们来看看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如何工作。

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

假设我想传递一个std::vector<T> v ,你应该把它作为数据成员_v存储在你的数据结构中。 天真(和安全)的解决scheme将始终将载体复制到其最终目的地。 所以如果你通过中介函数(方法)来做这个事情,那么这个函数应该被声明为引用。 (如果你声明它是通过值来获得一个向量,你将会执行一个额外的完全不必要的副本。)

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

如果你手上有一个左值,这样就好了,但右值呢? 假设该向量是调用函数makeAndFillVector() 。 如果您执行了直接分配:

 _v = makeAndFillVector(); 

编译器会移动vector而不是复制它。 但是,如果引入一个中介set() ,那么关于参数的右值性质的信息将会丢失,并且会被复制。

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

为了避免这个副本,你需要“完美的转发”,这将导致每次优化代码。 如果你被赋予一个左值,你希望你的职能被视为一个左值并且复制一份。 如果你有一个右值,你希望你的函数把它当作右值并移动它。

通常你可以通过重载函数set()分别为左值和右值做到这一点:

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

但是现在想象你正在写一个接受T的模板函数,并且用这个T调用set() (不要担心我们的set()只是为vector定义的事实)。 诀窍是你希望这个模板在模板函数用左值实例化的时候调用set()的第一个版本,在第二个时候用右值初始化。

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

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

根据你如何调用这个模板函数,typesT将有点神奇地推导出不同的。 如果你用左值调用它:

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

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

 perfectSet(makeAndFillVector()); 

(匿名)向量将通过右值引用传递。 所以C ++ 11的魔法是有目的地设置的,如果可能的话保持参数的右值性质。

现在,在perfectSet中,你想完美地将parameter passing给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并selectset的左值引用版本。 但是如果你转发了t ,那么编译器将保留它的右值,并且set()的右值引用版本将被调用。 这个版本移动了t的内容,这意味着原来的内容变空了。

这个答案结果比我最初想象的要长得多;-)

这是因为当调用完美的转发时,typesT 不是值types,它也可以是引用types。

例如:

 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可以简单地看看显式typesT,看看你真的通过它。 当然,如果我记得的话,这样做的确切实现是非三联的,但这就是信息的来源。

当你引用一个命名的右值引用时 ,那确实是一个左值。 然而,通过上面的方法forward检测,它实际上是一个右值,并正确地返回一个右值被转发。