In my opinion, the most complex rules in the C++ language have to do with the ordering of function templates and class template specializations. They are dense, difficult to follow, underspecified, involve jumping from section to section in the standard, and, on top of all of that, the major compilers don’t even agree with the standard anyway. Let’s explore what I mean.

When doing overload resolution, there’s a lot of steps we go through to determine which function to actually call. We rank different kinds of conversions, we prefer some kinds of references to others, we prefer non-templates to templates, all in careful order. The very last tie-breaker, before we give up and consider the call ambiguous, is to determine if one function template is more specialized than another. A simple example:

We have two viable candidates here, both of which take an int* and so exactly match the argument. How do we determine which function to call? We resort to [temp.func.order]: partial ordering of function templates. In layman’s terms, we try to pick which function template can apply to the fewest amount of types. In this case, it’s easy for us to conceptualize the two functions by just diagramming them:

foo<int*> (#1) vs foo<int> (#2)

Obviously, #2 is more specialized, since pointer types are a strict subset of all types. That’s all well and good that we can figure this out — but what do the rules say about how the compiler is supposed to reason about this?

The process is, roughly, the compiler synthesizes new types for each template parameter of one function template and attempts to perform template deduction against the other function template. It then does this in the reverse order. If deduction succeeds in both directions, or neither direction, neither function template is more specialized than the other. If deduction only succeeds in one direction, then that is the reverse direction of specialization.

In this simple example, we first attempt to deduce #1 from #2. We synthesize some new type, call it UniqueA , and attempt to perform deduction:

Function: template <class T> void foo(T ); // #1

Arguments: UniqueA*

This deduction succeeds, with T=UniqueA* . Now, we do the reverse. We synthesize another new type, call it UniqueB , and attempt to perform deduction:

Function: template <class T> void foo(T* ); // #2

Arguments: UniqueB