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—our assignment operator had 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 astd::ostream&
for some mysterious reason.Indeed. But now we're going to think about that code more deeply,
Rather than just doing a thing so we can concentrate on a different thing.
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.)