英文:
What are the rules of rvalue(&&) lvalue(&) reference binding in templates with regard to reference collapsing?
问题
考虑以下情况:
int i{};
int& ir{i};
class A {
public:
int& i;
A(int&& pi) : i(pi) {}
};
A a1{i}; // 错误 // 情况 1
A a2{int(1)}; // 正确 // 情况 2
class B {
public:
int& i;
template<typename TYPE>
B(TYPE&& pi) : i(pi) {}
};
B b1{i}; // 正确 // 情况 3
B b2{int(1)}; // 正确 // 情况 4
int& ii{int(12)}; // 错误 // 情况 5
int& iii{std::move(int(12))}; // 错误 // 情况 6
template<typename TYPE>
class C {
public:
TYPE& i;
C(TYPE&& pi) : i(pi) {}
};
C c1{i}; // 错误 // 情况 7
C c2{int(1)}; // 正确 // 情况 8
C<int&&> c3{i}; // 正确 // 情况 9
C<int&&> c4{int(1)}; // 错误 // 情况 10
int&& iiii{ir}; // 错误 // 情况 11
右值不能绑定到左值,如果我理解正确,TYPE&& 将要么折叠成 TYPE&&,要么折叠成 TYPE&。
然而,我对这些情况特别是情况 4 的理解有些困难。如果情况 4 是正确的,那么意味着我们有一个引用 b2.i,它是从临时值(右值)初始化的。那么为什么情况 2、5、6 和 7 是错误的呢?而且情况 9 正确,这意味着 TYPE&& 是 int&&&&,我假设它会折叠成 int&,那么为什么 c3.i 是一个右值(int&&)可以从左值初始化,而情况 10 和 11 是错误的呢?
我希望有人能解释一下关于这个主题的一般规则,以及详细解释这些情况。
英文:
Consider these cases:
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
A rvalue can not be bound to lvalue and if my understanding is correct, TYPE&& would either collapse to TYPE&& or TYPE&.
How ever I am having a hard time understanding these cases, specially case 4. If case 4 is correct, then it means we have b2.i which is a reference, initialized from a temporary (rvalue). Then why case 2, 5, 6 and 7 are incorrect? And when case 9 is correct it mean the TYPE&& is int&&& which (I assume) collapses to int& , then how could c3.i which is an rvalue(int&&) be initialized from a lvalue, while case 10 and 11 are incorrect?
I wish some one could explain the general rules regarding this subject and also these cases in detail.
答案1
得分: 3
以下是翻译的内容:
这里有几个机制在起作用:lvalue/rvalue语义、引用折叠、转发引用和CTAD。我认为解释你列出的情况应该足以形成一个整体图像。
- rvalue引用
int&& pi无法绑定到lvaluei。 - rvalue引用
int&& pi可以绑定到rvalueint(1)。i(pi)中的pi指的是一个内存位置,因此它是一个lvalue,允许初始化a2.i。构造完成后,a2.i是对int(1)的悬空引用。 - 转发引用
TYPE&& pi可以绑定到lvaluei。TYPE是int&。b1.i指的是i。 - 转发引用
TYPE&& pi可以绑定到rvalueint(1)。TYPE是int。TYPE&& pi是一个rvalue引用,类似于情况2,b2.i是对int(1)的悬空引用。 - lvalue引用
int& ii无法绑定到rvalueint(12)。 - lvalue引用
int& iii无法绑定到rvaluestd::move(int(12))。
在情况7-10中,TYPE&& pi 不是转发引用,因为TYPE 是 class C 的模板参数,而不是构造函数的模板参数。在7-8中用于推断 TYPE 的CTAD并没有改变这一点:预期的是一个rvalue,只有这样才能将 TYPE 推断为 int,形成 int&& pi。
- 无法推断出
TYPE。 TYPE被推断为int。声明为int& i;的c2.i形成了对int(1)的悬空引用。TYPE明确为int&。TYPE&& pi折叠为int& pi。TYPE& i折叠为int& i。c3.i指的是i。TYPE&& pi折叠为int& pi,lvalue引用无法绑定到int(1)。- rvalue引用
int&& iiii无法绑定到lvalueir。
还有一个可能会使情况2和4更容易理解的额外情况:
int&& rvr = 1;
int&& rvr2 = rvr;
第一行是正确的,最后一行是错误的。int&& rvr 是一个rvalue引用,并延长了临时对象的生命周期。但是在最后一行中提到的 rvr 是一个lvalue,因此rvalue引用 int&& rvr2 无法绑定到它。
英文:
There are several mechanisms at play here: lvalue/rvalue semantics, reference collapsing, forwarding reference and CTAD. I think explaining the cases you listed should be sufficient to form a big picture.
- rvalue reference
int&& pican't be bound to lvaluei. - rvalue reference
int&& pican be bound to rvalueint(1).piini(pi)refers to a memory location so it is an lvalue, allowinga2.ito be initialized. After constructiona2.iis a dangling reference toint(1). - forwarding reference
TYPE&& pican be bound to lvaluei.TYPEisint&.b1.irefers toi. - forwarding reference
TYPE&& pican be bound to rvalueint(1).TYPEisint.TYPE&& piis an rvalue reference, similar to case 2b2.iis a dangling reference toint(1). - lvalue reference
int& iican not be bound to rvalueint(12). - lvalue reference
int& iiican not be bound to rvaluestd::move(int(12)).
In cases 7-10 TYPE&& pi is not a forwarding reference because TYPE is a template parameter of class C, not of constructor. CTAD, used in 7-8 to deduce TYPE, doesn't change that: an rvalue is expected, only then TYPE can be deduced to int, forming int&& pi.
TYPEcan't be deduced.TYPEis deduced to beint.c2.i, declared asint& i;, forms a dangling reference toint(1).TYPEis explicitlyint&.TYPE&& piis collapsed toint& pi.TYPE& iis collapsed toint& i.c3.irefers toi.TYPE&& piis collapsed toint& pi, lvalue reference can't be bound toint(1).- rvalue reference
int&& iiiican't be bound to lvalueir.
And a bonus case that might make cases 2 and 4 easier to understand:
int&& rvr = 1;
int&& rvr2 = rvr;
The first line is correct, the last is an error. int&& rvr is an rvalue reference and extends lifetime of a temporary. But rvr mentioned in the last line is an lvalue, so rvalue reference int&& rvr2 can't be bound to it.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论