Method and Constructor Overloading
Learning Objectives
Declare overloads and predict which one is selected.
Explain implicit conversions, ambiguity and default arguments.
Know that the return type alone cannot distinguish C++ overloads.
Compare overloading support across languages.
Prerequisites: Modules 2, 5.
Intuition and Definition
Overloading gives several functions the same name with different parameter lists. The compiler chooses one at compile time by overload resolution: it ranks candidates by how well each argument matches (exact match > promotion > standard conversion > user-defined conversion) and picks the unique best one; if two are equally good, the call is ambiguous.
Why It Matters
Overloads let one concept (print, add, construct) accept natural argument types. Badly designed overload sets cause surprising choices (print(0) vs print(nullptr)) or ambiguity errors.
Syntax and Language Notes
Yes, by parameter types/count/qualifiers; templates and default arguments interact
Yes, by parameter types/count; resolution with boxing and varargs phases Yes, plus optional and named parameters
No — a later def replaces the earlier one; use default arguments, *args, or functools.singledispatch
Overload *signatures* for typing, but a single implementation that checks arguments at run time
void print(int value);
void print(double value);
void print(const std::string& value);Complete Example
An ambiguous call is a compile error — both candidates need a conversion of the same rank:
Python has no overloading; functools.singledispatch dispatches on the first argument's type at run time:
#include <iostream>
#include <string>
void print(int v) { std::cout << "int: " << v << "\n"; }
void print(double v) { std::cout << "double: " << v << "\n"; }
void print(const std::string& v) { std::cout << "string: " << v << "\n"; }
void print(const char* v) { std::cout << "c-str: " << v << "\n"; }
void print(bool v) { std::cout << "bool: " << std::boolalpha << v << "\n"; }
class Duration {
long long ms_;
public:
Duration() : Duration(0) {} // overloaded constructors
explicit Duration(long long ms) : ms_(ms) {}
Duration(int minutes, int seconds) : ms_((minutes * 60LL + seconds) * 1000) {}
long long ms() const { return ms_; }
};
int main() {
print(42); // exact match int
print(3.5); // exact match double
print(2.5f); // float -> double is a promotion
print('A'); // char -> int is a promotion
print("literal"); // const char* exact match beats conversion to std::string
print(std::string("s"));
print(true);
print(static_cast<short>(7)); // short -> int promotion
std::cout << Dura
…int: 42
double: 3.5
double: 2.5
int: 65
c-str: literal
string: s
bool: true
int: 7
0 1500 150000void scale(float f) { (void)f; }
void scale(double d) { (void)d; }
int main() {
scale(10); // int -> float and int -> double are both standard conversions
}m15_ambiguous.cpp:4:5: error: call to 'scale' is ambiguousfrom functools import singledispatch
@singledispatch
def describe(x):
return f"something: {x!r}"
@describe.register
def _(x: int):
return f"int {x}"
@describe.register
def _(x: list):
return f"list of {len(x)}"
print(describe(5), "|", describe([1, 2, 3]), "|", describe(2.5))
def area(w, h=None): # default argument instead of two overloads
return w * (w if h is None else h)
print(area(3), area(3, 4))int 5 | list of 3 | something: 2.5
9 12Step-by-Step Walkthrough
print(2.5f) — no float overload, but float→double is a *promotion*, better than float→int conversion.
print('A') — char→int is a promotion, so the int overload wins (prints 65); there is no char overload.
print("literal") — const char* is an exact match (after array-to-pointer decay); std::string would need a user-defined conversion, ranked lower.
Constructors overload the same way: Duration(2, 30) selects the two-int version.
scale(10) is ambiguous: int→float and int→double are both *standard conversions* with equal rank.
Common Mistakes
Overloading on types that convert into each other (int and bool, const char* and std::string and std::string_view) — confusing selections.
Expecting overloads to dispatch on dynamic type — resolution uses the *static* types of arguments.
Different behaviour in overloads of the same name — overloads should do "the same thing" for different inputs.
Default arguments + overloads creating ambiguous calls.
Python: defining two def f versions — only the last survives.
Trade-offs and Alternatives
Templates (template <typename T> void print(const T&)) avoid writing N overloads but accept types you didn't intend unless constrained by concepts (Module 17).
Named factory functions (Duration::fromSeconds) beat ambiguous constructor overloads with the same parameter types.
Real-World Use
std::to_string, std::abs, stream operator<<, logging APIs (log(level, msg) / log(msg)) and math libraries are overload sets.
Practice
Q1. (Easy) Can two C++ functions differ only in return type?
Answer: No — return type is not part of overload resolution for ordinary functions.
Q2. (Medium) Which overload does print(0) pick if only print(int) and print(const char*) exist? And print(nullptr)?
Answer: print(0) → int (exact match). print(nullptr) → const char* (nullptr_t converts to pointers, not to int).
Q3. (Advanced) Replace the ambiguous scale(float) / scale(double) pair with a single constrained template that accepts any arithmetic type.
#include <concepts>
#include <iostream>
template <typename T>
requires std::integral<T> || std::floating_point<T>
double scale(T value, double factor = 1.5) { return static_cast<double>(value) * factor; }
int main() {
std::cout << scale(10) << " " << scale(2.0f) << " " << scale(4.0, 0.5) << "\n";
}Interview Question
Q1. "What's the difference between overloading and overriding?"
Answer: Overloading = same name, different parameter lists, chosen at compile time from argument types, within one scope. Overriding = a derived class replaces a virtual function with the same signature, chosen at run time from the object's dynamic type. A derived function with the same name but different parameters *hides* the base functions rather than overriding them (Module 7).
Quick Quiz
Q1. Which ranks higher: promotion or user-defined conversion?
Answer: Promotion.
Q2. How does Python emulate overloading?
Answer: Default/keyword arguments, *args, type checks, or functools.singledispatch.
Q3. What do TypeScript overload signatures produce at run time?
Answer: One implementation function; the signatures are type-only.
Summary
Overload resolution ranks conversions; ties are ambiguity errors.
Return type alone doesn't overload; static argument types decide.
Keep overload sets small and semantically identical; prefer constrained templates or named functions.
Related: Module 8, Module 16 (operators), Module 17 (templates).