Function Overloading and Default Arguments in C++
Function Overloading
Function overloading allows several functions with the same name but different parameters within the same scope. The compiler determines which function is called based on the number, type, and order of the arguments. This is called compile-time polymorphism or static polymorphism.
Overloading rules:
- Parameters must differ in number OR type OR order.
- Differing only in return type is NOT enough for overloading.
- const and non-const parameters are considered the same for overloading with pass by value (but different for pass by reference).
Name Mangling
The compiler changes the names of overloaded functions to make them unique in the symbol table. This process is called name mangling. Example: add(int, int) might become _Z3addii in GCC. This allows several functions with the same name to be linked together.
Default Arguments
Default arguments allow parameters to have a built-in value. If an argument is not provided at the call site, the default value is used. Rule: default parameters must go from right to left (no non-default parameters may follow a default one).
Code Example
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
// Overloading: different number of parameters
void display() {
cout << "No parameters" << endl;
}
void display(int x) {
cout << "Integer: " << x << endl;
}
void display(int x, int y) {
cout << "Two integers: " << x << ", " << y << endl;
}
// Overloading: different parameter types
void process(int x) {
cout << "Process int: " << x * 2 << endl;
}
void process(double x) {
cout << "Process double: " << x * 2.5 << endl;
}
void process(string x) {
cout << "Process string: " << x << "!" << endl;
}
// Overloading: different type order
void print(int a, double b) {
cout << "int-double: " << a << ", " << b << endl;
}
void print(double a, int b) {
cout << "double-int: " << a << ", " << b << endl;
}
// Default arguments
void greet(string name, string prefix = "Hello", string suffix = "!") {
cout << prefix << " " << name << suffix << endl;
}
// Defaults combined with overloading
double computeArea(double side) {
return side * side; // square
}
double computeArea(double length, double width = 0) {
if (width == 0) width = length;
return length * width; // rectangle
}
double computeArea(double base, double height, bool isTriangle) {
if (isTriangle) return 0.5 * base * height;
return base * height; // parallelogram
}
// Default arguments with const reference
void printData(const string& name, int age = 0,
const string& city = "Unknown") {
cout << "Name: " << name << ", Age: " << age
<< ", City: " << city << endl;
}
// Example: math functions with overloading
double compute(double a, double b, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return (b != 0) ? a / b : 0;
default: return 0;
}
}
int compute(int a, int b, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return (b != 0) ? a / b : 0;
default: return 0;
}
}
// Default arguments for a recursive function
void countdown(int n, string indent = "") {
if (n <= 0) {
cout << indent << "Done!" << endl;
return;
}
cout << indent << n << "..." << endl;
countdown(n - 1, indent + " ");
}
int main() {
// Overloading: different number of parameters
cout << "--- Overloading (different count) ---" << endl;
display();
display(42);
display(10, 20);
// Overloading: different parameter types
cout << "\n--- Overloading (different types) ---" << endl;
process(10);
process(3.14);
process("Hello");
// Overloading: different type order
cout << "\n--- Overloading (different order) ---" << endl;
print(1, 2.5);
print(2.5, 1);
// Default arguments
cout << "\n--- Default Arguments ---" << endl;
greet("John"); // prefix & suffix default
greet("Mike", "Good morning"); // suffix default
greet("Anna", "Hey", "!!"); // all provided
// Default arguments: only right-hand parameters can be defaults
cout << "\n--- Area of Shapes ---" << endl;
cout << "Square (5): " << computeArea(5.0) << endl;
cout << "Rectangle (4, 6): " << computeArea(4.0, 6.0) << endl;
cout << "Triangle (4, 5): " << computeArea(4.0, 5.0, true) << endl;
// Data with defaults
cout << "\n--- Data with Defaults ---" << endl;
printData("Mike");
printData("John", 25);
printData("Anna", 22, "New York");
// Calculator with overloading
cout << "\n--- Calculator ---" << endl;
cout << "5 + 3 = " << compute(5, 3, '+') << endl;
cout << "5.5 * 2.0 = " << compute(5.5, 2.0, '*') << endl;
cout << "10 / 3 (int) = " << compute(10, 3, '/') << endl;
// Countdown with a default parameter
cout << "\n--- Countdown ---" << endl;
countdown(5);
// Overloading with references
cout << "\n--- Reference Overloading ---" << endl;
int x = 10;
const int cx = 20;
auto displayInt = [](const int& val) { cout << "const ref: " << val << endl; };
auto displayInt2 = [](int& val) { cout << "non-const ref: " << val << endl; };
displayInt(x); // a non-const can be called with a const ref
displayInt(cx); // a const works too
displayInt2(x); // a non-const ref only works with a non-const
// Overloading to print various types
cout << "\n--- Print Various Types ---" << endl;
display();
display(42);
display(42, 100);
return 0;
}
Code Explanation
void display()/void display(int x)/void display(int x, int y)— Threedisplayfunctions with different numbers of parameters. The compiler picks the right one based on the number of arguments at the call site.void process(int x)/void process(double x)/void process(string x)— Overloading based on parameter type.process(10)calls theintversion,process(3.14)thedoubleversion.void print(int a, double b)vsvoid print(double a, int b)— Overloading based on type order. The order of the arguments' types determines which function is called.- Return type alone is not enough:
int someFunction(int x)anddouble someFunction(int x)will error because the parameters are the same. The compiler cannot distinguish them by return type alone. string prefix = "Hello"— A default argument. If the caller does not provideprefix, the value "Hello" is used. Defaults must go from right to left.void greet(string name, string prefix = "Hello", string suffix = "!")— Three parameters, two of them defaults.greet("John")= "Hello John!",greet("John", "Hey")= "Hey John!".computeArea(double side)/computeArea(double length, double width = 0)— Overloading AND defaults combined. The compiler first matches the number of arguments, then the types.- Ambiguity: If there are too many defaults, ambiguity can occur.
computeArea(5)could match the one-parameter version or the two-parameter version (with default width = 0). This is a compile error. countdown(int n, string indent = "")— A default argument can differ on every call.indentgains spaces on each recursion, printing indented output.- Name mangling: Behind the scenes, the compiler changes
display(int, double)into a unique name such as_Z7displayid. This allows the linker to find the correct function.
Everyday Use Cases
Flexible print function: In a logging system, you might need log("Error"), log("Error", 404), or log("Error", 404, "file not found"). Overloading allows a single function name for all three cases.
Geometry calculator: computeArea(side) for a square, computeArea(p, l) for a rectangle, computeArea(s, t, true) for a triangle. Same name, different parameters.
Backward-compatible API: Default arguments allow adding new parameters without changing old code. A function greet(name, prefix, suffix) can have a fourth parameter added without breaking code that only provides three arguments.
Conclusion
Function overloading allows a single function name for several input variations. The compiler chooses based on the number, type, and order of the parameters. Default arguments simplify calls by providing built-in values. Combining both creates flexible, easy-to-use APIs. Be careful of ambiguity when combining overloading with default arguments.