考虑这些情况:
int i{};
int& ir{i};
class A{
public:
int& i;
A(int&& pi):i(pi){}
};
A a1{i}; // Error // case 1
A a2{int(1)}; // OK // case 2
class B{
public:
int& i;
template<typename TYPE>
B(TYPE&& pi):i(pi){}
};
B b1{i}; // OK // case 3
B b2{int(1)}; //OK // case 4
int& ii{int(12)}; // Error // case 5
int& iii{std::move(int(12))}; // Error // case 6
template<typename TYPE>
class C{
public:
TYPE& i;
C(TYPE&& pi):i(pi){}
};
C c1{i}; // Error // case 7
C c2{int(1)}; // OK // case 8
C<int&> c3{i}; // OK // case 9
C<int&> c4{int(1)}; // Error // case 10
int&& iiii{ir}; // Error // case 11
右值不能绑定(bind)到左值,如果我的理解是正确的,TYPE&&
将折叠为 TYPE&&
或 TYPE&
。
然而我很难理解这些情况,特别是情况 4。如果情况 4 是正确的,那么这意味着我们有 b2.i
这是一个引用,从临时(右值)初始化。那么为什么情况2、5、6、7是错误的呢?当情况 9 正确时,这意味着 TYPE&&
是 int&&&
(我假设)折叠为 int&
,那么 c3. i
是从左值初始化的右值(int&&
),而情况 10 和 11 不正确?
我希望有人能够详细解释有关此主题的一般规则以及这些案例。
最佳答案
这里有几种机制在起作用:左值/右值语义、引用折叠、转发引用和 CTAD。我认为解释你列出的案例应该足以形成一个整体。
- 右值引用
int&& pi
无法绑定(bind)到左值i
。 - 右值引用
int&& pi
可以绑定(bind)到右值int(1)
。i(pi)
中的pi
引用内存位置,因此它是一个左值,允许初始化a2.i
。构造后,a2.i
是对int(1)
的悬空引用。 - 转发引用
TYPE&& pi
可以绑定(bind)到左值i
。TYPE
是int&
。b1.i
指的是i
。 - 转发引用
TYPE&& pi
可以绑定(bind)到右值int(1)
。TYPE
是int
。TYPE&& pi
是一个右值引用,类似于情况 2b2.i
是对int(1)
的悬空引用。 - 左值引用
int& ii
无法绑定(bind)到右值int(12)
。 - 左值引用
int& iii
无法绑定(bind)到右值std::move(int(12))
。
在情况 7-10 中,TYPE&& pi
不是转发引用,因为 TYPE
是 class C
的模板参数,而不是构造函数的模板参数。 CTAD 在 7-8 中用于推导 TYPE
,但不会改变这一点:需要一个右值,只有这样才能将 TYPE
推导为 int
>,形成int&& pi
。
TYPE
无法推断。TYPE
被推断为int
。c2.i
,声明为int& i;
,形成对int(1)
的悬空引用。TYPE
明确为int&
。TYPE&& pi
折叠为int& pi
。TYPE& i
折叠为int& i
。c3.i
指的是i
。TYPE&& pi
折叠为int& pi
,左值引用无法绑定(bind)到int(1)
。- 右值引用
int&& iiii
无法绑定(bind)到左值ir
。
还有一个额外的案例,可以使案例 2 和 4 更容易理解:
int&& rvr = 1;
int&& rvr2 = rvr;
第一行是正确的,最后一行是错误的。 int&& rvr
是一个右值引用,可以延长临时变量的生命周期。但最后一行提到的 rvr 是一个左值,因此右值引用 int&& rvr2 不能与其绑定(bind)。
关于c++ - 模板中关于引用折叠的右值(&&) 左值(&) 引用绑定(bind)的规则是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76627864/