As a supplement for the existing answers, here aims to provide an
explanation strictly aligning with the C++20 Standard rules, yet more
intuitive one.
In short, this is related to the internal/behind-the-scence process of
turning "Templates" into "Normal C Functions"(done by compilers,
restricted by the rules mandated by C++ Standard), which leads to the
result that the behavior of "Select from two normal function
candidates" is way different from that of "Select from two template
function candidates".
In your scenario, when you compile:
int main () { foo<0>(); }
against the foo
s definitions:
template <int I> void foo() { ... << "I: " << ... } // ft1
template <short S> void foo() { ... << "S: " << ... } // ft2
the function foo
is overloaded due to its two template
definitions(ft1
and ft2
),
when a function template is overloaded like this, an important
property Partial Ordering(13.7.6.2) among its all definitions will be
determined, with this ordering, the compiler is confident enough to
select most appropriate one from them, so that there is
only one definition being invoked at runtime, which is exactly the
behavior we want when using normal C function.
Okay then, what the heck is "Partial Ordering"? The most detail of it
is present in C++20 Standard's 13.7.6.2. In human language terms, it
is a result of two steps:
- Turn each function template into the more specific one.
- Compare the generated one at step1 to the others' original form.
Since these are parts of the underlying algorithm, the picture might
not be much clear at this point, just keep in mind that they are used by
compiler to
robustly determine "Which function template should be selected". And
elaborating the whole algorithm is kind of stuff that the Standard would
do, not here. C++20 Standard's 13.7.6.2, 13.10.2.4, 13.5.4 contain the detail of it,
check it if you're curious about.
Let's try to apply the mechanism described above to your scenario.
After the 1st step, the two function templates will be tranformed, by
substituting one by one: the actual type for Type Template Parameter,
the non-type
value for the Non-type Template Parameter, the class template for the
Template Template Parameter. Here, we substitue 0
for S
/I
,
resulting in two transformed ones Tft1
and Tft2
:
template <0> void foo() { ... << "I:" << ... } // Tft1
template <0> void foo() { ... << "S:" << ... } // Tft2
I emphasize the "value" because your scenario only covers the Non-type
Template Parameter.
As you can see, each of two transformed function templates is
equivalent to each other, except the function body, which has nothing
to do during this compile-time process.
Let's continue and go step 2. What happens here are
effectively some comparisons. Specifically, we compare Tft1
to ft2
, and
deduce that the S
could be 0
, which means deduction
succeeds, which means our 0
is as specialized as original S
, in
other words, when we use 0
in place of a type short
, the semantics
is more specific, realizing this is important because what we are
doing is making our template from a generic form to a specialized form.
With this relationship, Standard(13.10.2.4-10) tells us that Tft1
is at least as specialized as Tft2
. Obviously
the same holds when comparing Tft2
to ft1
, so Tft2
is also as
specialized as Tft1
. Recall that our goal is to find the exact one
that is more specialized than all the others. Unfortunately, combining these two, we cannot
determine which one (of Tft1
, Tft2
) is more specialized than the
other one, since they are both as specialized as each other (there is
a formal definition for at least as specialized as in 13.10.2.4-8, more specialized than in
13.10.2.4-10). Hence we consider both function templates as valid
candidates after step2, since there are no C++20's "Constraints"
involved in our scenario, they both are final valid candidates.
After all of these mess, the compiler got not one, but two valid
function template candidates, which leads to an ambiguous result. And
the compiler should complain about that. Here answers your 1st question.
The most noteworthy point in your scenario is that, when
transforming the very orginal function templates, for their non-type
template parameters, the compiler should synthesize the values for
them, not the type. In other words, the synthesized value
contains no information about its original type. Here answers your 2nd
question: no matter how you cast your 0
into (int)0
or (short)0
,
only the eventual value will be used, which is a plain 0
.
As long as the value fits in the type of the
parameter, it should be fine and everything in this process should go
on, in other words, the corresponding function template should be a
valid candidate. We can prove it by slightly changing your example:
template <int I> void foo() { ... << "I: " << ... } // ft1
template <short S> void foo() { ... << "S: " << ... } // ft2
int main () { foo<32769>(); } // #1 Good, 1 candidate 'ft1'
int main () { foo<32768>(); } // #2 Good, 1 candidate 'ft2'
int main () { foo<32767>(); } // #3 ambiguous, 2 candidates 'ft1' and 'ft2'
int main () { foo<32766>(); } // #4 ambiguous, 2 candidates 'ft1' and 'ft2'
// ...
int main () { foo<0>(); } // #5 ambiguous, 2 candidates 'ft1' and 'ft2', your scenario
// ...
On a machine where short
resides in [-32768, 32768)
and int
resides in [-2147483648, 2147483648)
, #1~2
can compile and ft1
will
be selected, this is because the value we supplied cannot
fit in short
, thus ft1
will be the only candidate, nothing
ambiguous! It is not true for the rest of them, since the values suppiled
by them can fit in both short
and int
, following the steps we
described above, two identical candidates are waiting for the pick,
thus an ambiguous result.
If you doubt this, try compile and test '#1~5' on your machine. That
gives you a more intuitive sense.
This is not an exhaustive explanation for "Partial Ordering", see
C++20 Standard N4849 sections 13.7.6.2, 13.10.2.4, 13.5.4 and other related parts for details. For
"Specialized Than" terms, check another comprehensive
answer.
foo(i)
the compiler uses the type ofi
for template deduction. When usingfoo<i>()
the compiler is no longer using the type ofi
but its value since the template is<int I>
. Since the value ofi
can satisfy both templates you are getting the ambiguity error. – Jazminejazzf()
; you're asking the compiler to differentiate betweenfoo<0>()
andfoo<0>()
(where one is a zero of typeint
and the other a zero of typeshort
). – Puddleint
orshort
, why is ambiguous in this case? – Affirmation