CS 70

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

  • Goat speaking

    Uh, I don't think it's a huge surprise because we've already seen it.

  • Cat speaking

    Yes—our assignment operator had to return a reference.

  • Hedgehog speaking

    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.

  • Horse speaking

    Hay, yeah—I saw an example in the current homework, too: operator<< returns a std::ostream& for some mysterious reason.

  • LHS Cow speaking

    Indeed. But now we're going to think about that code more deeply,

  • RHS Cow speaking

    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!

For our first example, can we say ++maxVal(x,y); ?

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.

  • Bjarne speaking

    But you can call a non-const member function on a temporary object.

  • LHS Cow speaking

    Yes, that's true. So we could say Cow{}.feed(); to create a temporary (default) Cow, feed it, and throw it away.

  • Cat speaking

    This seems a bit inconsistent. We can't modify temporary objects, except for the times when we can??

  • Bjarne speaking

    Err… It's part of our heritage…?

In the second code sample, can we say ++maxRef(x,y); ?

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