条款28:理解引用折叠
条款28:理解引用折叠
万能引用的模板推导原理
template<typename T>
void func(T&& param);
- 当传入左值时,
T会被推导为 左值引用类型,如Widget&。 - 当传入右值时,
T会被推导为 非引用类型,如Widget。
示例:
Widget w; // 左值
Widget widgetFactory(); // 返回右值
func(w); // T = Widget&,param 类型为 Widget& &&
func(widgetFactory()); // T = Widget, param 类型为 Widget&&
C++中禁止声明引用的引用
int x;
auto& & rx = x; // 错误!不能声明引用的引用
- 虽然代码禁止写引用的引用,但模板实例化时可能会“产生”引用的引用。
- 例如
func(w)的形参param类型是Widget& &&,这是引用的引用。
引用折叠规则(Reference Collapsing)
当出现引用的引用时,编译器会折叠为单个引用,规则是:
| 外层引用 | 内层引用 | 折叠结果 | 示例代码说明 |
|---|---|---|---|
& |
& |
& |
T = int& → T& = int& & → 折叠为 int& |
& |
&& |
& |
T = int&& → T& = int&& & → 折叠为 int& |
&& |
& |
& |
T = int& → T&& = int& && → 折叠为 int& |
&& |
&& |
&& |
T = int → T&& = int && → 保留为 int&& |
- 只要有左值引用(&),结果就是左值引用(&);否则才是右值引用(&&)。
例子解析
// 当传入左值时:
T = Widget&
// 参数类型展开:
Widget& && param
// 引用折叠后:
Widget& param
所以传入左值时,形参变为左值引用。
std::forward 的实现机制
template<typename T>
T&& forward(std::remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
万能引用折叠保证:
- 传入左值时,
T = Widget&,返回Widget&,不会转换为右值。 - 传入右值时,
T = Widget,返回Widget&&,实现完美转发。
auto&& 变量的引用折叠
Widget w;
auto&& w1 = w; // w 是左值
// 推导过程:
// auto 推导为 Widget&,所以 w1 的类型是 Widget& &&
// 引用折叠规则:Widget& && 折叠为 Widget&
// => 最终:w1 是 Widget& 类型,绑定到左值 w
auto&& w2 = widgetFactory(); // widgetFactory() 是右值
// 推导过程:
// auto 推导为 Widget(注意不是引用)
// 所以 w2 的类型是 Widget&&(右值引用)
// 引用折叠不发生(没有引用的引用)
// => 最终:w2 是 Widget&& 类型,绑定到右值 widgetFactory()
万能引用的定义
- 万能引用是满足两个条件的右值引用:
- 通过模板类型推导区分左值和右值,左值被推导为
T&,右值被推导为T。 - 发生引用折叠,得到最终参数类型。
- 通过模板类型推导区分左值和右值,左值被推导为
- 万能引用是一个特殊上下文中的右值引用,如模板函数参数或
auto&&。
引用折叠发生的四种情况总结
模板实例化
如万能引用函数 template<typename T> void func(T&& param),传入左值会推导成 T = U&,折叠 U& && 为 U&。
auto 类型推导
Widget w;
auto&& w1 = w; // auto 推导为 Widget&,折叠为 Widget&
typedef 和别名声明中
// 一个模板类,内部声明了一个 typedef 类型 T&&
template<typename T>
class Widget {
public:
typedef T&& RvalueRefToT; // 这个类型可能是引用的引用
};
// 实例化模板时传入 T = int&,即 T 是左值引用类型
Widget<int&> w;
// 此时 typedef 展开为:
typedef int& && RvalueRefToT;
// 由于是引用的引用,触发引用折叠规则:
// - 左值引用 & 与 右值引用 && 折叠结果是左值引用 &
// 所以最终:RvalueRefToT 被折叠为 int&
decltype 表达式中
如果 decltype 表达式涉及引用的引用,也会触发折叠:
int x;
decltype((x))& & rx = x;
先看 decltype((x)) 返回什么类型?
x是int类型变量,(x)是 对变量 x 的括号表达式,表达式是一个左值。- 根据 C++ 规则:
decltype((x))—— 当表达式是一个 左值,decltype返回 该表达式的引用类型,也就是int&。
所以:
decltype((x)) == int&
替换 decltype((x))
带入后原表达式变成:
int& & & rx = x;
decltype((x))是int&- 后面又接了两个
&,所以类型写成了三层引用:外层引用 & ,里面还有int& &
逐步折叠
第一次折叠,先折叠最内层两个引用:
int& &,外层&,内层&,折叠结果是&,即:
int&
折叠后剩下:
int& & rx = x;
第二次折叠:
int& &,同样外层&,内层&,折叠结果还是&:
int&
decltype((x))& & rx = x; 最终等价于:
int& rx = x;
总结
- 引用折叠发生在这四种情况:
- 模板实例化
- auto 类型推导
- typedef/using 别名定义
- decltype 语境
- 规则:只要有左值引用,结果就是左值引用;否则是右值引用。
- 理解引用折叠是理解万能引用、完美转发和
std::forward的关键。

浙公网安备 33010602011771号