std :: forward如何工作?
可能重复:
使用forward的优点
我知道它做什么,什么时候使用它,但我仍然无法围绕它如何工作。 请尽可能详细地说明,如果允许使用模板参数推导, std::forward
将不正确。
我的一部分混乱是这样的:“如果它有一个名字,这是一个左值” – 如果这是为什么std::forward
行为不同,当我通过thing&& x
与thing& 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
检测,它实际上是一个右值,并正确地返回一个右值被转发。