This topic is unfortunately very complicated! I'm basing myself on this answer and cppreference, while quoting from the current working draft of the standard.
Firstly, according to "Matching of partial specializations":
A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list, and the deduced template arguments satisfy the associated constraints of the partial specialization, if any.
In your example, the context is the statement X<int, int*, 10> f;
, which matches both partial specializations:
template<class T, int I> struct X<T, T*, I> {...} // -> T = int, I = 10
template<class T> struct X<int, T*, 10> {...} // -> T = int
Since this is the case, further checking is required to determine whether one of these candidates is more specialized than the other. That is described under "Partial ordering of partial specializations":
For two partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates:
- Each of the two function templates has the same template parameters and associated constraints as the corresponding partial specialization.
- Each function template has a single function parameter whose type is a class template specialization where the template arguments are the corresponding template parameters from the function template for each template argument in the template-argument-list of the simple-template-id of the partial specialization.
That is very wordy, but as shown by the example it simply comes down to writing the partial specializations as function arguments:
template<class T, int I> void f(X<T, T*, I>); // #1
template<class T> void f(X<int, T*, 10>); // #2
From there we have to look at a different section about the "Partial ordering of function templates". Quoting the relevant paragraphs:
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. [...]
To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs 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.
Using the transformed function template's function type, perform type deduction against the other template as described in [temp.deduct.partial].
In other words, transform #1 and #2 (again) by "instantiating" each with unique arguments, that is, different from any other arguments present:
void f(X<Ut1, Ut1*, Uv1>); // #A
void f(X<int, Ut2*, 10>); // #B
And then use their "function type", being the return type and the argument types, to perform deduction according to yet another section, "Deducing template arguments during partial ordering":
The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template.
This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.
Ok, going by the example on cppreference:
// #1 from #B:
// void(X<T, T*, I>) from void(X<int, Ut2*, 10>): deduction fails
// #2 from #A:
// void(X<int, T*, 10>) from void(X<Ut1, Ut1*, Uv1>): deduction fails
At least, I think that's the case, and therefore the result of this whole ordeal would be that neither template is more specialized than the other.