I'm coming from a C# background and trying to learn a little C . I came across the following lines:
int x[3] = { 1, 2, 3 };
int*&& y = x;
int* z = y;
I know what pointers and arrays are and have some small understanding on lvalue and rvalue references. However I can't wrap my head around what int*&& y = x;
actually does.
It would read like it creates a pointer to an rvalue reference, is this correct? What would be the use case of something like that, e.g. what is going on in memory if we execute this?
CodePudding user response:
int*&& y = x;
declares y
to be a rvalue reference to pointer to int
and initializes it with x
. (Note that pointer to reference types (e.g. int&&*
) do not exist in C .)
Now the issue is that x
is an array of int
, not a pointer to int
. So the reference can't bind directly to x
. The types are not reference-compatible.
In such a situation (if the reference is either a rvalue reference or a const
lvalue reference) an (unnamed) temporary object of the referenced type is created and the reference binds to that object instead. In this case an int*
object is created and initialized with x
, which by array-to-pointer decay means that the int*
temporary object will be initialized to point to the first element of the array x
.
Temporary objects normally live only until the end of the (full-)expression in which they are created, but in this case, because a reference is immediately bound to it, so-called temporary lifetime extension applies and the temporary int*
object will live as long as the reference does (i.e. until the end of the reference's scope).
In other words it is (mostly) equivalent to
int* /*unnamed*/ = x;
int*&& y = /*unnamed*/;
where the comment /*unnamed*/
is supposed to represent the non-existing name of the temporary object.
When a reference's name is used in an expression (and this is completely independent of whether or not it is a lvalue or rvalue reference), it behaves exactly the same as if the object to which the reference is bound would have been named instead (but with the type of the reference with reference-qualifiers stripped which may e.g. differ by a const
).
In other words int* z = y;
behaves equivalently to int* z = /*unnamed*/;
. So z
is intialized from the temporary int*
object, which has a pointer value pointing to the first element of the x
array. For scalar types like int*
initialization is simply copying the value, so z
will also be initialized to point to the first element of x
.
The whole thing is needlessly convoluted. It is exactly equivalent to int* z = x;
. Using rvalue references usually only really makes sense as function parameters and some constructs where type deduction occurs. The important difference between lvalue and rvalue references is that they affect overload resolution differently and that they may be initialized with different value categories. There is also one special case in type deduction where rvalue references behave differently (as so-called forwarding references). Aside from that there is no difference between the different kinds of references.
what is going on in memory if we execute this?
That's mostly an implementation detail that shouldn't matter. You have an array that is stored somewhere depending on where you put these lines in your program. It is likely to be physically present somewhere in memory if the compiler doesn't figure it isn't needed. The reference y
, the temporary int*
which it points to and the z
pointer may or may not actually be physically present in some memory, but the compiler is likely to just reduce all of them directly to the array. In particular, on the language level, references are not object and do not have storage (e.g. they don't have a memory size or location). If the compiler needs some memory to implement them (e.g. as a pointer), then that is purely an implementation detail of the compiler.