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.
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.
int&& pi
can't be bound to lvalue i
.int&& pi
can be bound to rvalue int(1)
. pi
in i(pi)
refers to a memory location so it is an lvalue, allowing a2.i
to be initialized. After construction a2.i
is a dangling reference to int(1)
.TYPE&& pi
can be bound to lvalue i
. TYPE
is int&
. b1.i
refers to i
.TYPE&& pi
can be bound to rvalue int(1)
. TYPE
is int
. TYPE&& pi
is an rvalue reference, similar to case 2 b2.i
is a dangling reference to int(1)
.int& ii
can not be bound to rvalue int(12)
.int& iii
can not be bound to rvalue std::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
.
TYPE
can't be deduced.TYPE
is deduced to be int
. c2.i
, declared as int& i;
, forms a dangling reference to int(1)
.TYPE
is explicitly int&
. TYPE&& pi
is collapsed to int& pi
. TYPE& i
is collapsed to int& i
. c3.i
refers to i
.TYPE&& pi
is collapsed to int& pi
, lvalue reference can't be bound to int(1)
.int&& iiii
can't be bound to lvalue ir
.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.