Skip to content
VLSI Mentor

C++ Part I · Module 6 · Functions

Function Overloading and Overload Resolution

Chapter 6.1 mentioned this feature in passing and in a bad light: it is the reason a mistyped parameter type produces a linker error rather than a compiler error, because changing the parameters does not redeclare a function but declares a second one. This chapter is that feature used on purpose. Several functions may share a name as long as their parameter lists differ, which lets one name describe one operation across the types it applies to — and it is why std::cout can print an int, a double and a string with the same syntax. The cost is that a call no longer names a function; it names a set of candidates, and the compiler has to choose. That choosing follows a rank order which is short enough to learn and sharp enough to produce answers nobody predicts the first time. Two measurements carry the chapter. In the first, a call passing the character 'a' selected the integer overload and printed 97, because the rank order prefers a promotion over a conversion. In the second, two entirely reasonable overloads for int and double refused an ordinary call passing a long — not because neither matched, but because both did, equally, and the language will not guess.

Intermediate17 min readC++Part IFundamentalsFunctions

Part I · Module 6 · Chapter 6.7 · Function Overloading and Overload Resolution

1. One Name, Several Functions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void show(int v);
    void show(double v);
    void show(const char* v);

Three functions, one name. This is overloading, and it is legal because their parameter lists differ.

You have been using it since Chapter 2.8. std::cout << x works whether x is an int, a double or a string, and that is overloading — a set of functions sharing one name, with the compiler selecting by argument type.

The reason to want it is that a name should describe an operation, not a type. show, print, max, swap — each is one idea that applies to several types, and inventing show_int, show_double, show_string puts type information into names where the compiler already has it.

2. What Makes Two Functions Different Enough

Overloads must differ in their parameter list — the number of parameters or their types.

The return type does not count. Chapter 6.1 §6 already measured this from the other direction:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    static int    f(int a);
    static double f(int a);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: functions that differ only in their return type cannot be overloaded

The reason is a property of call sites. f(3); as a statement discards the result — Chapter 4.1 §5 — so there is nothing in the call to say which f was wanted. A rule that depended on information the call site need not supply could not work, so the language forbids the declaration instead.

The parameters do count, and that is the whole of Chapter 6.1's typo bug. Declaring twice(int) and defining twice(double) declares two functions. By link time the parameter types are part of the mangled name — Chapter 2.6 showed "cycles_for(int, int)" in a linker error for exactly this reason — so twice(int) and twice(double) are unrelated symbols.

A note on the parameter forms from earlier in this module: show(const SampleBlock&) and show(SampleBlock&) are also a legal overload pair, and the choice between them has rules this chapter will not need. Chapter 11.7 is where that pattern becomes useful.

3. The Rank Order

When a call is made, the compiler collects every visible function with that name, discards the ones that cannot accept the arguments at all, and ranks what is left. For the arguments in this curriculum, three ranks are enough:

RankMeansExample with g(int) / g(double)
1 — exact matchthe argument already has the parameter's typeint → g(int)
2 — promotiona widening to int or double that Chapter 4.2 §3 definedchar → g(int)
3 — conversionany other standard conversionlong → g(int)

A better rank always wins. Rank 1 beats rank 2, which beats rank 3, and nothing about the order of declaration or the "closeness" of the numbers enters into it.

Two candidates at the same best rank is an error. The compiler does not guess, and §4 is what that looks like.

Here it is measured, with two overloads and five argument types:

Azvya Education Pvt. Ltd.VLSI Mentor
resolve.cpp — complete program
#include <iostream>

static void g(int v)    { std::cout << "  g(int)    got " << v << '\n'; }
static void g(double v) { std::cout << "  g(double) got " << v << '\n'; }

int main()
{
    int i = 1; double d = 1.5; char c = 'x'; float f = 1.5f; bool b = true;
    std::cout << "g(int):\n";    g(i);
    std::cout << "g(double):\n"; g(d);
    std::cout << "g(char):\n";   g(c);
    std::cout << "g(float):\n";  g(f);
    std::cout << "g(bool):\n";   g(b);
    return 0;
}

Measured output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
g(int):
  g(int)    got 1
g(double):
  g(double) got 1.5
g(char):
  g(int)    got 120
g(float):
  g(double) got 1.5
g(bool):
  g(int)    got 1

Zero warnings. Three of those lines are worth stopping on.

char selected g(int) and printed 120. 'x' is 120 — Chapter 3.6 established that a char holds a small integer. Promotion to int is rank 2; conversion to double is rank 3; so the integer overload wins and the character arrives as a number.

float selected g(double). Promotion to double is rank 2, conversion to int rank 3.

bool selected g(int) and printed 1. Same promotion rule. true is 1.

That last pair is the one that catches people out in practice. A function overloaded on int and on something else will quietly take a bool as an integer, and Chapter 3.6 §6's argument against treating bool as a number applies with extra force once overloads are involved.

