Function Overloading
Function overloading allows multiple functions with the same name but different parameters. The compiler selects the best match based on arguments.
Compile-Time Selection
Overload resolution happens at compile-time based on argument types, not runtime values.
Basic Overloading​
void print(int x) {
std::cout << "int: " << x << "\n";
}
void print(double x) {
std::cout << "double: " << x << "\n";
}
void print(const char* x) {
std::cout << "string: " << x << "\n";
}
print(42); // Calls print(int)
print(3.14); // Calls print(double)
print("hello"); // Calls print(const char*)
Overloading Rules​
Functions can differ by:
âś… Number of parameters
void func(int x);
void func(int x, int y);
âś… Type of parameters
void func(int x);
void func(double x);
âś… const/volatile qualifiers
void func(int x);
void func(const int x); // ❌ Same (top-level const ignored)
void func(int* p);
void func(const int* p); // âś… Different (low-level const)
void process(std::string& s); // Lvalue
void process(const std::string& s); // Lvalue (different)
void process(std::string&& s); // Rvalue
âś… Reference qualifiers
void func(int& x); // Lvalue reference
void func(int&& x); // Rvalue reference
❌ Return type only (not allowed)
int func(int x);
double func(int x); // ❌ Error: differs only by return type
Overload Resolution Process​
- Find candidates: Functions with matching name
- Filter viable: Can be called with given arguments
- Select best match: Most specific match wins
void func(int x); // #1
void func(double x); // #2
void func(int x, int y); // #3
func(42); // Calls #1: exact match
func(3.14); // Calls #2: exact match
func(42, 10); // Calls #3: exact match
func('c'); // Calls #1: char promotes to int
Exact Match vs Conversion​
void print(int x);
void print(double x);
print(42); // Exact match → print(int)
print(3.14); // Exact match → print(double)
print(3.14f); // float → double conversion → print(double)
print('A'); // char → int promotion → print(int)
Ranking:
- Exact match
- Promotions (char→int, float→double)
- Standard conversions (int→double)
- User-defined conversions
Ambiguous Overloads​
void func(int x, double y);
void func(double x, int y);
func(42, 3.14); // âś… OK: exact match on both
// func(42, 42); // ❌ Error: ambiguous!
// Both require one conversion
Resolving Ambiguity​
// Explicit cast
func(42, static_cast<double>(42)); // Calls first overload
func(static_cast<double>(42), 42); // Calls second overload
// Or add more overloads
void func(int x, int y); // Exact match for func(42, 42)
const Overloading​
class Widget {
public:
int getValue() const { // const version
std::cout << "const\n";
return value;
}
int& getValue() { // non-const version
std::cout << "non-const\n";
return value;
}
private:
int value = 42;
};
Widget w;
w.getValue() = 100; // Calls non-const, returns int&
const Widget cw;
int x = cw.getValue(); // Calls const version
Reference Overloading​
void process(const std::string& s) {
std::cout << "lvalue: " << s << "\n";
}
void process(std::string&& s) {
std::cout << "rvalue: " << s << "\n";
// Can move from s
}
std::string str = "hello";
process(str); // Calls lvalue version
process("temporary"); // Calls rvalue version
process(std::move(str)); // Calls rvalue version
Pointer vs Array​
void func(int* arr);
void func(int arr[]); // Same as int*
void func(int arr[10]); // Still same as int*
// All three are identical - no overloading!
void func(int (&arr)[10]); // âś… Different: reference to array
Default Arguments​
void func(int x, int y = 0);
void func(int x); // ❌ Error: ambiguous with func(x, 0)
// Call
func(42); // Which one? Ambiguous!
Template Overloading​
template<typename T>
void func(T x) {
std::cout << "template\n";
}
void func(int x) {
std::cout << "non-template\n";
}
func(42); // Calls non-template (exact match preferred)
func(3.14); // Calls template (only option)
Rule: Non-template functions preferred over templates for exact matches.
Variadic Overloading​
void print(int x) {
std::cout << x << "\n";
}
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << ", ";
print(rest...); // Recursive
}
print(1, 2, 3, 4); // 1, 2, 3, 4
Name Hiding in Inheritance​
class Base {
public:
void func(int x) {
std::cout << "Base::func\n";
}
};
class Derived : public Base {
public:
void func(double x) { // Hides Base::func!
std::cout << "Derived::func\n";
}
};
Derived d;
d.func(42); // Calls Derived::func(double), not Base::func(int)!
// Even though int→int is better than int→double
Solution: Use using declaration:
class Derived : public Base {
public:
using Base::func; // Bring Base::func into scope
void func(double x) {
std::cout << "Derived::func\n";
}
};
Derived d;
d.func(42); // Now calls Base::func(int) - better match
Operator Overloading​
class Complex {
double real, imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
// Member function
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// Can also overload as friend/global function
};
Complex a(1, 2), b(3, 4);
Complex c = a + b; // Calls operator+
Best Practices​
DO
- Overload for different types/counts
- Use const overloading for getter/setter pairs
- Use reference qualifiers (lvalue/rvalue) for optimization
- Keep overload sets intuitive
DON'T
- Overload with same parameter types (differs only by names)
- Create ambiguous overload sets
- Differ only by return type
- Use overloading when different names are clearer
Common Pitfalls​
Surprising Conversions​
void func(long x);
void func(double x);
func(42); // ❌ Ambiguous! int→long and int→double both conversions
Pointer/Bool Conversion​
void func(int x);
void func(bool x);
func(nullptr); // ❌ Ambiguous in some cases
Forwarding References​
template<typename T>
void func(T&& x); // Universal reference
void func(int& x);
int x = 42;
func(x); // Which one? Template is exact match!
Summary​
Function overloading:
- Same name, different parameters
- Resolved at compile-time
- Ranked: Exact > Promotion > Conversion
Can overload on:
- Parameter count
- Parameter types
- const qualifiers (low-level)
- Reference type (lvalue/rvalue)
Cannot overload on:
- Return type only
- Parameter names
- Top-level const
// Good overload set
void process(int x); // Integer
void process(double x); // Floating-point
void process(const std::string& s); // String lvalue
void process(std::string&& s); // String rvalue
Overloading enables type-safe, expressive APIs while maintaining performance through compile-time resolution.