How C++ Template Partial Ordering Resolves Overloads
When multiple function template overloads match a function call in C++, the compiler relies on template partial ordering to select the "most specialized" template. This mechanism transforms candidate templates by inventing unique placeholder types, tests whether each template can accept the synthesized types of the others through bidirectional deduction, and selects the template that imposes the strictest type constraints. This article details the formal steps C++ compilers follow during partial ordering, illustrates the process with a concrete example, and outlines how ambiguities are resolved.
The Principle of Partial Ordering
Template partial ordering determines which of two matching function
templates is more specialized. A template A is considered
more specialized than a template B if A
accepts a strict subset of the argument types that B can
accept. If a call matches both templates, the compiler picks
A because it represents a more specific implementation.
The Deduction Process: Step by Step
To determine which template is more specialized, the compiler ignores the actual call arguments and performs a theoretical comparison between pairs of candidate templates using the following procedure:
1. Generating Invented Types
For each template under consideration, the compiler replaces its template parameters with unique, fictional types or non-type values (often referred to as invented or synthesized types).
For example, given:
- Template 1:
template<typename T> void f(T); - Template 2:
template<typename T> void f(T*);
The compiler replaces parameter T in Template 1 with a
synthesized type $T1, yielding the parameter type
$T1. For Template 2, T is replaced with
$T2, yielding the parameter type $T2*.
2. Bidirectional Template Argument Deduction
The compiler performs template argument deduction in both directions using these synthesized parameter types:
Direction 1 (Testing if Template 1 can accept Template 2):
Can Template 1's parameter pattern (T) match the synthesized argument of Template 2 ($T2*)?
Result: Yes,Tis deduced as$T2*.Direction 2 (Testing if Template 2 can accept Template 1):
Can Template 2's parameter pattern (T*) match the synthesized argument of Template 1 ($T1)?
Result: No,$T1is a plain type and cannot be decomposed into a pointer typeT*. Deduction fails.
3. Determining the Winner
After testing deduction in both directions:
- If Template 1 can accept Template 2's types, but Template 2 cannot accept Template 1's types, Template 2 is at least as specialized as Template 1, and overall more specialized.
- If both directions succeed, neither is more specialized than the other.
- If both directions fail, neither is more specialized than the other.
In the example above, Template 2 is selected as the most specialized overload for any call passing a pointer argument.
Additional Ordering Rules
When performing the transformation and deduction steps, the standard specifies special handling for specific language constructs:
- References and Qualifiers: For value arguments, top-level references and cv-qualifiers are typically dropped before comparison. If one parameter is a reference and another is by-value, specific tie-breaking rules favor the reference binding based on value category.
- Parameter Packs: A non-pack parameter is considered
more specialized than a function parameter pack. Given
void g(T, Args...)versusvoid g(T), the version without the pack is preferred for a single-argument call. - Concepts and Constraints: Since C++20, if two
templates have identical parameter types, partial ordering evaluates
their
requiresclauses. A template whose constraints subsume (logically imply) the constraints of another is considered more specialized.
Ambiguity Resolution
If the partial ordering comparison fails to find a single template that is strictly more specialized than all other matching candidates, the compiler halts with an ambiguity error. Furthermore, if a non-template function overload matches the call with an equal conversion sequence to the best template, the non-template function is always chosen over the template specialization.