Functions Can Return References
We've now seen that whenever a function returns, it's either providing initialization for a named variable, or initializing a secret temporary variable.
So perhaps it isn't a huge surprise to know that we can also have a function return a reference to set up a reference variable (either an actual variable, or a secret temporary one).
Uh, I don't think it's a huge surprise because we've already seen it.
Yes—assignment operators have to return a reference.
I remember, too! We had to say
return *this;
to return the current object. It all seemed to be a bit odd, but I go along to get along.Hay, yeah—I saw an example in the current homework, too:
operator[]
returns a reference to an element of an array.Indeed. But now we're going to think about that code more deeply and think about what's actually going on.
Let's consider these two pieces of code and what they do.
int maxVal(int lhs, int rhs) {
if (lhs > rhs) {
return lhs;
} else {
return rhs;
}
}
int main() {
int x = 41;
int y = 7;
int z = maxVal(x,y);
++z;
cout << "main's x is at address " << &x
<< ", and holds " << x << endl;
cout << "main's y is at address " << &y
<< ", and holds " << y << endl;
cout << "main's z is at address " << &z
<< ", and holds " << z << endl;
return 0;
}
This code prints
main's x is at address s1, and holds 41
main's y is at address s2, and holds 7
main's z is at address s3, and holds 42
Notice that x
and z
refer to different locations in memory and have different values.
Experiment!
int& maxRef(int& lhs, int& rhs) {
if (lhs > rhs) {
return lhs;
} else {
return rhs;
}
}
int main() {
int x = 41;
int y = 7;
int& z = maxRef(x,y);
++z;
cout << "main's x is at address " << &x
<< ", and holds " << x << endl;
cout << "main's y is at address " << &y
<< ", and holds " << y << endl;
cout << "main's z is at address " << &z
<< ", and holds " << z << endl;
return 0;
}
This code prints
main's x is at address s1, and holds 42
main's y is at address s2, and holds 7
main's z is at address s1, and holds 42
Notice that here x
and z
refer to the same location in memory. When we ran ++z
it had the same effect as saying ++x
(but if y
had been larger, we would have modified y
).
Experiment!
It would cause a compiler error. clang++
would say
maxval.cpp:18:5: error: expression is not assignable
++maxVal(x,y);
^ ~~~~~~~~~~~
and g++
would say
maxval.cpp: In function 'int main()':
maxval.cpp:18:13: error: lvalue required as increment operand
18 | ++maxVal(x,y);
| ~~~~~~^~~~~
The compilers complain because—usually—trying to modify a temporary value in this way is a mistake, so it's prohibited.
But you can call a non-
const
member function on a temporary object.Yes, that's true. So we could say
Cow{}.feed();
to create a temporary (default)Cow
, feed it, and throw it away.This seems a bit inconsistent. We can't modify temporary objects, except for the times when we can??
Err… It's part of our heritage…?
Writing
++maxRef(x,y);
is basically the same as writing
{
int& _temp1 = maxRef(x,y);
++_temp1;
}
This behavior is, essentially, what we already saw with z
. _temp1
is an alias for whichever of x
or y
holds the largest value, so in this case, we increment x
.
New Memory Diagram Rule
When you want to be clear about which thing a function is returning a reference to, then create a new reference, _retval
, just as the function returns. You'll immediately cross _retval
out once you get back to the calling function, but you might also choose to give it a new name, as we did with z
in the code above.
(When logged in, completion status appears here.)