Arbalest
Arbalest

Reputation: 1105

Deferencing a returned reference

Given:

int& foo(); // don't care what the reference is to
int intVal;

In the following two cases the right hand side is the same function call

int& intRef = foo();
intVal = foo();  // a reference is returned... a value is assigned.

In the second case how is the returned reference "converted" into a value?

Is it done by the assignment operator for the int?

Upvotes: 4

Views: 160

Answers (3)

Jonathan Wakely
Jonathan Wakely

Reputation: 171263

Assigning to intVal is an assignment-expression defined in 5.17 [exp.ass] in the standard. The grammar rules for an assignment-expression are quite complicated, depending on several other grammar rules, but basically you need a modifiable lvalue on the left hand side of the = operator, and a prvalue expression on the right hand side.

In the case of

intVal = foo();

the expression on the RHS is an lvalue of type int, so the built-in lvalue-to-rvalue conversion takes place ... this is barely a conversion, in that the value doesn't change and neither does the type (except that for fundamental types cv-qualifiers are removed, so if the lvalue is type const int the prvalue will be type int). [conv.lval] says

A glvalue (3.10) of a non-function, non-array type T can be converted to a prvalue. [...] If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T. [...] the value contained in the object indicated by the glvalue is the prvalue result.

So the prvalue has type int and the same value as foo() i.e. the same value as the variable the returned reference is bound to.

The rules of assignment expressions say:

In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand.

So the value of intVal will be replaced by the value of the prvalue. The rules continue:

If the left operand is not of class type, the expression is implicitly converted (Clause 4) to the cv-unqualified type of the left operand.

So because int is not a class type (and therefore has no overloaded operator= it just uses the built-in assignment operator) the assignment will convert the RHS to int, which is the type it already has in your case.

So the value of intVal gets set to the value of the prvalue, which we said is the value of the glvalue expression foo(), i.e. the value of the variable the reference is bound to.

Note that the lvalue-to-rvalue conversion is nothing to do with the RHS being a reference. The same thing happens here:

int val = 0;
intVal = val;

val is an lvalue of type int so it's converted to a prvalue of type int and the value of intVal is set to the value of that prvalue.

The rules are expressed in terms of an expression's "value category" (i.e. lvalue or rvalue) not whether it's a reference or not. Any "dereferencing" of a reference that's needed is done implicitly and invisibly by the compiler in order to implement the required behaviour.

Upvotes: 1

Joseph Mansfield
Joseph Mansfield

Reputation: 110658

foo is returning some reference to an object of type "int". We won't care about where that "int" came from and we'll just assume it exists.

The first line, int& intRef = foo(), creates intRef which also refers to exactly the same object of type "int" as is referenced by the return value of foo.

The second line, the value of intVal is replaced by the value of the object referred to by the returned reference.


In response to your comments:

You seem to be getting very confused between pointers and references. References are just like aliases for an object. Doing anything to a reference will actually affect the object it refers to.

There is no such thing as dereferencing a reference. You can only dereference pointers. Dereferencing is the act of using the unary * operator to get the object pointed at by a point. For example, if you have a int* p, you can do *p to get the object that it points at. This is dereferencing p.

The only time you can do * on a reference is if the object it refers to is a pointer (or if it overloads operator*). In your case, since foo returns an int&, we can't dereference it. The expression *foo() just won't compile. That's because the return value of foo has type "int" which is not a pointer and doesn't overload operator*.

For all intents and purposes, you can treat the reference returned from foo as simply being the object it refers to. Assigning this value to intVal is really no different to assigning x to intVal in the following code:

int intVal;
int x = 5;
intVal = x;

As I'm sure you understand, intVal is given the value of x. This is defined simply by the standard:

In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand.

No conversion needs to occur at all because both sides of the operator are the same type.

This is really no different to your situation. You just have:

intVal = some_ref_to_int;

Where some_ref_to_int is the expression foo(). The fact that it's a reference doesn't matter. intVal receives the value of the object that the reference denotes.

Upvotes: 3

AnT stands with Russia
AnT stands with Russia

Reputation: 320391

At the language level there's no such concept as "dereferencing a reference". A reference implements the concept of an lvalue. A variable and a reference are basically the same thing. The only difference between a variable and a reference is that the variable is bound to its location in storage automatically, by the compiler, while a reference is generally bound through user action at run time.

In your example, there's no conceptual difference between intRef and intVal. Both are lvalues of type int. And at the conceptual level both are accessed through the same mechanism. You can even think of all variables in your program as references, which were implicitly pre-bound for you by the compiler. This is basically what Bjarne Stroustrup means in TC++PL when he says (not verbatim) that one can think of references as just alternative names for existing variables.

The only moment when the difference between the two is perceptible is when you create these entities and initialize them. Initialization of a reference is an act of binding it to some location in storage. Initialization of a variable is an act of copying the initial value into the existing storage.

But once a reference is initialized, it acts as an ordinary variable: an act of reading/writing a reference is an act of reading/writing the storage location it is bound to. Taking the address of a reference evaluates to the address of the storage location it is bound to. And so on.

It is not a secret that in many cases a reference is implemented internally as a pointer in disguise, i.e. as an invisible pointer that is implicitly dereferenced for you every time you access it. In such cases (when it is really implemented through a pointer) the dereference is done, again, every time you access it. So, it is not the assignment operator that does it, as you ask in your question. It is the very fact that you mentioned the name of that reference in your code that causes the invisible pointer to get dereferenced.

However, an entity that implements "alternative name for existing variable" does not necessarily require storage for itself, i.e. in a compiled language it is not required to be represented by anything material, like a hidden pointer. This is why the language standard states in 8.3.2 that "It is unspecified whether or not a reference requires storage".

Upvotes: 10

Related Questions