Functions
Function Overloading in C++
Table of Contents
Introduction
Function overloading allows multiple functions with the same name but different parameters. The compiler selects the correct version based on the arguments.
int add(int a, int b); // For integers
double add(double a, double b); // For doubles
string add(string a, string b); // For strings
Why Overload?
- Consistent interface for similar operations
- Avoid inventing many function names (addInt, addDouble, addString)
- Cleaner, more intuitive API
How Overloading Works
Function Signature
The signature determines uniqueness:
- Function name
- Number of parameters
- Type of parameters
- Order of parameters
NOT part of signature:
- Return type
- Parameter names
// Valid overloads - different signatures
void print(int x);
void print(double x);
void print(int x, int y);
void print(string s);
// INVALID - only differs by return type
int getValue();
double getValue(); // ERROR: redefinition
Name Mangling
The compiler internally renames functions to encode parameter types:
print(int) → _Z5printi
print(double) → _Z5printd
print(int,int) → _Z5printii
Overload Resolution
When you call an overloaded function, the compiler:
- Finds all candidates with that name
- Filters to viable functions (matching parameter count)
- Ranks by conversion quality
- Selects best match or reports ambiguity
Matching Priority (Best to Worst)
| Priority | Match Type | Example |
|---|---|---|
| 1 | Exact match | print(5) → print(int) |
| 2 | Promotion | print('a') → print(int) (char→int) |
| 3 | Standard conversion | print(3.14) → print(int) (double→int) |
| 4 | User-defined conversion | Custom conversion operators |
Examples
void func(int x); // (1)
void func(double x); // (2)
void func(long x); // (3)
func(10); // Exact → (1)
func(3.14); // Exact → (2)
func(10L); // Exact → (3)
func('A'); // Promotion char→int → (1)
func(10.0f); // Promotion float→double → (2)
Common Use Cases
1. Different Types
void print(int value) {
cout << "Integer: " << value << endl;
}
void print(double value) {
cout << "Double: " << value << endl;
}
void print(const string& value) {
cout << "String: " << value << endl;
}
2. Different Number of Arguments
int max(int a, int b) {
return (a > b) ? a : b;
}
int max(int a, int b, int c) {
return max(max(a, b), c);
}
int max(int a, int b, int c, int d) {
return max(max(a, b), max(c, d));
}
3. Optional Parameters
void greet() {
greet("World"); // Call other overload
}
void greet(const string& name) {
greet(name, "Hello");
}
void greet(const string& name, const string& greeting) {
cout << greeting << ", " << name << "!" << endl;
}
4. Container Variants
double average(const vector<int>& nums);
double average(const vector<double>& nums);
double average(const int* arr, size_t size);
Ambiguity and Pitfalls
Ambiguous Calls
void process(int x, double y);
void process(double x, int y);
process(1, 2); // ERROR: ambiguous!
// Both require one conversion
// Fix: explicit cast
process(1, 2.0); // Calls first
process(1.0, 2); // Calls second
const Overloading
class Container {
public:
int& get(int index); // For non-const objects
const int& get(int index) const; // For const objects
};
Container c;
const Container cc;
c.get(0) = 10; // Uses non-const version
int x = cc.get(0); // Uses const version
Reference vs Value Ambiguity
void func(int x); // Value
void func(int& x); // Reference
int n = 5;
func(n); // ERROR: ambiguous!
func(5); // OK: only value works (5 is rvalue)
Default Arguments Interaction
void display(int x, int y = 0);
void display(int x);
display(5); // ERROR: ambiguous!
Overloading vs Default Arguments
| Aspect | Overloading | Default Arguments |
|---|---|---|
| Different logic | ✓ Each has own impl | ✗ Same impl |
| Compile-time binding | At call site | At call site |
| Flexibility | More flexible | Simpler |
| Code duplication | May duplicate | Single function |
When to Use Each
Default Arguments:
// Same logic, just different defaults
void connect(string host, int port = 80, bool ssl = false) {
// Single implementation
}
Overloading:
// Fundamentally different implementations
void sort(vector<int>& v) {
// Integer-specific optimization
}
void sort(vector<string>& v) {
// String comparison logic
}
Operator Overloading Preview
Operators are special functions that can be overloaded:
class Vector2D {
public:
double x, y;
Vector2D operator+(const Vector2D& other) const {
return {x + other.x, y + other.y};
}
Vector2D operator*(double scalar) const {
return {x * scalar, y * scalar};
}
bool operator==(const Vector2D& other) const {
return x == other.x && y == other.y;
}
};
Vector2D a{1, 2}, b{3, 4};
Vector2D c = a + b; // Uses operator+
Vector2D d = a * 2.0; // Uses operator*
bool eq = (a == b); // Uses operator==
(Full coverage in 05_Operator_Overloading)
Recommended Conventions
✅ Do
// Consistent behavior across overloads
int abs(int x);
double abs(double x);
long abs(long x);
// Clear semantic difference
void draw(Point p);
void draw(Line l);
void draw(Circle c);
❌ Don't
// Semantically different operations
void process(int x); // Prints x
void process(string s); // Writes to file
// Confusing - same name, completely different behavior!
// Too similar, easy to call wrong one
void update(int id, string name);
void update(string name, int id);
Guidelines Summary
- Overload for same conceptual operation on different types
- Avoid ambiguous parameter combinations
- Document when behavior differs between overloads
- Consider templates if logic is identical across types
- Be careful mixing overloading with default arguments
Cheat Sheet
// Valid overloads
void f(int);
void f(double);
void f(int, int);
void f(const string&);
// Invalid - return type doesn't distinguish
int f();
double f(); // ERROR
// const overloading (member functions)
T& get();
const T& get() const;
// Ambiguity resolution
void g(int, double);
void g(double, int);
g(1, 2); // ERROR: ambiguous
g(1, 2.0); // OK: first overload
Compile & Run
g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples