在理想的转发中, std::forward
用于将命名的右值引用t1
和t2
转换为未命名的右值引用。 这样作的目的是什么? 若是将t1
和t2
保留为左值,这将如何影响被调用函数的inner
? ios
template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
我认为有一个实现std :: forward的概念代码能够增长讨论的范围。 这是斯科特·迈耶斯(Scott Meyers)讲的《有效的C ++ 11/14采样器》中的一张幻灯片 函数
代码中的函数move
为std::move
。 在该演讲的前面有一个(有效的)实现。 我在libstdc ++中的move.h文件中找到了std :: forward的实际实现 ,但这根本没有启发性。 oop
从用户的角度来看,其含义是std::forward
是有条件强制转换为右值。 若是我编写的函数指望参数中包含左值或右值,而且仅当将其做为右值传递时,但愿将其做为右值传递给另外一个函数,则该功能颇有用。 若是我没有将参数包装在std :: forward中,它将始终做为常规引用传递。 spa
#include <iostream> #include <string> #include <utility> void overloaded_function(std::string& param) { std::cout << "std::string& version" << std::endl; } void overloaded_function(std::string&& param) { std::cout << "std::string&& version" << std::endl; } template<typename T> void pass_through(T&& param) { overloaded_function(std::forward<T>(param)); } int main() { std::string pes; pass_through(pes); pass_through(std::move(pes)); }
果真,它会打印 指针
std::string& version std::string&& version
该代码基于前面提到的演讲中的示例。 从大约15:00开始滑动10。 code
还没有明确的一点是static_cast<T&&>
也能够正确处理const T&
。
程序: htm
#include <iostream> using namespace std; void g(const int&) { cout << "const int&\n"; } void g(int&) { cout << "int&\n"; } void g(int&&) { cout << "int&&\n"; } template <typename T> void f(T&& a) { g(static_cast<T&&>(a)); } int main() { cout << "f(1)\n"; f(1); int a = 2; cout << "f(a)\n"; f(a); const int b = 3; cout << "f(const b)\n"; f(b); cout << "f(a * b)\n"; f(a * b); }
产生: 对象
f(1) int&& f(a) int& f(const b) const int& f(a * b) int&&
注意,“ f”必须是模板函数。 若是仅将其定义为“ void f(int && a)”,则此方法无效。 get
若是将t1和t2保留为左值,这将如何影响被调用函数的内部? 编译器
若是在实例化以后, T1
是char
类型,而T2
是类,则您但愿传递每一个副本t1
和每一个const
引用t2
。 好吧,除非inner()
每一个非const
引用都接受它们,也就是说,在这种状况下,您也要这样作。
尝试编写一组outer()
函数,该函数在没有右值引用的状况下实现此功能,从而推断出从inner()
类型传递参数的正确方法。 我认为您将须要2 ^ 2的东西,须要大量的模板元数据来推论参数,而且须要大量时间来在全部状况下都作到这一点。
而后有人带来了inner()
,它每一个指针都接受参数。 我认为如今等于3 ^ 2。 (或4 ^ 2。该死,我没必要费心去考虑const
指针是否会有所做为。)
而后想象您要为五个参数执行此操做。 或七个。
如今您知道了为何有一些聪明的想法想出“完美转发”的方法:它使编译器为您完成全部这些工做。
您必须了解转发问题。 您能够详细阅读整个问题 ,但我将进行总结。
基本上,给定表达式E(a, b, ... , c)
,咱们但愿表达式f(a, b, ... , c)
等价。 在C ++ 03中,这是不可能的。 尝试了不少,但都在某些方面失败了。
最简单的方法是使用左值引用:
template <typename A, typename B, typename C> void f(A& a, B& b, C& c) { E(a, b, c); }
但这没法处理临时值: f(1, 2, 3);
,由于这些不能绑定到左值引用。
下一个尝试多是:
template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c) { E(a, b, c); }
能够解决上述问题,但会出现触发器。 如今,它不容许E
具备很是量参数:
int i = 1, j = 2, k = 3; void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
第三次尝试接受const-references,可是const_cast
将const
移开:
template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c) { E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c)); }
这能够接受全部值,能够传递全部值,但可能致使未定义的行为:
const int i = 1, j = 2, k = 3; E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
最终的解决方案能够正确处理全部问题,但要以没法维护为代价。 使用const和non-const的全部组合提供f
重载:
template <typename A, typename B, typename C> void f(A& a, B& b, C& c); template <typename A, typename B, typename C> void f(const A& a, B& b, C& c); template <typename A, typename B, typename C> void f(A& a, const B& b, C& c); template <typename A, typename B, typename C> void f(A& a, B& b, const C& c); template <typename A, typename B, typename C> void f(const A& a, const B& b, C& c); template <typename A, typename B, typename C> void f(const A& a, B& b, const C& c); template <typename A, typename B, typename C> void f(A& a, const B& b, const C& c); template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c);
N个参数须要2 N个组合,这是一场噩梦。 咱们但愿自动执行此操做。
(这其实是使编译器在C ++ 11中为咱们完成的工做。)
在C ++ 11中,咱们有机会解决此问题。 一种解决方案修改了现有类型的模板推导规则,但这可能会破坏大量代码。 所以,咱们必须寻找另外一种方法。
解决方案是改成使用新添加的rvalue-references ; 咱们能够在推导右值引用类型并建立任何所需结果时引入新规则。 毕竟,咱们如今不可能破坏代码。
若是给出对引用的引用(请注意引用是一个包含T&
和T&&
的通用术语),咱们将使用如下规则来计算结果类型:
“给定类型T,它引用了类型T,尝试建立类型“对cv TR的左值引用”会建立类型“对T的左值引用”,而尝试建立对类型T的“右值引用” cv TR”建立TR类型。”
或以表格形式:
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)
接下来,使用模板参数推导:若是参数是左值A,则为模板参数提供对A的左值引用。不然,咱们一般进行推论。 这给出了所谓的通用参考 (术语“ 转发参考”如今是正式参考 )。
为何这有用? 由于合并在一块儿,因此咱们能够跟踪类型的值类别:若是是左值,则有一个左值引用参数,不然有一个右值引用参数。
在代码中:
template <typename T> void deduce(T&& x); int i; deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&) deduce(1); // deduce<int>(int&&)
最后一件事是“转发”变量的值类别。 请记住,一旦在函数内部,该参数就能够做为左值传递给任何对象:
void foo(int&); template <typename T> void deduce(T&& x) { foo(x); // fine, foo can refer to x } deduce(1); // okay, foo operates on x which has a value of 1
很差 E须要得到与咱们获得的相同的价值类别! 解决方法是这样的:
static_cast<T&&>(x);
这是作什么的? 考虑咱们在deduce
函数内部,而且已经传递了一个左值。 这意味着T
是A&
,所以静态类型转换的目标类型是A& &&
,或者仅仅是A&
。 因为x
已是A&
,因此咱们什么也不作,只剩下一个左值引用。
当咱们传递了一个右值时, T
为A
,所以静态类型转换的目标类型为A&&
。 强制转换会产生一个右值表达式, 该表达式再也不能够传递给左值引用 。 咱们维护了参数的值类别。
将它们放在一块儿可使咱们“完美转发”:
template <typename A> void f(A&& a) { E(static_cast<A&&>(a)); }
当f
收到一个左值时, E
获得一个左值。 当f
收到一个右值时, E
获得一个右值。 完善。
固然,咱们要摆脱丑陋的境地。 static_cast<T&&>
晦涩难懂,难以记住; 让咱们改成建立一个名为forward
的实用程序函数,该函数执行相同的操做:
std::forward<A>(a); // is the same as static_cast<A&&>(a);
在理想的转发中,std :: forward用于将命名的右值引用t1和t2转换为未命名的右值引用。 这样作的目的是什么? 若是将t1和t2保留为左值,对被调用函数内部的影响如何?
template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
若是在表达式中使用命名的右值引用,则它其实是左值(由于您按名称引用对象)。 考虑如下示例:
void inner(int &, int &); // #1 void inner(int &&, int &&); // #2
如今,若是咱们这样称呼outer
outer(17,29);
咱们但愿将17和29转发给#2,由于17和29是整数文字,而且是这样的右值。 可是因为表达式inner(t1,t2);
中的t1
和t2
inner(t1,t2);
是左值,则您将调用#1而不是#2。 这就是为何咱们须要使用std::forward
将引用转换回未命名的引用。 所以, outer
t1
始终是左值表达式,而forward<T1>(t1)
多是取决于T1
的右值表达式。 若是T1
是左值引用,则后者只是左值表达式。 而且,若是对external的第一个参数是左值表达式,则仅推导T1
为左值引用。