C++ / Programming

Templates: specialization

Templates are great. You write once, and you use over and over, with different types. While most people use templates more than they write them, even fewer are aware of the specialization options.

First thing? I’m not a big fan, for the most of it. The reason? If you pull something like this, then you have to maintain more than one version of “kind of” the same function. Not only that, there is a chance, and a pretty big one at that, that you (or someone else) will miss updating the specialized version, and just update the general one. Then, they might do the same for the tests, and you have a serious bug, which will be **very** hard to find. Point being – if you can avoid it, avoid it. But this is for the cases you can’t, or these cases where there is a significant performances improvement.

Start: a simple bigger function

Lets say we have a simple function that finds and return the bigger of two values:

T BiggerValue(T first, T second) { if (first > second) return first; return second; }
Code language: C++ (cpp)

Running something like this will be simple, and will work very well:

cout << BiggerValue(2, 3) << endl; cout << BiggerValue(4.5, 3.1) << endl;
Code language: C++ (cpp)

The problem arises when we try something like this:

char *v2{"Hello world!"}; char *v3{"Apple"}; cout << BiggerValue(v2, v3) << endl;
Code language: C++ (cpp)

Technically – we can’t tell what will be printed. In reality, in most cases, it will be “Apple”. The reason? We’re comparing char *, that is the address of the pointers, not the actual values that they point to, and it will be Apple most of the times because Apple is defines later, so most likely it will be entered into memory later.

Function specialization to the rescue

Since we know that we want to compare the actual values, we can use the strcmp C function, for that, we will need a specialized version of this function:

template <> char *BiggerValue(char *first, char *second) { if (strcmp(first, second) > 0) return first; return second; }
Code language: C++ (cpp)

Note the anatomy of this function: it starts with an empty template tag, that means that this is a specialized version. The function then commence with all former parameters being typed. The body of this function is obviously different. Running this code will yield the right result.

Good example, BAD example

This is a good example of how to achieve specialization; However, this is also a bad coding example. First? Whenever you think about specialization, you must be sure that there isn’t a better option. Now we’re maintaining two functions, which goes against the write once principal.
Second? These are strings. Proper use of string instead of char* will yield the correct results, without the need to “overload” the function.

Specialized classes

As in functions, template classes can also be specialized. For a class like this one

template <typename T> class ValuePrinter { private: T *theValue; public: ValuePrinter(T *value) { theValue = value; } ~ValuePrinter() {} public: void Print() { cout << *theValue << endl; } };
Code language: C++ (cpp)

The specialized version will be

template <> class ValuePrinter<char *> { private: char *theValue; public: ValuePrinter(char *value) { theValue = value; } ~ValuePrinter() {} public: void Print() { cout << theValue << endl; } };
Code language: C++ (cpp)

Again note the motifs: the type T is gone, and the signatures include the known type. Classes, however, has an interesting shortcut, that allows you to only overwrite the method you want to replace. Of course, we’re assuming that there is only one; If you need a bigger structural change, you’re better with a full rewrite. So for a very limited number of methods, you might want to do something like this:

template <> void ValuePrinter<std::vector<int>>::Print() { for(auto i:*theValue) { cout<<i<<endl; } }
Code language: C++ (cpp)

Interesting, we have access to the internal class member theValue even though it’s outside the class; This was design specifically for template usage. BTW, this well known simple loop reminds me of another annoyance suppose to be solved in c++23, which is the range index, but that’s another topic.

Another thing: if you didn’t see it the vector is an int vector. What will happen if you want it for any vector? Well, you can specialize for a template. In that case, you’ll need a full class, specialized for the vector type. In that case by the way, you will be able to use specialized vector of a template:

template <typename T> class ValuePrinter<vector<T>*> { private: vector<T> *theValue; public: ValuePrinter(vector<T> *value) { theValue = value; } ~ValuePrinter() {} public: void Print() { for (auto item:*theValue) { cout<<item<<endl; } } };
Code language: C++ (cpp)

So this code

vector<int> myVector{1,2,3,4,221,0,42}; ValuePrinter<vector<int>*> vp(&myVector);
Code language: C++ (cpp)

Will now work as intended. Note that a regular vector will not! So the type has to be accurate.

Be careful when using this “overloading” methods – they are even harder to read. Go figure what method will actually be called for what type. This one one of the cases where the sheer power of C++ is clashing with good coding practices.

Leave a Reply

Your email address will not be published. Required fields are marked *