Skip to content
Belajar C++

Function Overloading

25 minutes Beginner

Learning Objectives

  • Understand the concept of function overloading
  • Create functions with the same name but different parameters
  • Understand how the compiler selects the correct function version
  • Know the rules for valid overloading

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:

  1. Number of argumentsarea(5) has 1 argument, area(4, 6) has 2
  2. Type of argumentsarea(5) sends an int, area(3.0) sends a double

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?

AspectOverloadingDifferent Names
Easy to rememberOne name for similar operationsMust remember many names
ClaritySometimes unclear which version is calledAlways clear from the function name
When to useFunctions doing the same thing with different data types/countsFunctions 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

Given `double area(int side)` and `double area(int length, int width)`, which version is called by `area(5)`?

Valid vs Invalid Overloading

Which of the following pairs of function signatures represents INVALID overloading in C++?

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 number
  • display(int number, int times) — prints the number times times
  • display(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 Fahrenheit
  • convert(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”.