Why does const
-qualifying the types make the second template no longer more specialized?
It doesn't.
The "problem" we're observing here is that overload resolution takes into account a few things before checking what type is more specialized.
Reading the section Best viable function we can see that:
For each pair of viable function F1 and F2, the implicit conversion sequences from the i
-th argument to i
-th parameter are ranked to determine which one is better (except the first argument, the implicit object argument for static member functions has no effect on the ranking)
F1 is determined to be a better function than F2 if implicit conversions for all arguments of F1 are not worse than the implicit conversions for all arguments of F2 [...]
Now let's take a look at both overloads:
template <typename X, typename Y>
auto f(const X a, const Y b) { return a + b; };
int main() {
int* p;
int* q;
f(p, q);
}
Here X = int*
and Y = int*
. No conversions are necessary. It's worth to point out that the types of a
and b
are not const int*
, but int* const
.
Let's now take a look at the second overload:
template <typename X, typename Y>
auto f(const X* a, const Y* b) { return *a + *b; };
int main() {
int* p;
int* q;
f(p, q);
}
Here, in order to get const int*
(the desired type of the argument) from int*
(which is the type of p
and q
), X
and Y
have to be deduced as int*
and then a conversion has to occur. That's because const int*
doesn't mean const
pointer to int
, but a pointer to const int
. Pointer to const int
and pointer to int
are not the same, but the latter is convertible to the former. There is no way to deduce X
and Y
for it to be no conversions in this case.
Thus, overload resolution chooses the candidate with fewer conversions. That overload tries to add two pointers and it results in a compile-time error.