You would think that the compiler, when resolving overloaded function templates, tries to figure out which of the templates matches the given arguments better. Based on that assumption, a template with a uint8_t
should match a function call with a uint8_t
argument better than a template for int
.
But that's not how template overload resolution works. Template overload resolution (§14.5.6.2) is different from ordinary function overload resolution (§13.3). It first establishes candidate templates and then, rather than trying to check how well each matches the given arguments, it simply establishes which of the two (or more) candidate templates is the most specialized.
Note that this is a matter between the candidate templates only. It does not take into account the given arguments of the function call. (Those are taken into account for type deduction, which is only part of the procedure that establishes the candidate set of templates.)
So it checks whether uint8_t
is more specialized than int
or vice versa (in general – not with respect to the given arguments of the function call at hand). It does this, basically, by checking whether any given uint8_t
argument could (in theory) be used to fill an int
parameter without non-standard conversions, and vice versa. This is the case (in both directions), so neither template is more specialized than the other. Hence the ambiguity cannot be resolved.
The relevant sections of the Standard are as follows.
First, §13.3.3 establishes that when two functions templates (as opposed to two ordinary functions, or one function and one template) compete for a function call, the template overload mechanism is used to select the best:
[...] a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
[...]
— F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.
Then, §14.5.6.2 is very long, but the most relevant parts are:
(2) Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process.
(3) To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (14.5.3) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template. [...]
(4) Using the transformed function template’s function type, perform type deduction against the other template as described in 14.8.2.4.
So the idea here is: Take the uint8_t
template and transform it by replacing the uint8_t
parameter with an actual, synthesized value (I guess that value could be taken from the actual function call, but the Standard doesn't say that). Then use the type deduction process to check if the transformed template, taken as a function call, would "match" the other template (i.e. the int
template), i.e. if the int
parameter of the other template could be deduced without non-standard conversions. The answer is yes, it could.
Then go the other way, take the int
template, synthesize a value and try if this "matches" the uint8_t
template, i.e. if the uint8_t
parameter could be deduced without non-standard conversions. The answer is yes again.
If this works in only one direction, one of the two templates must be more specialized than the other and is chosen to resolve the ambiguity. If it works both ways (as in your case), the ambiguity cannot be resolved.
Note. The entire procedure is in fact more complicated, and its description in the Standard very long, mainly for the following reasons:
- The type deduction process itself is complicated. It does allow for certain implicit conversions (basically, standard-conversions including some related to cv-qualifiers);
- It has a number of special arbitration rules for the case when one candidate parameter is a const-reference and the other in a non-const reference, and some similar cases;
- Each candidate template may give rise to multiple transformed templates, especially when there is more than one template parameter to be deduced;
- The presence of default-arguments as well as template parameter packs complicates the situation further.
uint8_t
is in fact a typedef ofint
(making it essentially the same type) in this case, whereas static_cast is normally used to resolve precedence rules concerning implicit typecasts and the like. Could be wrong on this one, though. – Eteoclestemplate<uint8_t T>
didn't exist... – Denationalizeuint8_t
is a typedef defined incstdint
, a header added in C++11. No new syntax there though. – Eteoclesunsigned int
, it's not a problem about the new fixed size integers types. – Mammillate