Why this matters
Real functions are called in several ways. Sometimes the caller gives every detail, and sometimes it only gives the usual ones. A function that prints a line of characters might be called with no arguments for the default line, with a length only, or with both a length and a character. Writing a separate function for each case would make your code longer and harder to keep consistent.
C++ gives you two tools for this. Default arguments let a parameter have a value the caller may leave out. Overloading lets several functions share one name, as long as their parameter lists are different. Both tools appear in almost every C++ library, and both produce compiler errors that confuse beginners. This lesson teaches the rules, shows what the compiler really does, and shows how to read its messages.
The idea: one name, several ways to ask
Start from something you already do. When you say "open" you might mean open a door, open a file or open a bank account. The word stays the same, but the thing being opened is different, and the listener works out the meaning from the object you name. A human does this without thinking.
Overloading is the same idea for the compiler. You give two functions the same name, and the compiler picks the right one by looking at the arguments in the call. The arguments are the clue, just as the object is the clue in "open the door."
Default arguments answer a different question: what should happen when the caller says nothing about a detail? A default is a fallback value written into the parameter list. If the caller leaves that argument out, the fallback is used.
A useful way to choose between the two:
- Use a default when the work is the same and only a detail changes, such as the number of dashes in a line.
- Use overloading when the inputs differ in kind, and the work differs too, such as adding two whole numbers versus adding two decimals.
Default arguments
A default argument is a value that a parameter takes when the caller does not supply one. You write it with an equals sign in the parameter list.
Here is a function that greets a person. The greeting parameter has a default of "Hello".
#include <iostream>
#include <string>
using namespace std;
void greet(string name, string greeting = "Hello") {
cout << greeting << ", " << name << "!" << endl;
}With that definition, the caller can write greet("Anisha") or greet("Ravi", "Welcome"). The first call leaves out the second argument, so greeting takes its default. The second call supplies both, so the default is ignored.
The program below puts the function to work. Because the prototype carries the default, the definition below main does not repeat it. Keep that rule in mind; the next section explains why.
Press Run. No input is needed. The output is:
Hello, Anisha!
Welcome, Ravi!Trace the first call, greet("Anisha"), in full.
- The compiler sees one argument for two parameters. It matches
"Anisha"tonameby position. - The second parameter
greetinghas no argument, so the compiler uses the default from the prototype, which is"Hello". - The function receives
nameas"Anisha"andgreetingas"Hello". - The body prints
Hello, Anisha!and the newline fromendl.
The second call, greet("Ravi", "Welcome"), has two arguments, so both parameters take the values that were passed. The default is not used.
The rule for placement
Defaults must sit at the end of the parameter list. Once one parameter has a default, every parameter after it needs one too. The reason is that the compiler matches arguments by position. If the first parameter had a default and the second did not, a call with one argument would be ambiguous: is that argument for the first parameter, or for the second?
void f(int a = 1, int b); // error: b has no default but follows a, which has one
void f(int a, int b = 1); // fine: the defaulted parameter is lastThe compiler gives this message for the broken line:
error: default argument missing for parameter 2 of 'void f(int, int)'The message names parameter 2, which is b. It tells you the problem is a missing default on the second parameter, not an unexpected extra value.
Where the default goes
A default may be written in the prototype or in the definition, but never in both. Writing it twice is an error, because the compiler then sees two different rules for the same function. The usual practice is to put the default in the prototype, which is the part the caller reads.
void greet(string name, string greeting = "Hello"); // prototype: default here
void greet(string name, string greeting) { /* ... */ } // definition: no defaultIf you put the default in both places, GCC reports:
error: default argument given for parameter 2 of 'void greet(std::string, std::string)' [-fpermissive]The [-fpermissive] tag means the compiler can accept the code if you tell it to be lenient, but you should fix the code instead of relying on that setting. Remove the second default.
A default that is a computed value
Defaults must be constant values or expressions the compiler can fill in when it reads the call. They cannot depend on another parameter. This is a sign of the rule:
void printRow(int width, int height = width); // error: width is not in scope hereIf you need a default that depends on another value, use overloading instead. You will see an example below.
Worked example: a line of characters
This example shows a default for each of two parameters. The function prints one row of characters. It has a count and a character to repeat, and both have defaults.
Press Run. No input is needed. The output is:
----------
-----
*****Trace the three calls.
line()has no arguments. Both parameters take their defaults:nis 10 andcis'-'. The loop prints 10 dashes, thenendlmoves to a new line.line(5)gives one argument, which matches the first parametern. Sonis 5. The second parameterctakes its default,'-'. The loop prints 5 dashes.line(5, '*')gives both arguments.nis 5 andcis'*', so five stars are printed.
Look at the second call again. The argument 5 went to n, not c, because arguments fill parameters from the left. To change only the second parameter, you must supply the first one as well. C++ has no way to skip a middle parameter. That is one of the reasons defaults belong at the end of the list.
Choosing defaults that are safe
A default should be a value that works for most callers. Here 10 dashes is a sensible width, and '-' is a sensible character. If a caller passes 0, the loop does not run and nothing is printed, which is a correct result for "zero characters". If a caller passes a negative count, the loop does not run either, because i < n is false from the start. That is a choice you should write down, because the function does not warn the caller.
Function overloading
Overloading means writing several functions with the same name in the same scope, as long as their parameter lists differ. A parameter list is the number of parameters and their types, in order. Overloading lets one name serve several kinds of input.
Here is a set of three add functions. Read the three signatures before you look at the output.
Press Run. No input is needed. The output is:
5
6
6Trace each call.
add(2, 3)passes twointarguments. Theint, intversion matches exactly, so it runs. It returns 5.add(2.5, 3.5)passes twodoublearguments. Thedouble, doubleversion matches exactly. It returns 6.0.coutprints adoublewithout trailing zeros, so the output is6.add(1, 2, 3)passes three arguments. Only the three-parameter version has three parameters, so it is the one chosen. It returns 6.
The compiler decides at compile time, before the program runs. It looks at the number and types of the arguments and finds the one matching overload. Nothing is decided at run time. That is why the cost of overloading is zero when the program runs.
Three overloads of add side by side, with arrows from the call add(2.5, 3.5) to the double version
You already use overloading
You have used overloading since the start of this course, even if you did not notice it. The << operator in cout << value is one name with many versions: one for int, one for double, one for string and so on. Each version prints its own kind of value. Your own functions can work the same way. A family of functions that share one name is easy to learn, because you only remember the action, not a different name for each type.
Overloads may differ by count or by type, but not by return type
Functions can be overloaded by the number of parameters, the types of parameters, or both. They cannot be overloaded by the return type alone:
int value();
double value(); // error: same parameters, only the return type differsGCC reports:
error: ambiguating new declaration of 'double value()'The reason is that a call such as value(); gives the compiler no clue about which version you want. The call is written with no arguments, so the compiler has nothing to match except the parameter lists. Two versions with the same empty list both match, so the compiler refuses to choose. The return type is only used after the choice is made.
Overloads and the types of arguments
When the exact types match, the choice is easy. Many calls do not match exactly, so the compiler tries conversions. A promotion is a small, safe conversion, such as turning a char into an int. A conversion is any other allowed change, such as turning an int into a double. The compiler prefers promotions to conversions, and it prefers an exact match to both.
Press Run. No input is needed. The output is:
int: 7
double: 2.5
string: hi
int: 65The last call is the interesting one. 'A' is a char. Its code in the character table is 65. The compiler can turn a char into an int by a promotion, or into a double by a conversion. It takes the promotion, so the int version runs and prints 65, not A. Interviewers like this example because it shows that the rule is a ranking, not a guess.
The ranking the compiler uses
When no version matches exactly, the compiler ranks the candidates. It looks at each argument and asks how much that argument must change to fit each version. Here is the order, from best to worst, for one argument.
| Match kind | What changes | Example | Rank |
|---|---|---|---|
| Exact match | Nothing | an int passed to an int parameter | Best |
| Promotion | A small, safe widening | char to int, float to double | Second |
| Conversion | A standard change between types | int to double, double to int | Third |
| No match | Nothing allowed | string passed to an int parameter | Not a candidate |
The compiler then compares the candidates argument by argument. A candidate wins when it is at least as good as every other candidate for every argument, and strictly better for at least one. If one candidate is best for some arguments and another is best for the rest, neither wins, and the call is ambiguous. That is why maxOf(7, 2.5) from the practice list is an error. For the first argument the int version is an exact match, and for the second the double version is an exact match. Each version wins one argument, so the compiler reports an ambiguous call.
Use the table to predict a call before you compile it. Look at each argument, find its match kind for each candidate, and see whether one candidate wins every argument. Then run the program to check.
When the choice is ambiguous
Sometimes two overloads are equally good, and the compiler cannot choose. This is an error called ambiguity. Look at this code:
void show(int x) { }
void show(double x) { }
int main() {
show(10L); // 10L is a long
return 0;
}A long can be converted to an int or to a double, and neither conversion is better than the other. GCC reports:
error: call of overloaded 'show(long int)' is ambiguousWhen you meet this message, look at the argument type named in the error, then find the candidates. The fix is to match the argument exactly, for example by writing show(10) for an int, show(10.0) for a double, or by casting the argument to the type you want with static_cast<int>(10L). A cast is a written instruction that changes a value's type. You will learn more about casts in the pointers module.
Defaults and overloads together: a warning
Defaults and overloads can make a call ambiguous when they overlap. Suppose you write one function with one parameter, and another with two parameters where the second has a default:
void f(int a) { }
void f(int a, int b = 5) { }
int main() {
f(1); // both versions match
return 0;
}The call f(1) matches the first version exactly. It also matches the second version, because b takes its default. GCC reports:
error: call of overloaded 'f(int)' is ambiguousThe safe rule is to pick one of the two designs. Use a default for the extra parameter, or write a separate overload, but not both for the same shape of call.
Adding a default to an overload is not the same as a new overload
A common mistake is to think that giving a default makes a second version of a function. It does not. The following two functions have the same parameter types, so they are a redefinition:
int add(int a, int b) {
return a + b;
}
int add(int a, int b = 0) { // same parameter types: this is a redefinition
return a + b;
}GCC reports:
error: redefinition of 'int add(int, int)'The default does not change the parameter list that the compiler uses to tell two functions apart. If you want the one-argument form, write a single function with a default, and remove the other.
Defaults and overloads together
Defaults and overloads often appear in the same program. The next example has one print function with a default argument for the line ending, and a second print for numbers. Read the calls first and predict which version runs for each one.
Press Run. No input is needed. The output is:
Hello
Same line done
number 42Trace the four calls.
print("Hello")has one argument, which is text. Only the text version fits, and itsendparameter takes the default newline. It printsHelloand then a newline.print("Same line", " ")passes the text and a space forend. It printsSame lineand no newline, so the next output continues on the same line.print("done")uses the default newline again. It printsdoneand a newline, which gives the lineSame line done.print(42)has anintargument. The number version is an exact match. The text version does not fit, because anintcannot be turned into astringwith no help from you. So the number version runs and printsnumber 42.
The rule in this example is that a default belongs to one function. Step 1 works without a second version of print, because the default covers the missing argument. Step 4 works because the second version has a different parameter type. Neither feature had to be told about the other, and the compiler sorted out each call.
Defaults versus overloading: choosing a design
Here is the same task written two ways. The task is computing a price with an optional quantity and an optional tax rate. Both versions work, and the difference is in how the calls read.
To run it, type 50 4 in the Input box, then press Run.
Input: 50 4
Expected output:
50
200
236Trace the three calls, where price is 50.0 and quantity is 4.
totalCost(price)leaves out both optional arguments.quantityis 1 andtaxRateis 0.0. The result is 50 × 1 × 1.0, which is 50.totalCost(price, quantity)leaves out only the tax.taxRateis 0.0. The result is 50 × 4 × 1.0, which is 200.totalCost(price, quantity, 0.18)gives all three. The result is 50 × 4 × 1.18, which is 236 in exact arithmetic. The binary form of 1.18 is not exact, so the stored value is a little off, butcoutshows six significant digits, so the output reads236.
A single function with defaults replaces three functions here. A design with overloads would need three names or three signatures, and the logic would be copied. Use defaults when the same formula applies and only the inputs are optional.
The next example shows the other choice, where overloads are the better design. Each overload does a different job for a different kind of input.
Overloaded functions that share work
Overloads often call each other. One version does the real work, and the others convert their input and pass it on. Here is a maxOf family. The two-number version does the comparison. The three-number version uses the two-number version twice.
int maxOf(int a, int b) {
return a > b ? a : b;
}
double maxOf(double a, double b) {
return a > b ? a : b;
}
int maxOf(int a, int b, int c) {
return maxOf(maxOf(a, b), c);
}The call maxOf(maxOf(a, b), c) is an example of overload resolution inside the body. The inner call has two int arguments, so it picks the int, int version. The outer call also has two int arguments, one of them the result of the inner call. Both are int, so the same version runs again.
The ? : operator in return a > b ? a : b; is a short form of an if that picks one of two values. You may already know it. If not, read it as "if a > b then a, else b."
What happens inside
Overloading is handled at compile time. The compiler gives each overload a different internal name that encodes its parameter types. The internal name for int add(int, int) is not the same as the name for double add(double, double). The linker then joins each call to the right internal name. This process is called name mangling. You never type the mangled names, but they are why overloading costs nothing at run time: each call jumps straight to one function.
Default arguments are also handled at compile time. The compiler copies the default value into the call. If you write line(), the compiled code is the same as writing line(10, '-'). The function itself does not check whether it received a default or a real argument.
Designing overloads that do not surprise
An overload is a promise that every version does the same kind of job. Keep the meaning the same across the family. add(int, int) and add(double, double) both add, so a reader who knows one version can guess the other. If show(int) prints a number and show(string) writes a file, the shared name lies, and the reader will be misled. Use two names for two actions.
Keep the parameters in the same order across the family. If one version takes (name, age) and another takes (age, name), a caller who mixes them up compiles without error and gets wrong results. Errors of this kind are hard to find, because nothing fails.
Be careful with a bool parameter that switches behaviour. A call such as report(true) says nothing to the reader. Two functions with clear names, such as printPlain and printFancy, read better and are easier to change later.
A default should be the value most callers want. If nine out of ten calls use a width of 80, make 80 the default, and let the tenth caller pass its own width. Also remember that a default expression is evaluated at each call that needs it. A constant such as "Hello" looks the same every time, but a default that calls a function runs that function on every call that omits the argument.
Before you ship an overload set, read each call site once, and ask whether a reader could guess the meaning from the name and the arguments. If the answer is no, rename something.
Common mistakes
- Putting a default before a parameter without one. The compiler reports
error: default argument missing for parameter 2 of 'void f(int, int)'. Move the defaulted parameter to the end of the list. - Repeating the default in the prototype and the definition. The compiler reports
error: default argument given for parameter 2. Keep it in the prototype only. - Overloading by return type alone. The compiler reports
error: ambiguating new declaration. Change the parameter list, or give the functions different names. - Mixing a default with an overload that has the same shape. A call such as
f(1)can match both, and the compiler reportserror: call of overloaded 'f(int)' is ambiguous. Choose one design. - Expecting
add(2, 3.5)to choose the double version. The argument2is anint, and3.5is adouble. Neither overload matches both arguments exactly. The compiler may find that one conversion is better, or it may report ambiguity. Write the argument types explicitly, such as2.0, when you want the decimal version. - Passing a
longwhere anintordoubleoverload is expected. The same ambiguity error appears. Check the types of your arguments.
Debugging walkthrough: the wrong overload runs
Suppose you add a new overload, and a call that used to print one result now prints another. The steps below find the cause.
- Find the call. Note the exact argument types. Remember that
5is anint,5.0is adouble,'5'is acharand5Lis along. - List the candidates. Look for every function with that name in scope. A function declared below the call is not a candidate, so check for a missing prototype that makes an older version visible.
- Rank the candidates. An exact match beats a promotion, and a promotion beats a conversion. If two candidates tie, the call is ambiguous and the compiler stops with an error. So if the program compiles, one candidate won.
- Add a print line. Put
cout << "in the double version" << endl;at the top of each version. Run the program. The line that prints tells you which version ran.
A real case: a student writes describe(65) and expects the char version to run, because 65 is the code for A. Step 1 says the argument is an int. Step 2 finds only the int version. The answer is that there was never a char version, so the int version ran. The fix is to write the overload they wanted, or to call describe('A').
Variations and edge cases
- Default arguments with a
bool. Aboolparameter with a defaultfalseis common for optional switches, such asvoid printList(bool reverse = false). - More than one default. A function can have several defaults, as long as they are at the end. The caller fills them from left to right.
- Calls with zero arguments. A function that has defaults for all its parameters can be called with no arguments at all.
line()in the earlier example is one. - Overloads in a class. The same rules apply to member functions of a class, which you meet in the object-oriented module.
maincannot be overloaded. There must be exactly onemain. Overloading it is an error, and interviewers sometimes ask this to check the rule.
Complexity and cost
Default arguments and overloading cost nothing when the program runs. The compiler resolves both at compile time, and each call goes straight to the chosen function. So the cost of totalCost(price) is the same as the cost of totalCost(price, 1, 0.0).
The complexity of your algorithm is not changed by overloading. If maxOf(a, b, c) calls maxOf(a, b) twice, it does a constant amount of work, so it is O(1). A three-argument maxOf over a list of n values is O(n), because it does one constant step per value.
Reading an overload set in a code review
When you review a set of overloads, list each version's parameter types in one table, then check two things. First, do any two versions accept the same kinds of argument? If yes, a call with those arguments will be ambiguous or will silently pick the one with the better conversion. Second, does every version do the same job? A reviewer who sees one version that behaves differently should ask for a new name instead of a new overload.
Defaults get the same treatment. Look at each function with defaults, and ask whether a call with the defaults can match another overload of the same name. The f(int) and f(int, int = 5) pair from earlier is the typical case, and the compiler catches it only when the ambiguous call is written.
Interview questions on this topic
Can two functions have the same name and different return types? Only if their parameter lists differ too. A return type alone is not enough, because the call value(); does not say which type it wants.
If f(int x = 5) exists, can you also write f()? Not as a second function. The one function already accepts a call with no arguments, so a second f() would be a redefinition or an ambiguous call. Explain that a default lets one function serve two call shapes.
Why does C++ allow overloading, but Python does not? Python checks types when the program runs, and it allows one function with optional arguments to handle many cases. C++ checks types at compile time, and it needs a separate signature for each set of parameter types. Overloading is C++'s way to write those separate versions under one name.
Real-world uses
- Libraries. The standard library's output and math functions use overloads so one name works for many types.
abshas versions forint,longanddouble. - Formatting helpers. A function that prints a title may take a default width, so most callers pass only the text.
- Conversion helpers. A
toStringoverload may exist for each type a program prints, so the caller does not need to know which conversion to use.
Practice
- Write
void line(int n = 10, char c = '-')that prints a row ofncopies ofc. Call it with zero, one and two arguments. Predict the output first.
Solution
#include <iostream>
using namespace std;
void line(int n = 10, char c = '-') {
for (int i = 0; i < n; i++) {
cout << c;
}
cout << endl;
}
int main() {
line();
line(3);
line(4, '=');
return 0;
}Output: ten dashes, then three dashes, then four equals signs.
- Write three overloaded
maxOffunctions: two ints, two doubles, and three ints. Call each one frommainwith suitable values.
Solution
#include <iostream>
using namespace std;
int maxOf(int a, int b) {
return a > b ? a : b;
}
double maxOf(double a, double b) {
return a > b ? a : b;
}
int maxOf(int a, int b, int c) {
return maxOf(maxOf(a, b), c);
}
int main() {
cout << maxOf(3, 8) << endl;
cout << maxOf(2.5, 1.5) << endl;
cout << maxOf(4, 9, 6) << endl;
return 0;
}Output: 8, 2.5, 9.
- Add
int add(int a, int b = 0)next toint add(int a, int b)and compile. Explain the error in your own words.
Solution
The two functions have the same parameter types, int, int. A default value is not part of the signature, so the compiler sees two definitions of one function. GCC reports redefinition of 'int add(int, int)'. Keep only the version with the default, and call it as add(5) or add(5, 2).
- Write
double area(double w, double h = 1.0)that returnsw * h. Call it witharea(5)and witharea(5, 3). Predict both outputs.
Solution
#include <iostream>
using namespace std;
double area(double w, double h = 1.0) {
return w * h;
}
int main() {
cout << area(5) << endl;
cout << area(5, 3) << endl;
return 0;
}Output: 5 and then 15. The first call uses h = 1.0.
- Explain why
void f(int a = 1, int b)does not compile, and rewrite it so it does.
Solution
a has a default, but b after it does not. The compiler matches arguments by position, so a single argument would be ambiguous. Rewrite it with the defaulted parameter last: void f(int a, int b = 1).
- Write
void show(int x)andvoid show(double x). Callshow('A'),show(2.5f)andshow(10). Predict which version runs in each case. Check your answer by adding a print line to each version.
Solution
'A' is a char, which is promoted to int, so show(int) runs. 2.5f is a float, which is converted to double, and show(double) runs. 10 is an exact int match, so show(int) runs. To check, give each version a print line that names it, and call the three arguments in order.
- Write
int power(int base, int exp = 2)that returnsbaseraised toexp, using a loop. Call it withpower(3)and withpower(2, 5). The outputs should be9and32.
Solution
#include <iostream>
using namespace std;
int power(int base, int exp = 2) {
int result = 1;
for (int i = 0; i < exp; i++) {
result = result * base;
}
return result;
}
int main() {
cout << power(3) << endl;
cout << power(2, 5) << endl;
return 0;
}The default exp = 2 means power(3) computes the square of 3.
- Write two functions with the same name,
report. One takes astringmessage. The other takes astringmessage and anintlevel with a default of 1. Explain what happens when you callreport("hi").
Solution
The call report("hi") matches the one-parameter version exactly. The two-parameter version also matches, because its second parameter has a default. The compiler reports an ambiguous call. Fix it by removing one of the two functions, and give the other a default if you want one shape to serve both calls.
- Write
void printRow(int width, int height = width);and explain why it fails. Then rewrite it as two overloads that do the same job.
Solution
A default must be a value the compiler can fill in at the call, and it cannot use another parameter. width is not in scope in the default. Rewrite it as two functions:
void printRow(int width) {
printRow(width, width);
}
void printRow(int width, int height) {
// print a rectangle of size width by height
}- A program calls
maxOf(7, 2.5), wheremaxOfhas anint, intversion and adouble, doubleversion. Predict whether the call compiles. Then explain what changes if you writemaxOf(7.0, 2.5)instead.
Solution
The first argument is an int, and the second is a double. Neither overload matches both exactly. The double, double version needs one conversion (int to double), and the int, int version needs one conversion (double to int). Both need one conversion, so the compiler reports an ambiguous call. Writing 7.0 makes both arguments double, so maxOf(double, double) is an exact match and the call compiles.
- Write
int sumTo(int n = 10)that returns 1 + 2 + ... + n using a loop. Call it with no arguments, and then with 4. Print both results. The outputs should be55and10.
Solution
#include <iostream>
using namespace std;
int sumTo(int n = 10) {
int total = 0;
for (int i = 1; i <= n; i++) {
total = total + i;
}
return total;
}
int main() {
cout << sumTo() << endl;
cout << sumTo(4) << endl;
return 0;
}The first call uses n = 10, which gives 55. The second gives 1 + 2 + 3 + 4 = 10.
What you now know
- A default argument gives a parameter a value when the caller leaves it out. Defaults go at the end of the parameter list.
- Put a default in the prototype, not in the definition, and never in both places.
- Overloading lets one name serve several parameter lists. The compiler chooses by the number and types of the arguments.
- Overloads cannot differ only by return type.
- An exact match beats a promotion, and a promotion beats a conversion. A tie is an ambiguous call, which is a compile error.
- A default and an overload with the same shape can make a call ambiguous. Choose one design per shape.
- Both features cost nothing at run time, because the compiler settles the choice before the program runs.