Function Overriding
A derived function overrides a base virtual function only when name and type match exactly.
The Look-Alike Trap
Imagine a base class with a virtual function, and a derived class that defines a function whose name is almost identical to the base version. The derived function looks like it was written to replace the base one. A reader skimming the code would likely assume the two are connected. This section asks whether that assumption is correct, and how a programmer can remove the doubt for anyone who reads the code later.
What do you think happens?
A derived class defines a function whose name differs by one letter from a base class virtual function. Does it override the base function?
Reveal answer
No, because a near match is a different function under the exact name and type rule
A derived class function overrides a base class virtual function only when it has exactly the same name and type. Similar names do not count. A near-match function is a separate function, so appearance alone cannot tell you whether overriding was intended.
The belief behind that guess is common and it is a misconception. A function with a slightly different name, or a slightly different type, may be intended to override the base function. It may also be intended to be a separate function. Appearance does not settle which one was meant. The rest of this article works through the precise rule that decides the question, the keyword that states intent, and the closely related idea of overloading, where one name is shared by several functions.
What a Base Class Call Reaches
Overriding matters because of what happens when a function is called through the base class. The diagram below follows one such call. The caller invokes the base class virtual function. When the derived class contains a function that overrides it, that overriding function is the one that takes the call, and its result returns to the caller. The diagram shows that path. Notice that the path only exists when the derived function really is an override, which is exactly what the matching rule in the next section decides.
The Exact Name and Type Test
A derived class function overrides a base class virtual function only when it has exactly the same name and type as that base function. The type test covers the full signature the base class declared, so a difference in a single parameter type is enough to break the match.
The word exactly carries the weight here. The comparison below uses a generic example with a function named notify that takes one int in the base class. The derived function with the same name and the same parameter type overrides the base function. A derived function whose parameter type is double is a separate function, even though its name matches. A derived function named notif with an int parameter is also separate, even though its signature looks nearly identical to the base one. In each non-matching case, the derived function may have been intended as a replacement, but the rule does not treat it as one.
Intent in Large Hierarchies
The source notes that in large class hierarchies it is not always obvious whether overriding was intended. A hierarchy may have many levels, many base classes, and many functions with similar names. A reader scanning a derived class cannot always tell, from the code alone, whether a near-match function was meant to replace an inherited virtual function or to add something new beside it.
This ambiguity is about the reader, not only the machine. The programmer who wrote the code knows what was intended. The problem is that the intent stays hidden unless the code says it. That gap is what the override keyword is designed to close.
One Name, Several Functions
Function overloading lets several functions share one name when they differ in argument types. Each definition is a separate function with its own parameter list. The shared name does not make one function that handles every type.
The source uses print as its example. It declares three overloads of print with different parameter lists. The name print is therefore not one function that handles every type; it is a set of separate functions that the compiler treats as alternatives. The compiler's choice is made at each call site, so the same name can invoke different functions in different places in the same program.
- Four declarations share the name print
- Each has its own parameter list, so each is a separate function
- The first two take two arguments, but in swapped positions
- The last two take one argument each, of different types
Tracing the Compiler's Choice
For every call to an overloaded name, the compiler follows the same decision. It first collects the functions with that name. It then asks whether one of them is most appropriate for the argument types at that call. If one is, that function is invoked. If no alternative is better than the others, the call is ambiguous and the compiler reports an error. The source describes the outcome of this comparison but does not list the full ranking rules, so this article focuses on observable results: which call succeeds and which is rejected.
Choosing an overload for a single integer argument
Given the overloads print(int, double), print(double, int), print(int), and print(string), which function does the compiler call for print(42)?
- 1Collect the candidates
The call uses the name print, so all four declared print functions are candidates.
- 2Read the argument types
The call passes one argument, the integer 42. Two-argument overloads do not match a one-argument call.
- 3Compare the remaining candidates
The one-argument overloads are
print(int)andprint(string). The integer argument fitsprint(int)and notprint(string). - 4Select the function
The compiler selects
print(int). The choice comes from the argument type at this call site, not from the order in which the functions were declared.
print(42) calls print(int).
Apply the same steps to a call that passes a string literal, such as print with the text D is for Digital. The argument is a string, so the compiler selects print(string). Both calls follow the same four steps, and only the argument types differ between them.
Why print(0,0) Has No Winner
What do you think happens?
The overloads print(int, double) and print(double, int) are both declared, and the program calls print(0, 0). Will the compiler pick one of them, or report a problem?
Reveal answer
It reports an error because the call is ambiguous
Neither overload is better than the other for the arguments 0 and 0, so no single candidate wins. The declarations were never the problem. The problem appears only when a call uses them in a way that does not single out one function.
The two candidates are declared with the same two types, but in swapped positions. One expects the integer first and the other expects it second. The comparison below lays them side by side for the call print(0, 0). Each candidate matches the call in one position and not in the other, so neither is better overall.
| Overload | Expects first argument | Expects second argument | Outcome for print(0, 0) |
|---|---|---|---|
| print(int, double) | int | double | Not better than the other candidate; no single winner |
| print(double, int) | double | int | Not better than the other candidate; no single winner |
Two overloads that swap the same parameter types, compared for one call
Mistakes Beginners Make
Common mistake Assuming a derived function with a similar name overrides the base function
What it looks likeA base virtual function named notify(int) and a derived function named notif(int).
Why it failsOverriding requires exactly the same name and type. A near match is a separate function.
Do this insteadCompare the name and every parameter type against the base declaration, character by character.
Common mistake Believing a one-letter difference in a parameter type is harmless
What it looks likeA base function taking int and a derived function taking double, both named notify.
Why it failsThe type test covers the full signature, so a single differing parameter type breaks the match.
Do this insteadTreat any difference in a parameter type as a reason the derived function is not an override.
Common mistake Believing the compiler will always find the right overload
What it looks likeCalling print(0, 0) when print(int, double) and print(double, int) are both declared.
Why it failsWhen no candidate is better than the others, the call is ambiguous and is reported as an error.
Do this insteadCheck each call against every overload of its name, and make sure one overload is clearly the best match.
Common mistake Treating the override keyword as the thing that makes overriding happen
What it looks likeAdding override to a function whose name or type differs from the base function.
Why it failsThe source describes the explicit form as equivalent to the form without the keyword. It states intent; it does not change which functions match.
Do this insteadUse override to state a real intent to override, and still verify the exact name and type.
Common mistake Assuming the declaration order decides which overload runs; the choice is made from the argument types at each call site
Practice Your Reading
Two overloads are declared: print(int, double) and print(double, int). For each call below, decide whether the compiler selects an overload or reports an ambiguous call: (a) print(42, 3.5); (b) print(0, 0). Then, for a base virtual function notify(int), decide whether a derived function notify(double) overrides it. Explain each answer using the exact name and type rule or the single-best-match rule.
Hints
- Ask whether any one overload is better than the other for the argument types at that call
- Overriding depends only on whether the name and every parameter type match exactly
- Compare each argument position in the call with each overload's parameter list
Key Takeaways
- A derived class function overrides a base class virtual function only when its name and type match exactly. Similar names or types create a separate function.
- The override keyword states that a function is meant to override a base class virtual function. The source describes it as equivalent to the form without the keyword, so its value is clarity of intent in large hierarchies.
- Function overloading lets several functions share one name when they differ in argument types. Each overload is a separate function, and the compiler chooses among them at each call site.
- If no overload is better than the others for a call, the call is ambiguous and the compiler reports an error at the call site.