In the following example, 0 behaves in a special way: it chooses a different overload than one would expect for one example function call. I would like to know why. My understanding is also below.
#include <iostream>
template<typename T>
void f(T a) {
std::cout << "first" << std::endl;
}
template<typename T>
void f(T* a) {
std::cout << "second" << std::endl;
}
int main()
{
f(0);
f<size_t>(0);
f<size_t>(0UL);
f(1);
f<size_t>(1);
}
Output:
first
second
first
first
first
My understanding:
f(0) - template argument deduction, integer literal 0 is int type, hence first f is chosen with T=int
f<size_t>(0) - explicit template instantiation with integer promotion, chosen type is T=size_t, first function is chosen and 0 is promoted from int to size_t (I AM WRONG HERE)
f<size_t>(0UL) - same as above but with no promotion (0 is already type size_t)
f(1) - same as 1.
f<size_t>(1) - same as 2. (I am right here for some reason??)
NOTE:
I know that 0 is implicitly convertible to a null pointer:
A null pointer constant is an integer literal (5.13.2) with value zero or a prvalue of type std::nullptr_t
However, I also know from the standard that promotion has higher priority than conversion:
Each type of standard conversion sequence is assigned one of three ranks:
- Exact match: no conversion required, lvalue-to-rvalue conversion, qualification conversion, function pointer conversion,(since C++17) user-defined conversion of class type to the same class
- Promotion: integral promotion, floating-point promotion
- Conversion: integral conversion, floating-point conversion, floating-integral conversion, pointer conversion, pointer-to-member conversion, boolean conversion, user-defined conversion of a derived class to its base
The reason
f<size_t>(0)calls/uses the second overloadf(T*)is because of partial ordering rules. In particular, the second overloadf(T* a)is more specialized than the first overloadf(T a).Note that if we were to have ordinary(non-template) functions say
void f(std::size_t*)andvoid f(std::size_t)then the callf(0)would've been ambiguous but with function templates(as in your case) the callf(0)prefers/selects thevoid f(T*)version because it is more specialized thanvoid f(T)Because implicit conversions are not considered during template argument deduction. This means that for the call
f(0)only the first overloadvoid f(T a)is viable. In other words, the second overloadvoid f(T* a)is not even viable for the callf(0).You can verify this in this demo: