Function Overloading
Imagine you’re at the school cafeteria. You say “Buy a drink.” If you only have $1, you get tea. If you have $5, you get juice. If you say “Buy a cold drink”, you get iced tea. One command (“buy a drink”), but the result differs depending on the situation.
In C++, this is called function overloading — you can create multiple functions with the same name, but different parameters. The compiler automatically selects the correct version based on the arguments you provide.
First Example: Area Calculation Functions
The area of a square, a rectangle, and a circle all have different formulas. Without overloading, you’d have to create different function names:
// Without overloading — different names
double squareArea(int side);
double rectangleArea(int length, int width);
double circleArea(double radius);
With overloading, one name is enough:
// With overloading — one name, different parameters
double area(int side); // 1 int parameter = square
double area(int length, int width); // 2 int parameters = rectangle
double area(double radius); // 1 double parameter = circle
Here’s the complete code:
#include <iostream>
// Area of a square (1 int parameter)
double area(int side) {
std::cout << "Calculating square area..." << std::endl;
return side * side;
}
// Area of a rectangle (2 int parameters)
double area(int length, int width) {
std::cout << "Calculating rectangle area..." << std::endl;
return length * width;
}
// Area of a circle (1 double parameter)
double area(double radius) {
std::cout << "Calculating circle area..." << std::endl;
return 3.14159 * radius * radius;
}
int main() {
std::cout << "Square (5): " << area(5) << std::endl;
std::cout << "Rectangle (4, 6): " << area(4, 6) << std::endl;
std::cout << "Circle (3.0): " << area(3.0) << std::endl;
return 0;
}
Output:
Calculating square area...
Square (5): 25
Calculating rectangle area...
Rectangle (4, 6): 24
Calculating circle area...
Circle (3.0): 28.2743
The compiler looks at the arguments you provide and automatically picks the matching function version. Pretty cool, right?
How Does the Compiler Choose?
This process is called overload resolution. The compiler looks at:
- Number of arguments —
area(5)has 1 argument,area(4, 6)has 2 - Type of arguments —
area(5)sends anint,area(3.0)sends adouble
Based on those two things, the compiler selects the best match.
area(5); // 1 int argument -> area(int side)
area(4, 6); // 2 int arguments -> area(int length, int width)
area(3.0); // 1 double argument -> area(double radius)
This happens at compile time (before the program runs), not while the program is running. So there’s no impact on program speed.
Example: Print Functions
Overloading is very useful for functions that display various data types:
#include <iostream>
#include <string>
void print(int number) {
std::cout << "Integer: " << number << std::endl;
}
void print(double number) {
std::cout << "Decimal: " << number << std::endl;
}
void print(const std::string& text) {
std::cout << "Text: " << text << std::endl;
}
void print(bool value) {
std::cout << "Boolean: " << (value ? "true" : "false") << std::endl;
}
int main() {
print(42);
print(3.14);
print(std::string("Hello C++!"));
print(true);
return 0;
}
Output:
Integer: 42
Decimal: 3.14
Text: Hello C++!
Boolean: true
One function name print, but it can handle various data types. The code becomes cleaner and easier to remember!
You’ve Already Been Using Overloading!
Did you know? std::cout actually uses overloading! The << operator in std::cout has many versions:
std::cout << 42; // version for int
std::cout << 3.14; // version for double
std::cout << "hello"; // version for string
std::cout << true; // version for bool
That’s why std::cout can print various data types without you having to say “this is an int” or “this is a string”. The compiler already knows which version to call!
Many features in the C++ Standard Library use overloading. So this concept isn’t just theory — you’ll keep encountering it!
Overloading Rules
There are several important rules you need to know:
1. Parameters Must Differ
Overloaded functions must differ in number of parameters or parameter types:
// VALID: different number of parameters
void greet();
void greet(std::string name);
void greet(std::string name, int age);
// VALID: different parameter types
void process(int data);
void process(double data);
void process(std::string data);
2. Return Type Alone Is NOT Enough
This is a common mistake:
// NOT VALID! Only the return type differs
int calculate(int x);
double calculate(int x); // ERROR: both accept 1 int
Why? Because when you call calculate(5), the compiler can’t tell whether you want the version that returns int or double. It’s ambiguous!
3. Be Careful with Automatic Conversions
C++ sometimes performs automatic type conversion, and this can cause confusion:
void process(int x) {
std::cout << "int version: " << x << std::endl;
}
void process(double x) {
std::cout << "double version: " << x << std::endl;
}
int main() {
process(5); // clear: int -> int version
process(5.0); // clear: double -> double version
process(5.5f); // float -> converted to double -> double version
return 0;
}
If the compiler is confused (ambiguous), it will give an error. This is actually a good thing — it’s better to get an error at compile time than a bug at runtime!
Practical Example: Greet Function
#include <iostream>
#include <string>
// Version 1: no parameters
void greet() {
std::cout << "Hello, World!" << std::endl;
}
// Version 2: with name
void greet(const std::string& name) {
std::cout << "Hello, " << name << "!" << std::endl;
}
// Version 3: with name and age
void greet(const std::string& name, int age) {
std::cout << "Hello, " << name << "! You are " << age << " years old." << std::endl;
}
// Version 4: with name and class
void greet(const std::string& name, const std::string& className) {
std::cout << "Hello, " << name << " from class " << className << "!" << std::endl;
}
int main() {
greet();
greet("Budi");
greet("Ani", 15);
greet("Citra", "9A");
return 0;
}
Output:
Hello, World!
Hello, Budi!
Hello, Ani! You are 15 years old.
Hello, Citra from class 9A!
Overloading vs Different Names: Which Is Better?
| Aspect | Overloading | Different Names |
|---|---|---|
| Easy to remember | One name for similar operations | Must remember many names |
| Clarity | Sometimes unclear which version is called | Always clear from the function name |
| When to use | Functions doing the same thing with different data types/counts | Functions doing different things |
Use overloading when the functions truly perform operations that are conceptually the same — like calculating area (but for different shapes) or printing data (but for different types).
Don’t use overloading when the functions do different things. For example, don’t make process(int) for calculating and process(string) for displaying — that’s confusing!
Complete Example: 2D Shape Calculator
#include <iostream>
// === PERIMETER ===
double perimeter(int side) {
return 4.0 * side; // square
}
double perimeter(int length, int width) {
return 2.0 * (length + width); // rectangle
}
double perimeter(double radius) {
return 2.0 * 3.14159 * radius; // circle
}
// === AREA ===
double area(int side) {
return side * side; // square
}
double area(int length, int width) {
return length * width; // rectangle
}
double area(double radius) {
return 3.14159 * radius * radius; // circle
}
int main() {
std::cout << "=== 2D SHAPE CALCULATOR ===" << std::endl;
std::cout << std::endl;
// Square with side 5
std::cout << "Square (side = 5):" << std::endl;
std::cout << " Perimeter: " << perimeter(5) << std::endl;
std::cout << " Area: " << area(5) << std::endl;
std::cout << std::endl;
// Rectangle 4 x 7
std::cout << "Rectangle (4 x 7):" << std::endl;
std::cout << " Perimeter: " << perimeter(4, 7) << std::endl;
std::cout << " Area: " << area(4, 7) << std::endl;
std::cout << std::endl;
// Circle with radius 3.5
std::cout << "Circle (radius = 3.5):" << std::endl;
std::cout << " Perimeter: " << perimeter(3.5) << std::endl;
std::cout << " Area: " << area(3.5) << std::endl;
return 0;
}
How the Compiler Picks an Overload
Valid vs Invalid Overloading
Exercises
Exercise 1: Create a max_value function that is overloaded to accept 2 ints, 3 ints, and 2 doubles. Each returns the largest value.
Exercise 2: Create a display function that is overloaded:
display(int number)— prints the numberdisplay(int number, int times)— prints the numbertimestimesdisplay(const std::string& text, char delimiter)— prints text with the delimiter on the left and right (e.g.,*Hello*)
Exercise 3: Create a convert function that is overloaded:
convert(double celsius)— returns Fahrenheitconvert(double value, const std::string& from)— can convert from “km” to meters, or “kg” to grams
Exercise 4: Explain why this code causes an ERROR:
int divide(int a, int b);
double divide(int a, int b);
Now you know how to create multiple functions with the same name! In the next lesson, we’ll learn about scope and lifetime — where variables “live” and when they “die”.