引用折叠

引用折叠会出现在四种语境中:

  • 模板实例化
  • auto 类型推断
  • decltype 类型推断
  • typedefusing 别名声明

引用的引用是非法的:

int a = 1;
int& & b = a; //@ 错误

当左值传给接受转发引用的模板时,模板参数就会推断为引用的引用:

template<typename T>
void f(T&&);

int i = 1;
f(i); //@ T为int&,T& &&变成了引用的引用,于是需要引用折叠的机制

为了使实例化成功,编译器生成引用的引用时,将使用引用折叠的机制,规则如下:

& + & → &
& + && → &
&& + & → &
&& + && → &&

引用折叠是 std::forward 的支持基础:

//@ 不完整的实现
template<typename T>
T&& forward(remove_reference_t<T>& x)
{
    return static_cast<T&&>(x); //@ 如果传递左值A,T推断为A&,此时需要引用折叠
}

//@ 传递左值A时相当于
A&&& forward(remove_reference_t<A&>& x)
{
    return static_cast<A&&&>(x);
}
//@ 简化后
A& forward(A& x)
{
    return static_cast<A&>(x);
}

//@ 传递右值A相当于
A&& forward(remove_reference_t<A>& x)
{
    return static_cast<A&&>(x);
}
//@ 简化后
A&& forward(A& x)
{
    return static_cast<A&&>(x);
}

auto&& 与使用转发引用的模板原理一样:

int a = 1;
auto&& b = a; //@ a是左值,auto被推断为int&,int& &&折叠为int&

decltype 同理,如果推断中出现了引用的引用,就会发生引用折叠。

如果在 typedef 的创建或求值中出现了引用的引用,就会发生引用折叠。

template<typename T>
struct A {
    using RvalueRef = T&&; //@ typedef T&& RvalueRef;
};

int a = 1;
A<int&>::RvalueRef b = a; //@ int& &&折叠为int&,int& b = a

并且 top-level cv 限定符会被丢弃:

using A = const int&; //@ low-level
using B = int&&; //@ low-level
static_assert(std::is_same_v<volatile A&&, const int&>);
static_assert(std::is_same_v<const B&&, int&&>);