考虑如下代码:
template<typename T>
void func(T &&parem);
Widget widgetFactory(); // 返回右值的函数
Widget w; // 变量(左值)
func(w); // 调用func并且传入左值,T的推导类型为Widget&
func(widgetFactor()); // 调用func并且传入右值,T的推导类型为Widget
传递实参类型都为Widget
,一个左值,一个右值,导致了针对模板形参T
得出了不同的推导结果。决定万能引用是变成左值还是右值引用的机制,也是std::forward
得以运作的机制。
在C++
中“引用的引用”是非法的:
int x;
auto& & rx = x; //错误!不可以声明引用的引用
当左值传递给万能引用时会发生下面的情况:
template<typename T>
void func(T &&parem);
func(w);
如果把T
的推导类型(即Widget &
)代码实例化模板,就得到如下结果:
void func(Widget& && param);
上述为引用的引用。由于万能引用param
是左值初始化的,其推导结果类型应该是左值引用, 但是编译器进行了哪些操作从而有了如下的函数签名呢?
void func(Widget ¶m);
答案为引用折叠,规则为:
如果任一引用为左值引用,则结果为左值引用,否则为右值引用
引用折叠是使std::forward
得以运作的关键:
template<typename T>
void f(T &&fParam) {
someFunc(std::forward<T>(fParam)); // 將fParam转发至somFunc
}
std::forward
的任务为:当且仅当编码T
中的信息表明传递给实参是个右值,对fParam
(左值)实施到右值的强制类型转换
引用折叠发生的情况还有auto
变量的类型生成:
Widget w;
auto&& w1 = w;
初始化w1
的是个左值,因此auto
的类型推导结果为Widget &
。在w1
声明中以Widget&
带入auto,久产生如下代码:
Widget& && w1 = w;
引用折叠后会变成:
Widget & w1 = w;
第三种引用折叠的情况是生存和使用typedef
和别名声明:
template<typename T>
class Widget {
public:
typedef T&& RvalueRefToT;
}
假设我们用坐值引用实例化:
Widget<int &> w;
在Widget中以int&
带入T
的位置得到如下:
typedef int & &&RvalueRefToT;
引用折叠后为:
typedef int& RvalueRefToTl;