4. When Two Candidates Tie

Now the second surprise. The same two overloads, and an argument type that is neither:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    static void g(int v);
    static void g(double v);
    // ...
    long l = 1;
    g(l);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
error: call to 'g' is ambiguous
note: candidate function
note: candidate function

Not "no match" — too many matches. long to int is a conversion. long to double is a conversion. Both are rank 3, neither is better, and the compiler refuses rather than choosing.

The same happens with unsigned int:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    unsigned u = 1;
    g(u);                 // error: call to 'g' is ambiguous

Put the whole set together, because the contrast is the lesson:

ArgumentResultWhy
intg(int)exact match
doubleg(double)exact match
charg(int), prints 120promotion beats conversion
floatg(double)promotion beats conversion
boolg(int), prints 1promotion beats conversion
longambiguousboth are mere conversions
unsigned intambiguousboth are mere conversions

So char, float and bool resolve because each has a promotion available to exactly one candidate. long and unsigned int do not, because reaching either candidate is a plain conversion, and two plain conversions tie.

The fix is to stop the tie, and there are three ways:

Add an exact match:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    static void g(long v);        // now g(l) selects g(long)

Measured: with that overload present, a long argument selects g(long).

Or cast at the call site, saying which you meant — Chapter 4.10 §4's argument, here resolving an overload rather than fixing arithmetic:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    g(static_cast<int>(l));

Or change the design so the overload set does not span int and double at all. Often the honest answer: two functions whose parameter types differ only by an implicit conversion are frequently one function with a single parameter type.

5. What This Model Leaves Out

Chapter 1.3 §8 asked for simplified models to be labelled, so here is the label.

The three ranks in §3 are a working subset. The standard's actual rules are considerably larger, and they bring in cases this chapter has not needed:

  • References and const. How const T& and T& overloads rank against each other, and against a by-value overload.
  • User-defined conversions. Once a type can convert itself to another, Chapter 12.5's territory, there are more ranks below rank 3.
  • Templates. Chapter 18.3 adds function templates, which participate in resolution with rules of their own.
  • Namespaces. Which functions are even visible candidates depends on which namespace a name is found in and on how names have been brought into scope — Chapters 7.6 and 7.7.

Nothing in §3 is wrong within its scope, and it is enough to predict every resolution in Part I. It is not enough to adjudicate an obscure ambiguity in a template-heavy codebase, and when you meet one, the thing to read is the compiler's candidate list rather than a remembered rule.

Which is the practical skill. The error in §4 printed note: candidate function once per candidate. Reading those notes — what the candidates were, and which conversion each would have needed — resolves real ambiguities faster than recalling a rank order.

6. Overloads and Default Arguments Together

Chapter 6.6 §7 closed with a warning and deferred the evidence to here.

Each feature can give one name several call shapes. Using both at once can make a call match two candidates:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void report(int value, int width = 8);     // matches report(5) and report(5, 4)
    void report(int value);                    // also matches report(5)

report(5) now has two candidates, both exact matches on the argument it supplies. That is a rank-1 tie, and §3's second rule applies: the compiler does not choose.

The habit that avoids it is Chapter 6.6 §8's: use one mechanism per function name. Either one function with defaults, or a set of overloads — not both.

7. Designing an Overload Set

Overloading is easy to use and easy to overuse. Four rules cover it.

Overload only for the same operation. show(int) and show(const char*) both display something. open(const char*) that opens a file and open(int) that opens a socket share a word and not an operation; two names would be clearer.

Keep the behaviour consistent across the set. A caller who changes an argument's type should get the same semantics at a different type, not a different algorithm. If the bodies differ in what they mean, the names should differ too.

Avoid overloading on types related by an implicit conversion. g(int) and g(double) is the set that produced §4's ambiguities. If both are genuinely needed, expect to need an exact overload for every integer type callers will actually pass.

Prefer one function when the types can be unified. Taking double alone would have accepted every argument in §4's table without a single ambiguity — at the cost of Chapter 3.5's precision limits for large integers. That is a real trade-off and it is usually the right one for a small overload set.

8. What to Actually Do

Overload when one name describes one operation across several types.

Do not overload on return type. It is not allowed, and the reason is that a call site need not use the result.

Expect promotion to beat conversion. A char, a bool or a float will select the int or double overload, and the character or boolean arrives as a number.

Expect a tie to be an error. Two conversions of equal rank do not get resolved for you.

Read the candidate notes when you get an ambiguity. They are faster than recalling the rank order.

Break a tie with an exact overload or a static_cast at the call site, and consider whether the overload set should span those types at all.

Use defaults or overloads for a given name, never both.

Do not overload on int and double without expecting to add more overloads.

9. Mistakes Worth Making Once

Overloading on return type. functions that differ only in their return type cannot be overloaded.

Expecting show('a') to print a character. Measured: it selected show(int) and printed 97.

