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

  1. Add an overload of pick that takes a const char* and call it with a literal. Which overload wins for "text", and why?
  2. Give greet a third defaulted parameter for punctuation and call it with one, two and three arguments.
  3. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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

Banner image