Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 659
user1494080 Avatar asked Oct 27 '16 12:10

user1494080


People also ask

What is function template specialization?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.

What is the specialty of a template function give example?

Template in C++is a feature. We write code once and use it for any data type including user defined data types. For example, sort() can be written and used to sort any data type items. A class stack can be created that can be used as a stack of any data type.

What are templates How are templates used to define classes and functions?

Class Templates like function templates, class templates are useful when a class defines something that is independent of the data type. Can be useful for classes like LinkedList, BinaryTree, Stack, Queue, Array, etc. Following is a simple example of a template Array class.

What are the two types of templates?

There are two types of templates in C++, function templates and class templates.


1 Answers

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 during overload resolution when the compiler focuses on a particular call.

An example illustrating all this procedure is the following (taken from @Yakk's comment):

template <typename T, typename U>
void f(T, U) { std::cout << "f(1)\n"; }     // f(1)

template <typename T>
void f(T, T) { std::cout << "f(2)\n"; }     // f(2)

template <>
void f(int, int) { std::cout << "f(3)\n"; } // f(3); specializes f(2), not f(1); why?

// Now the same specialization but without any template overload...
template <typename T, typename U>
void g(T, U) { std::cout << "g(1)\n"; }     // g(1)

template <>
void g(int, int) { std::cout << "g(3)\n"; } // g(3); No ambiguity, specializes g(1)

Next, let's perform a few calls:

f(1, 1);            // Prints f(3)
f<int>(1, 1);       // Prints f(3)
f<int, int>(1, 1);  // Prints f(1)
f(0.1, 0.2);        // Prints f(2)

g(1, 1);            // Prints g(3)
g<int, int>(1, 1);  // Prints g(3)

All this can be seen in action here - copied from @Yakk's comment.

like image 178
Rerito Avatar answered Nov 15 '22 15:11

Rerito