user1494080 user1494080 - 1 month ago 5
C++ Question

How to determine the primary template of a function specialization?

It is usually quite intuitive what the primary template of a function template specialization is, however, I am looking for the formal rules to understand the more surprising situations. For example:

template <typename T, typename U>
void f(T, U) {} // (1)

template <typename T>
void f(T, T) {} // (2)

template <>
void f(int, int) {} // (3); specializes (2), not (1); why?


Theoretically, (3) could also be a specialization of (1), but as experiments show it is not.

Answer

Let's focus on the declaration of the generic templates (1) and (2). These are two distinct templates, e.g. (2) is not a specialization of (1). Ok, now when we write a specialization:

template <>
void foo(int, int) {}

When deducing which template to specialize, the compiler will identify two candidates. Then, it must chose which is the best fit. The process for such a choice is called "partial ordering of function templates". Chosen quote:

When the same function template specialization matches more than one overloaded function template (this often results from template argument deduction), partial ordering of overloaded function templates is performed to select the best match.

Let's call S the set of matching templates. Then, for each pair (f1, f2) in S, the compiler will transform f1 by applying dummy types (resp. values) on its type (resp. non type) parameters. Then it tries to match it against f2. Then it does the same procedure by transforming f2 and trying to match it against f1. At the end, after going through each pair, the compiler can determine which template candidate is the most specialized. If it fails to do so the compilation fails.

In our case we've got two matching templates so we apply the procedure described above:

  • Transformed (1) applied to (2): Say foo with T = T1 and U=T2. It tries to match with (2): deduction fails
  • Transformed (2) applied to (1): foo(T1, T1), when applied to (1), it resolves as T = T1 and U = T1.

From this procedure, the compiler deduces that (2) is more specialized than (1) and your specialization goes for (2). The same process is applied when making the call:

foo(1, 1);