Tuesday, July 23, 2013

Overloading (NOT) Partial Specialization

I haven't put up an entry the past few days because of the confusion I've had about overloading vs. partial specialization. There are a few things I'm absolutely sure of (because a book tells me so):


1) Templates can overload [1].

2) There is no such thing as partial specialization for a template function [1].


For point #1, it simply means that if you have a plain old function (no template parameters) and you also have a templatized function with the same name and template parameters, the templatized function can be used to overload. I like the example I found on the IBM page [2]. The example was:


template<class T> void f(T x, T y) { cout << "Template" << endl; }
void f(int w, int z) { cout << "Non-template" << endl; }
 
 
That is a nice simple example of how the function template overload works. We all know that this works and works really well.

Point #2 is where I had the most trouble. I pounded this statement into my head for the past four days; not only that this was also instilled in me from numerous C++ lectures. For me, the statement is better rewritten: "There is no such thing as partial specialation for a template function, there are only overloads." Think about it- you can overload functions with template parameters. Therefore the following is another example of simple template overloading:


template <typename T> 
void func(T t) { cout << "template <typename T> void func(T t)" << endl; }
 
template <typename T> 
void func(T* t) { cout << "template <typename T> void func(T* t)" << endl; }
 

These are both just overloaded functions. Two unique overloaded functions! Things were fine in my head until I saw the Dimov/Abrahams example (rewritten in terms of above):


// 1 
template <typename T> 
void func(T t) { cout << "template <typename T> void func(T t)" << endl; }
 
// 2
template <typename T> 
void func(T* t) { cout << "template <typename T> void func(T* t)" << endl; }
// 3
template <>
void func<int>(int* i) { cout << "template <int>(int* i)" << endl; }
 
// 4
template <>
void func<int*>(int* i) { cout << "template <int*>(int* i)" << endl; } 
int main(int argc, char**argv) 
{
    int value = 12345;
    func(&value);
}
 
Everyone knows when they see code like this and are asked, "Which overload is called?" that it's a trap. I would have said that the fourth would handle it- and I'd be wrong. The question becomes, why is #3 chosen and not #4? Moreover, say you have the following code (omitting #3:

// 1 
template <typename T> 
void func(T t) { cout << "template <typename T> void func(T t)" << endl; }
 
// 2
template <typename T> 
void func(T* t) { cout << "template <typename T> void func(T* t)" << endl; }

// 4
template <>
void func<int*>(int* i) { cout << "template <int*>(int* i)" << endl; } 
int main(int argc, char**argv) 
{
    int value = 12345;
    func(&value);
}
Surprisingly now, #2 is called! After studying for a bit, I came across what should be a bullet fact:

3) Overload resolution selects only a single, best-fit primary template.

Here is why function #4 is never called: it is an explicit specialization of function #1, NOT FUNCTION #2! Function #3 is a specialization of Function #2. Try the example above out and take out function #1:

// 2
template <typename T> 
void func(T* t) { cout << "template <typename T> void func(T* t)" << endl; }

// 3
template <>
void func<int>(int* i) { cout << "template <int>(int* i)" << endl; }
 
// 4
template <>
void func<int*>(int* i) { cout << "template <int*>(int* i)" << endl; } 
int main(int argc, char**argv) 
{
    int value = 12345;
    func(&value);
}

This WILL NOT COMPILE. The specialization for #4 requires that a primary template already exists. Function #3 can exist because its primary template is Function #2.  That is the key to understanding the Dimov/Abrahams problem, what specialization is tied to what primary template. Understand that first and the whole thing comes together.

Once it's understood the tie in between the primary and specialized templates we can now determine which primary template the compiler will choose to handle the overload. Given a choice between:


// 1 
template <typename T> 
void func(T t) { cout << "template <typename T> void func(T t)" << endl; }
 
// 2
template <typename T> 


void func(T* t) { cout << "template <typename T> void func(T* t)" << endl; }


and the input to func being an int*, the compiler will clearly pick option #2. The compiler then checks to see if option #2 has any specializations and if any of those fit. Since option #2's overloads were:

// 3
template <>
void func<int>(int* i) { cout << "template <int>(int* i)" << endl; }

It will pick #3 as the function used! Beautiful in how much sense it makes. If Option #3 isn't available, it would default back to the primary template which is Option #2. 

So in conclusion, the rules for understanding template function overloading are:

1) Templates can overload [1].

2) There is no such thing as partial specialization for a template function.

3) Overload resolution selects only a single, best-fit primary template.

4) Explicit specializations are chosen only if they fit the singly chosen best-fit primary template only, specializations applied to any other primary template are ignored.

 
REFERENCES

[1] - Exceptional C++ Style, Herb Sutter. Item #7: Why Not Specialize Function Templates?

[2] - http://publib.boulder.ibm.com/infocenter/comphelp/v7v91/index.jsp?topic=%2Fcom.ibm.vacpp7a.doc%2Flanguage%2Fref%2Fclrc16overl_fn_templates.htm


No comments:

Post a Comment