Lesson 6 of 8
C++ lets several functions share a name as long as their parameters differ:
#include <iostream>
#include <string>
void print(int value) {
std::cout << "int: " << value << "\n";
}
void print(double value) {
std::cout << "double: " << value << "\n";
}
void print(const std::string& value) {
std::cout << "string: " << value << "\n";
}
int main() {
print(42);
print(3.14);
print(std::string("hello"));
return 0;
}
int: 42
double: 3.14
string: hello
Three separate functions. The compiler picks one by looking at the argument types — a process called overload resolution, done entirely at compile time with no run-time cost.
Without overloading you'd need print_int, print_double, print_string, and callers would
have to remember which. This is why std::cout << works with everything: operator<< is
overloaded dozens of times.
What counts as different
Overloads must differ in the number or types of parameters:
void f(int);
void f(int, int); // different count — fine
void f(double); // different type — fine
void f(const std::string&); // fine
Return type alone is not enough:
// WILL NOT COMPILE
int value();
double value();
error: ambiguating new declaration of 'double value()'
That seems arbitrary until you consider value(); as a statement, discarding the result — the
compiler would have no information to choose from. Overload resolution looks only at arguments.
How the compiler chooses
For each candidate it ranks the conversions needed, roughly best to worst:
- Exact match — no conversion at all.
- Promotion —
float→double,char→int. Cheap and lossless. - Standard conversion —
int→double,double→int, anything→bool. - User-defined conversion — via a constructor or conversion operator (Module 9).
The best available match wins. A tie is a compile error, not a coin flip:
// WILL NOT COMPILE
#include <iostream>
void f(long) { std::cout << "long\n"; }
void f(double) { std::cout << "double\n"; }
int main() {
f(3); // int -> long and int -> double are both rank-3 conversions
return 0;
}
error: call of overloaded 'f(int)' is ambiguous
C++ refuses to guess. Fix it by being explicit — f(3.0) or f(static_cast<long>(3)) — or by
adding an int overload that matches exactly.
Note how finely this is graded. Swap long for int in that example and it compiles fine:
f(3) then matches f(int) exactly, which beats everything. And with f(int)/f(double),
calling f(3.0f) is also fine — float→double is a promotion (rank 2), which outranks
float→int (rank 3). Ambiguity needs two candidates at the same rank.
The surprise: literals
#include <iostream>
void show(int value) {
std::cout << "int " << value << "\n";
}
void show(char value) {
std::cout << "char " << value << "\n";
}
int main() {
show(65);
show('A');
return 0;
}
int 65
char A
65 and 'A' are the same number (lesson 3.5), but their types differ, so they select
different overloads. Type, not value, drives the decision.
A related trap: a string literal is const char*, not std::string. Given overloads for bool
and std::string, f("hello") may surprisingly pick bool — because pointer→bool is a
standard conversion while const char*→std::string is a user-defined one, which ranks worse.
If you have both, add a const char* overload.
Overloading and const
A const& parameter and a & parameter are different enough to overload on:
#include <iostream>
#include <string>
void handle(std::string& text) {
text += " (modified)";
std::cout << "non-const: " << text << "\n";
}
void handle(const std::string& text) {
std::cout << "const: " << text << "\n";
}
int main() {
std::string mutable_text = "hello";
const std::string fixed_text = "world";
handle(mutable_text);
handle(fixed_text);
handle("temporary");
return 0;
}
non-const: hello (modified)
const: world
const: temporary
A modifiable object prefers the non-const overload; a const object or a temporary can only
bind to the const& one. This pattern shows up throughout the standard library.
When not to overload
Overloading is for functions that do the same thing to different types. If the behaviour genuinely differs, separate names are clearer:
// bad — same name, unrelated work
void process(const Order&); // charges a card
void process(const LogEntry&); // writes to disk
// better
void chargeOrder(const Order&);
void writeLogEntry(const LogEntry&);
The reader shouldn't have to check argument types to find out what a call does.
Also note: if the overloads' bodies would be identical apart from the type, you want a template (Module 12), not a pile of copies.
Try it
Write three overloads of area: one taking a radius (double) for a circle, one taking width
and height (two doubles) for a rectangle, and one taking a single int side for a square.
Call all three and predict which is chosen for area(5).
Checkpoint
- Overloads share a name and differ in parameter count or types.
- Return type alone cannot distinguish overloads.
- Resolution ranks conversions: exact match, promotion, standard conversion, user-defined.
- An ambiguous call is a compile error — C++ never guesses.
- Type drives selection, not value:
show(65)andshow('A')differ. - Overload for the same operation on different types; use distinct names when behaviour differs, and templates when the bodies would be identical.
Questions about this lesson
Log in to ask a question.