在2.2节中介绍的右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值了,并不是它原来的类型了。比如:
template <typename T> void forwardValue(T& val) { processValue(val); // 右值参数会变成左值 } template <typename T> void forwardValue(const T& val) { processValue(val); // 参数都变成常量左值引用了 }
都不能按照参数的本来的类型进行转发。
因此,我们需要一种方法能按照参数原来的类型转发到另一个函数,这种转发被称为完美转发。所谓完美转发(Perfect Forwarding),是指在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。C++11中提供了这样的一个函数std::forward,它是为转发而生的,不管参数是T&&这种未定的引用还是明确的左值引用或者右值引用,它会按照参数本来的类型转发。看看这个例子:
代码清单2-4 参数转发的示例
template<typename T> void PrintT(T& t) { cout <<"lvaue"<< endl; } template<typename T> void PrintT(T && t) { cout <<"rvalue"<< endl; } template<typename T> void TestForward(T && v) { PrintT(v); PrintT(std::forward<T>(v)); PrintT(std::move(v)); } Test() { TestForward(1); int x = 1; TestForward(x); TestForward(std::forward<int>(x)); }
我们来分析一下测试结果。
TestForward(1):由于1是右值,所以未定的引用类型T && v被一个右值初始化后变成了一个右值引用,但是在TestForward函数体内部,调用PrintT(v)时,v又变成了一个左值(因为在这里它已经变成了一个具名的变量,所以它是一个左值),因此,第一个PrintT被调用,打印出“lvaue”。调用PrintT(std::forward<T>(v))时,由于std::forward会按参数原来的类型转发,因此,它还是一个右值(这里已经发生了类型推导,所以这里的T&&不是一个未定的引用类型(关于这点可以参考2.1节),会调用void PrintT(T &&t)函数。调用PrintT(std::move(v))是将v变成一个右值(v本身也是右值),因此,它将输出rvalue。
TestForward(x)未定的引用类型T && v被一个左值初始化后变成了一个左值引用,因此,在调用PrintT(std::forward<T>(v))时它会被转发到void PrintT(T& t)。
右值引用、完美转发再结合可变模板参数,我们可以写一个万能的函数包装器(可变模板参数将在3.2节中介绍,读者不妨读完下一节再回头来看这个函数),带返回值的、不带返回值的、带参数的和不带参数的函数都可以委托这个万能的函数包装器执行。下面看看这个万能的函数包装器。
template<class Function, class... Args> inline auto FuncWrapper(Function && f, Args && ... args) -> decltype(f(std::for ward<Args>(args)...)) { return f(std::forward<Args>(args)...); } 测试代码如下: void test0() { cout <<"void"<< endl; } int test1() { return 1; } int test2(int x) { return x; } string test3(string s1, string s2) { return s1 + s2; } test() { FuncWrapper(test0); // 没有返回值,打印1 FuncWrapper(test1); // 返回1 FuncWrapper(test2, 1); // 返回1 FuncWrapper(test3, "aa", "bb"); // 返回"aabb" }