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-
constmember 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.)