Kodewick
Lesson 9 of 30Contents

Overloading and default arguments

Lesson 9 of 30 · Part 2 · Control flow and functions · about 15 minutes

In C, two functions can't have the same name. In C++ they can: as long as their parameters differ, several functions can share one name. This is called overloading. When you call it, the compiler looks at the arguments you gave and picks the one that fits.

main.cpp
1#include <iostream>2 3int bigger(int a, int b) {4    if (a > b) {5        return a;6    }7    return b;8}9 10double bigger(double a, double b) {11    if (a > b) {12        return a;13    }14    return b;15}16 17int main() {18    std::cout << bigger(3, 8) << '\n';19    std::cout << bigger(2.5, 1.5) << '\n';20    return 0;21}
Output
8
2.5

No need for names like bigger_int and bigger_double: whoever uses it just remembers "bigger gives the bigger one".

Same name, different parameters

The parameters can differ in number or in type:

main.cpp
1#include <iostream>2 3int area(int side) {4    return side * side;5}6 7int area(int width, int height) {8    return width * height;9}10 11double area(double radius) {12    return 3.14159 * radius * radius;13}14 15int main() {16    std::cout << "Square: " << area(4) << '\n';17    std::cout << "Rectangle: " << area(4, 6) << '\n';18    std::cout << "Circle: " << area(2.0) << '\n';19    return 0;20}
Output
Square: 16
Rectangle: 24
Circle: 12.5664

Three areas: one int parameter, two int parameters, one double parameter. area(4) and area(2.0) both give one argument and are told apart by type: 4 is an int, 2.0 a double.

How the compiler picks

The compiler first looks for an exact type match. If there is none, it looks for one that fits after a small "promotion": a char or a bool can be used as an int, for example. Only after that does it consider other conversions.

main.cpp
1#include <iostream>2 3void show(int n) {4    std::cout << "int: " << n << '\n';5}6 7void show(double d) {8    std::cout << "double: " << d << '\n';9}10 11int main() {12    show(7);13    show(7.5);14    show('A');15    return 0;16}
Output
int: 7
double: 7.5
int: 65

'A' is a char, an exact match for neither show. Promoting a char to an int is the natural step, so show(int) is picked, and it shows 65, the code of 'A'.

Guess first

What does this program show?

1#include <iostream>2 3void show(int n) {4    std::cout << "int\n";5}6 7void show(double d) {8    std::cout << "double\n";9}10 11int main() {12    show(10 / 4);13    return 0;14}

A different return type alone isn't overloading. int read_value() and double read_value() have the same parameters, so a call to read_value() could never tell them apart, and the second definition is a compile error.

When it can't pick

Sometimes two functions fit equally well. The compiler won't guess for you; it stops with an error:

main.cpp
1#include <iostream>2 3double cost(int count, double price) {4    return count * price;5}6 7double cost(double weight, int price) {8    return weight * price;9}10 11int main() {12    std::cout << cost(3, 5) << '\n';13    return 0;14}
Output
main.cpp: In function 'int main()':
main.cpp:12:22: error: call of overloaded 'cost(int, int)' is ambiguous
   12 |     std::cout << cost(3, 5) << '\n';
      |                  ~~~~^~~~~~

call of overloaded 'cost(int, int)' is ambiguous. The first cost needs 5 converted to double, the second needs 3 converted to double: one conversion each, a tie. On your own computer, g++ also lists the two candidates (candidate) below the error.

The fix is to say what you mean: cost(3, 5.0) is the first one, cost(3.0, 5) the second. Better still, don't write two overloads this alike in the first place.

Default arguments

Some parameters have the same value most of the time. Give them a default value, and when a call leaves them out, the default is used:

main.cpp
1#include <iostream>2 3double final_price(double price, double discount = 0) {4    return price * (1 - discount);5}6 7int main() {8    std::cout << final_price(200) << '\n';9    std::cout << final_price(200, 0.25) << '\n';10    return 0;11}
Output
200
150

