C++ Function Overloading and Default Arguments: Resolve Valid and Ambiguous Calls

Learn why default arguments affect viability but not ranking, then resolve five score calls using exact matches, promotions and conversions. Includes ambiguity repairs and call-time default evaluation.

KnowledgeGate Team

Exam prep & CS education

Updated 10 Sep 20266 min read

You may be able to list three overloads and still guess which function a call selects. Two rules explain the difficulty: default arguments can make a function viable, while implicit conversions decide which viable function is best. A tie is a compile-time error, not a runtime choice. Start with C++ Overloading and Default Arguments: Rules and Examples for declarations, valid overloads, ambiguity repairs and exam practice; then apply the four-step resolver to five calls and call-time default evaluation through Coding & Skill Development Courses.

1. What C++ treats as an overload

Function overloading means declaring multiple visible functions with the same name but distinguishable parameter-type lists. The rule here applies to ordinary non-template free functions. Return types and default arguments do not distinguish overloads.

For example, int convert(int); double convert(int); is invalid because only the return type changes. It is not a runnable overload set. Similarly, void tag(int); void tag(const int); declares the same function twice. Top-level const does not distinguish a by-value parameter.

References are different: void read(int&) and void read(const int&) are distinguishable because their referred-to types differ. If you need declarations, parameters, or basic calls first, C Programming & Data Structures covers that foundation, though C does not support function overloading. The broader lesson is that calls must carry enough information to distinguish candidates.

2. The four-step resolution rule

Audit an overloaded call in this order:

  1. Collect the overloads visible at the call site.

  2. Remove candidates that cannot accept the supplied argument count or types. A visible trailing default may help with the count.

  3. Rank the implicit conversion for every explicit argument. For this example, exact match beats promotion, and promotion beats ordinary conversion. Thus, short -> int is a promotion, while int -> long and int -> double are conversions.

  4. Demand one candidate that is no worse for every explicit argument and better for at least one. If there is no unique best candidate, the call is ambiguous.

A default argument affects viability, not rank. Using fewer defaults is not an advantage. Only conversions for arguments actually written in the call are compared. Once the compiler chooses one unique function, it fills that function's omitted trailing arguments with their visible defaults.

3. Worked overload set: resolve five calls without guessing

Use one fixed overload set and apply the audit mechanically:

#include <iostream>

void score(long value) {
    std::cout << "long:" << value << '\n';
}

void score(double value) {
    std::cout << "double:" << value << '\n';
}

void score(int value, int multiplier = 10) {
    std::cout << "scaled:" << value * multiplier << '\n';
}

int main() {
    short s = 6;
    score(4);
    score(4L);
    score(2.5);
    score(s);
    score(4, 3);
}

Call

Best candidate

Why it wins

Output

score(4)

score(int, int = 10)

int is exact; int -> long and int -> double are conversions

scaled:40

score(4L)

score(long)

long is exact

long:4

score(2.5)

score(double)

double is exact

double:2.5

score(s) where s = 6 is short

score(int, int = 10)

short -> int is a promotion; the other routes are conversions

scaled:60

score(4, 3)

score(int, int = 10)

it is the only candidate that accepts two arguments

scaled:12

The calculations are direct. The first call uses the default, so 4 * 10 = 40. For s, the promoted value is 6, so 6 * 10 = 60. In the final call, the written 3 overrides the default 10, giving 4 * 3 = 12.

The exact output is:

scaled:40
long:4
double:2.5
scaled:60
scaled:12
An overload-resolution funnel where short-to-int promotion beats the long and double conversions for score(s), giving output scaled:60.

4. Why defaults create ambiguity instead of preference

Consider this separate, non-runnable fragment:

void score(int);
void score(int, int = 10);
score(4);

Both are viable and receive an exact match for the explicit 4. The compiler neither prefers the one-parameter function nor rewards or penalises the default. The call is ambiguous and produces no output.

Another tie is void mix(long); void mix(double); mix(4);. In this scoped comparison, both int -> long and int -> double have ordinary conversion rank. Neither function is uniquely better, so compilation fails.

Repair the call according to intent. Use mix(4L) for mix(long), mix(4.0) for mix(double), or add mix(int) when integers are a supported API case. For the two score declarations, remove the overlapping default, rename one operation, or supply the second argument as score(4, 10). Prefer a clear API over casts added only to silence ambiguity.

5. Default arguments: declaration, visibility and evaluation

A default normally appears where callers can see it:

int area(int width, int height = 2);

int area(int width, int height) {
    return width * height;
}

The definition must not repeat the same default in the same scope. At a call where the declaration is visible, area(7) supplies height = 2, so 7 * 2 = 14.

A default expression is evaluated when a call needs it:

int factor = 3;

int scale(int value, int multiplier = factor) {
    return value * multiplier;
}

std::cout << scale(4) << '\n';
factor = 5;
std::cout << scale(4) << '\n';
std::cout << scale(4, 2) << '\n';

The outputs are 12, 20, and 8. The first omitted multiplier reads factor as 3, giving 4 * 3 = 12. After the assignment, the next reads 5, giving 4 * 5 = 20. The explicit 2 bypasses the default, giving 4 * 2 = 8. A default is not a stored constant unless its expression is actually constant.

A timeline of call-time default evaluation: scale(4) reads factor 3 for 12, then factor 5 for 20, and scale(4, 2) bypasses it for 8.

6. Traps that change the answer

  • Counting defaults before ranking: defaults make a candidate viable, but conversion quality for explicit arguments selects the winner. In score(4), the exact int match wins even though it needs multiplier = 10.

  • Selecting by assignment context: double result = choose(4); cannot use the left-hand double to rescue declarations that differ only by return type. Those declarations are invalid before the call is considered.

  • Treating every conversion as exact: if only void score(int, int = 10) remains, score(2.9) converts 2.9 to 2, loses the fractional part, and prints scaled:20 because 2 * 10 = 20.

  • Expecting defaults through a function pointer: with auto p = &scale, p(4) is ill-formed because the pointer's function type has two parameters. p(4, 2) is valid and returns 8. Defaults belong to declarations used by direct calls, not to the function type.

7. How exam and interview questions frame the rule

Questions usually ask you to predict output, identify a compile-time error, rank conversion sequences, or review an API whose overload plus default creates ambiguity. Do not start with the function body. First settle whether the call has one unique best candidate.

Try this quick check: void test(long); void test(int, int = 5); short n = 3; test(n);. The route short -> int is a promotion, while short -> long is a conversion. Therefore, test(int, int = 5) wins with values 3 and 5. If its body prints their product, the result is 3 * 5 = 15.

The Programming Languages blog organises tutorials by topic, while Coding for Placements: C, C++, Java and Python provides a structured practice path across those languages. KnowledgeGate currently offers more than ten live practice questions on C++ function and operator overloading; that reflects available practice, not exam frequency.

8. The short version and the next step

Keep this memory card: list visible candidates, keep viable candidates, rank conversions for explicit arguments, and demand one unique best function. Only then fill omitted trailing arguments. A remaining tie means compilation fails.

Hide the table and resolve all five score calls again. Then explain why adding score(int) makes the one-argument integer calls ambiguous but leaves the other calls unchanged. For structured practice, continue with C++ Programming. Write the conversion beside every explicit argument before predicting output.