CS 70

Overloading

  • Pig speaking

    I'm not sure what overloading is, but I hope it's making the machine do so much MORE than it should that smoke comes out!!!

  • LHS Cow speaking

    Not quite.

  • Hedgehog speaking

    I'm worried that smoke will come out of my ears!

  • LHS Cow speaking

    It'll be fine.

Overloading is the idea that we can have more than one function with the same name.

  • Hedgehog speaking

    What!! That seems super confusing! How would it know which one to use?!

When we define multiple functions with the same name, C++ uses the types of the arguments (and only the types) to figure out which function to call.

So, for example, if we had these two functions:

int combine(double x, double y) {
    return (x+y)/2;
}

and

std::string combine(std::string x, std::string y) {
    x.append(" & ");
    x.append(y);
    return x;
}

We could write

cout << combine(3.142, 2.718) << endl
cout << combine("Chalk", "Cheese") << endl

and C++ will call the right function!

  • Dog speaking

    Whoa, that's cool. It's like, “Go fetch the right function!”.

  • Goat speaking

    Okay, sure, but why? Why not just have distinct names, like combineDoubles() and combineStrings()?

  • LHS Cow speaking

    That's what C would do. And lots of other languages, too.

  • Duck speaking

    Those longer names do look a bit clumsy.

  • LHS Cow speaking

    Indeed. And allowing multiple functions with the same name avoids issues with not being able to use a name because someone else has already used it for some other, totally unrelated, thing.

  • Horse speaking

    Hold your horses! What if I said combine(1, 2), which doesn't match either of those, and I hadn't written a combine() for integers?

  • LHS Cow speaking

    If there isn't a perfect match, C++ will perform promotions or conversions to try to find a suitable function.

Rules for Resolving Overloading

C++ finds the right function using the number of arguments and their types.

  • If there is a perfect match, it's found the right function and is done.
  • Otherwise, it produces a list of candidates based on the conversions/promotions it could perform.
  • If, out of that list of candidates, there is an overloaded function that is
    • Better than all the others for some arguments (i.e., fewer conversions); and,
    • No worse than any of the others for the remaining arguments, then
      • That function is the one that is used.
  • Otherwise, the best choice is still ambiguous, and the compiler throws an error.

In our example, if we wrote combine(1,2), there is only one candidate that matches—the combine() for doubles—and so it wins.

  • Hedgehog speaking

    Will you test us on these rules?

  • Goat speaking

    Meh. That's my question, Hedgehog.

  • LHS Cow speaking

    We expect you to understand the general idea, but we won't give you some complex case and have you work out candidates and which one wins.

  • Hedgehog speaking

    Oh, thank goodness!

Overloading Operators

The cool thing is, not only can we overload functions, we can also introduce new incarnations of C++'s existing operators, such as +, /, =, ++ and *.

  • Goat speaking

    We've been doing that. We already did operator= and operator<<.

  • LHS Cow speaking

    Yes, but you may not really have thought about what was going on until now.

You've written lines like

cout << 42 << 'x' << 3.14;

but there's actually a bunch of work being done “behind the scenes” to make that simple print command work. Now, with everything you've learned in today's lesson, we can actually break it down.

That code is equivalent to

((cout << 42) << 'x') << 3.14;

which is, in turn, equivalent to

{
    std::ostream& _temp1 = cout << 42;
    std::ostream& _temp2 = _temp1 << 'x';
    _temp2 << 3.14;
}

By convention, all these << operators return a reference to their left-hand argument, so our code is also equivalent to saying

cout << 42;         // calls operator<<(std::ostream, int)
cout << 'x';        // calls operator<<(std::ostream, char)
cout << 3.14;       // calls operator<<(std::ostream, double)

Now you can see how our (apparently) simple “print three values” code was actually calling three different functions.

  • Dog speaking

    Cool!

  • LHS Cow speaking

    What's nice about this mechanism is that it's extensible. For example, if we define operator<< for our own types, we can print them just as easily as we can with C++'s built-in types.

That extensibility is a key principle. In C++ you can create your own classes that behave exactly like types that are built into the language. You can even overload the subscript operator, [], and the function-call operator, (), to make objects that are array-like or function-like.

If I want to say, int x = goodNumber(); and double y = goodNumber();, could overloading be used to call a different function for each case?

If we have a function that takes a reference, should the design of C++'s system for overloading allow us to make two overloaded versions of the function, one for when the argument is a reference something const and one for when it's a something that isn't const?

(When logged in, completion status appears here.)