final_price(200) gives no discount, so it is 0; final_price(200, 0.25) gives one, so it is 0.25.

Several parameters can have defaults. A call fills them from left to right, and the ones not given get their defaults:

main.cpp
1#include <iostream>2 3void line(int width = 10, char ch = '-') {4    for (int i = 0; i < width; i++) {5        std::cout << ch;6    }7    std::cout << '\n';8}9 10int main() {11    line();12    line(5);13    line(5, '*');14    return 0;15}
Output
----------
-----
*****

In line(5), the 5 goes to the first parameter, width, and ch stays '-'. There is no way to skip width and give only ch.

Defaults go last

Once a parameter has a default, every parameter to its right needs one too. This is a compile error:

1void line(int width = 10, char ch) {2    // ...3}

g++ says default argument missing for parameter 2. The reason is the same as above: arguments fill parameters from the left, so in a call line('*'), the '*' could only go to width, and ch would never get a value. Put the parameters you can usually leave out at the end.

Note: If a function has a declaration (prototype), write the default in the declaration and not again in the definition; repeating it is a compile error too.

Overloads and defaults collide

A default argument lets one function be called with different numbers of arguments, so it can collide with another overload:

main.cpp
1#include <iostream>2 3int total(int a, int b) {4    return a + b;5}6 7int total(int a, int b, int c = 0) {8    return a + b + c;9}10 11int main() {12    std::cout << total(1, 2, 3) << '\n';13    std::cout << total(1, 2) << '\n';14    return 0;15}
Output
main.cpp: In function 'int main()':
main.cpp:13:23: error: call of overloaded 'total(int, int)' is ambiguous
   13 |     std::cout << total(1, 2) << '\n';
      |                  ~~~~~^~~~~~

Only the second total takes total(1, 2, 3), but both take total(1, 2), and once again the compiler can't pick. With its default, the second total already does the first one's job, so just delete the first.

If a default argument does the job, don't add another overload.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Average of two or three

Write two overloads of average: one takes two ints, the other three, and both return the average as a double. The program should show 7.5 and 6.

It should print
7.5
6
exercise1.cpp
1#include <iostream>2 3int main() {4    std::cout << average(5, 10) << '\n';5    std::cout << average(4, 6, 8) << '\n';6    return 0;7}
Output
Press Run or Ctrl+Enter
Exercise 2

The tip

Give the rate parameter of tip a default of 0.15, so that tip(100) works too. The program should show 15 and 20.

It should print
15
20
exercise2.cpp
1#include <iostream>2 3double tip(double bill, double rate) {4    return bill * rate;5}6 7int main() {8    std::cout << tip(100) << '\n';9    std::cout << tip(100, 0.2) << '\n';10    return 0;11}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: how many rings

This program doesn't compile: ring() doesn't know which function to call. Delete the extra function so that ring() rings twice and ring(3) three times.

It should print
RingRing
RingRingRing
exercise3.cpp
1#include <iostream>2 3void ring() {4    std::cout << "Ring\n";5}6 7void ring(int times = 2) {8    for (int i = 0; i < times; i++) {9        std::cout << "Ring";10    }11    std::cout << '\n';12}13 14int main() {15    ring();16    ring(3);17    return 0;18}
Output
Press Run or Ctrl+Enter

What you learned

  • Overloading: several functions share a name as long as their parameters differ in number or type; a different return type alone doesn't count.
  • The compiler picks by the arguments: an exact type match first, then a promotion (char → int), then other conversions.
  • When two fit equally well, it's the error call of overloaded ... is ambiguous; make the arguments clear, or don't write overloads that alike.
  • A default argument, double discount = 0, is used when a call leaves it out; arguments fill parameters from the left.
  • Parameters with defaults go last; the default goes in the declaration, not again in the definition.
  • If a default argument does the job, don't add another overload that could collide with it.