Expecting a bool argument to be treated as a bool. Measured: g(true) selected g(int) and printed 1.

Expecting long to pick the int overload "because it is closer". Measured: ambiguous. Closeness is not a rank.

Reading "ambiguous" as "no match". It means too many matches of equal quality.

Mixing defaults and overloads for one name. report(5) then matches two candidates equally.

Overloading on int and double and stopping there. Every other arithmetic type callers pass will tie.

Trusting §3's three ranks in template-heavy code. It is a labelled subset — read the candidate list instead.

10. Interview Relevance

"What is overloading, and what makes two overloads distinct?" Same name, different parameter lists; the return type does not participate. Add the reason — a call site may discard the result, so it carries no information about which return type was wanted — and the answer stops sounding memorised.

"Which overload does this call pick?" The question is testing the rank order. Say exact match, then promotion, then conversion, and that an equal-rank tie is an error. The show('a') → 97 example is the one worth having ready.

"Why is this ambiguous?" Measured here: g(int) and g(double) with a long argument. Both require a conversion, neither is better, and the compiler refuses to guess. Candidates who can also say why refusing is better than tie-breaking are describing a design principle rather than a rule.

11. What You Should Be Able to Say Now

  1. Overloading is several functions sharing one name with different parameter lists.
  2. You have used it since Chapter 2.8 — std::cout << is an overload set.
  3. The return type does not distinguish overloads: functions that differ only in their return type cannot be overloaded, because a call site may discard the result.
  4. Parameter types do, which is why Chapter 6.1 §6's typo became a linker error — the types are in the mangled name.
  5. The working rank order: exact match beats promotion beats conversion.
  6. A better rank always wins, and declaration order is irrelevant.
  7. Measured: char → g(int) printing 120, float → g(double), bool → g(int) printing 1 — promotion beating conversion three times.
  8. Measured: a long argument is ambiguous between g(int) and g(double); so is an unsigned int.
  9. Ambiguous means too many equally good matches, not none.
  10. Fix a tie with an exact overload (measured: g(long) is then selected), a static_cast, or a different design.
  11. §3's three ranks are a labelled simplification — references and const, user-defined conversions, templates and lookup all add rules.
  12. The practical skill is reading the note: candidate function lines.
  13. Defaults and overloads together can make one call match two candidates equally — use one mechanism per name.
  14. The language refuses rather than tie-breaking because a silent switch of target on an unrelated edit is worse than a build failure.

12. Exercises

Exercise 1 — legal or not? For each pair, say whether it is a valid overload set: (a) f(int) and f(double) (b) f(int) and g(int) (c) int f(int) and double f(int) (d) f(int) and f(int, int) (e) f(int) and f(const int&).

Exercise 2 — predict the selection. With void h(int) and void h(const char*), say which is chosen and what it receives: (a) h(5) (b) h('z') (c) h("z") (d) h(true) (e) h(5.0).

Exercise 3 — explain the 97. show('a') printed 97 with overloads for int, double and const char*. Name the rank of each candidate for that argument and say which rule decided it.

Exercise 4 — why ambiguous? With void g(int) and void g(double), explain why a long argument fails while a char argument succeeds. Your answer should name both ranks involved.

Exercise 5 — three fixes. Give three different ways to make g(l) compile for long l, and say which you would ship and why.

Exercise 6 — find the tie. void report(int, int width = 8); and void report(int); are both declared. Say which call is ambiguous, why, and the rule from Chapter 6.6 that prevents it.

Exercise 7 — design the set. You need to display a 32-bit register value, a voltage, and a signal name. Decide how many functions and what their parameters are, and justify each choice against §7's four rules.

13. Summary

  • Overloading is one name, several functions, distinguished by parameter lists.
  • The return type does not count — a call site may discard the result.
  • Parameter types are part of the mangled name, which is Chapter 6.1 §6's linker error.
  • The rank order: exact match > promotion > conversion, and a better rank always wins.
  • Measured: char → g(int) printing 120; bool → g(int) printing 1; float → g(double).
  • Measured: long and unsigned int are ambiguous against g(int)/g(double) — both are mere conversions.
  • Ambiguous means too many matches, and the fix is an exact overload, a cast, or a better design.
  • §3's ranks are a labelled subset; read the candidate notes in real code.
  • Never mix defaults and overloads for one name.
  • C++ refuses rather than tie-breaks, consistently with braces, const and static_cast.

Two chapters remain. The next one is the last mechanical topic in the module, and it answers a question this module has created: Chapter 6.1 recommended putting declarations in a header, and the three parameter chapters have all been one-line functions that it would be absurd to put in a separate source file. But a function definition in a header, included by two translation units, breaks a rule Chapter 2.6's table already named — a name defined twice — and the next chapter measures exactly that failure, then fixes it with a keyword whose name describes something it does not actually guarantee.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.