Overloading and default arguments
Two functions may share a name as long as their parameter lists differ. The compiler picks one from the argument types at the call site, which is overloading, and it can also fill in arguments the caller leaves out, which is default arguments. Both keep the call site short and the name meaningful.
The two features meet in the same problem: when the argument types match more than one overload, or when a default hides a case the caller meant to write, the compiler either picks the wrong function or refuses to pick at all. This lesson shows both outcomes with the real messages.
One name, several parameter lists
The three show functions below differ in their parameter type: int, double and const std::string&. Calling show(7) selects the int version, show(2.5) the double one and show("text") the string one, because a string literal converts to std::string.
The return type is not part of the signature: two functions that differ only in what they return cannot both exist. The parameter types, their order and their count are what distinguish the overloads.
Overloading is resolved at compile time, so the call has no run-time cost. It is also why the compiler must see every candidate: an overload declared after the call does not take part in it.
#include <iostream>
#include <string>
void show(int value)
{
std::cout << "int " << value << '\n';
}
void show(double value)
{
std::cout << "double " << value << '\n';
}
void show(const std::string& value)
{
std::cout << "string " << value << '\n';
}
int main()
{
show(7);
show(2.5);
show("text");
}
g++ -std=c++23 -Wall -Wextra overload.cpp -o overload
./overload
int 7
double 2.5
string text
Which overload wins
GCC ranks the candidates: an exact match beats a promotion, a promotion beats a conversion, and a user-defined conversion comes last. When two candidates rank equally, the call is ambiguous and the build stops.
The program makes the choice visible with three overloads and three arguments. pick(1) is an int and matches int exactly, pick(1L) is a long, and pick(1.0) is a double; no conversion is needed for any of them.
The ranking matters as soon as a literal is involved. An integer literal is an int, so a function taking long is not called for it unless no better candidate exists, which is rarely what the author of an API intended.
#include <iostream>
void pick(int value)
{
std::cout << "int " << value << '\n';
}
void pick(long value)
{
std::cout << "long " << value << '\n';
}
void pick(double value)
{
std::cout << "double " << value << '\n';
}
int main()
{
pick(1);
pick(1L);
pick(1.0);
}
g++ -std=c++23 -Wall -Wextra resolution.cpp -o resolution
./resolution
int 1
long 1
double 1
Default arguments
A default argument is written once, in the declaration the compiler sees, and supplies a value when the caller leaves the parameter out. The program's greet function has a default greeting, so greet("Ada") and greet("Ada", "Hi") reach the same body with different strings.
Defaults are filled in at the call site, not inside the function: the compiled call passes both arguments. That is why a default may not depend on another parameter, and why changing a default in a header recompiles every caller.
Put defaults in the declaration, not in the definition that a caller never sees. Defining the same default twice, in a header and in the .cpp file, is an error the compiler reports as a redefinition of the default argument.
#include <iostream>
#include <string>
std::string greet(const std::string& name, const std::string& greeting = "Hello")
{
return greeting + ", " + name + "!";
}
int main()
{
std::cout << greet("Ada") << '\n';
std::cout << greet("Ada", "Hi") << '\n';
}
g++ -std=c++23 -Wall -Wextra defaults.cpp -o defaults
./defaults
Hello, Ada!
Hi, Ada!
An ambiguous call
When two overloads need the same conversion from the argument, neither is better and the call is ambiguous. GCC lists both candidates and the argument that could not decide between them.
The program calls show(1) with an int and offers show(long) and show(double). Converting int to long is an integral conversion, converting int to double is a floating-integral conversion, and neither is preferred, so the build fails.
The fix is a third overload that takes int, or an explicit cast at the call site. Adding the int overload is better: the API then has a valid answer for the type callers actually pass.
#include <iostream>
void show(long value)
{
std::cout << "long " << value << '\n';
}
void show(double value)
{
std::cout << "double " << value << '\n';
}
int main()
{
show(1);
}
g++ -std=c++23 -Wall -Wextra ambiguity.cpp -o ambiguity
ambiguity.cpp: In function 'int main()':
ambiguity.cpp:15:9: error: call of overloaded 'show(int)' is ambiguous
15 | show(1);
| ~~~~^~~
ambiguity.cpp:15:9: note: there are 2 candidates
ambiguity.cpp:3:6: note: candidate 1: 'void show(long int)'
3 | void show(long value)
| ^~~~
ambiguity.cpp:8:6: note: candidate 2: 'void show(double)'
8 | void show(double value)
| ^~~~
Defaults go last
Default arguments fill the trailing parameters of a call, so once a parameter has a default every parameter after it needs one too. The declaration below gives width a default and leaves height without one, and the compiler reports the missing default on height.
The rule follows from the call syntax: area(3) would have to mean width = 3 and height = ?, and there is no way to skip a parameter in the middle of a call. Reordering the parameters so that the defaulted one comes last is the fix.
A function with many defaults is usually a sign that the parameters should be a struct with designated initialisers, which lesson 133 covers.
#include <iostream>
void area(int width = 1, int height)
{
std::cout << width * height << '\n';
}
int main()
{
area(3, 4);
}
g++ -std=c++23 -Wall -Wextra defaults_rule.cpp -o defaults_rule
defaults_rule.cpp:3:30: error: default argument missing for parameter 2 of 'void area(int, int)'
3 | void area(int width = 1, int height)
| ~~~~^~~~~~
defaults_rule.cpp:3:15: note: ...following parameter 1 which has a default argument
3 | void area(int width = 1, int height)
| ~~~~^~~~~~~~~
Exercises
- Add an overload of pick that takes a const char* and call it with a literal. Which overload wins for "text", and why?
- Give greet a third defaulted parameter for punctuation and call it with one, two and three arguments.
- Move the default of area to the end of the parameter list and compile. Does area(3) then do what you expect?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What allows two functions to share a name?
Answer
Parameter lists that differ in number or type
The parameters are what the compiler uses to tell the overloads apart at a call.
-
How does the compiler choose an overload?
Answer
From the types of the arguments at the call
Overload resolution is a match between the argument types and the parameter lists.
-
What is a default argument?
Answer
A value the caller may omit for a trailing parameter
The default belongs to the declaration, and the call uses it when the argument is missing.
-
Why must default arguments come last?
Answer
A call fills the parameters from the left, so a gap cannot be skipped
A parameter without a default cannot follow one with a default, or the call would be ambiguous.
-
What does this program print?
#include <iostream> #include <string> void show(int) { std::cout << "int 7\n"; } void show(double) { std::cout << "double 2.5\n"; } void show(const std::string&) { std::cout << "string text\n"; } int main() { show(7); show(2.5); show("text"); }Answer
int 7 / double 2.5 / string text
Each call matches the overload for its argument type, and each prints its own label.
-
What does this program print?
#include <iostream> void show(int) { std::cout << "int 1\n"; } void show(long) { std::cout << "long 1\n"; } void show(double) { std::cout << "double 1\n"; } int main() { show(1); show(1L); show(1.0); }Answer
int 1 / long 1 / double 1
The exact match wins for each argument type, and the printed label names the chosen overload.
-
What does this program print?
#include <iostream> #include <string> void greet(const std::string& name, const std::string& word = "Hello") { std::cout << word << ", " << name << "!\n"; } int main() { greet("Ada"); greet("Ada", "Hi"); }Answer
Hello, Ada! / Hi, Ada!
The first call uses the default greeting, the second passes its own, and both use the default name.
-
What does calling
show(1)with overloads for long and double produce?Answer
call of overloaded 'show(int)' is ambiguous
Both conversions are equally good, and the compiler refuses to choose between them.
-
What does
void area(int width = 1, int height)report?Answer
default argument missing for parameter 2
A parameter without a default follows one with a default, which the rule forbids.
-
Why is overloading on the return type alone impossible?
Answer
The call does not say which return type it wants
The arguments are the only information at the call site, so they must distinguish the overloads.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev