☑ Function Template Fun

Function template specialisation is a tricky beast — the short answer is, avoid it.

stencil lake

For my sins I’ve recently had to implement some fairly generic code in C++ using function templates, and I’ve realised something a little quirky about then that hadn’t occurred to be before.

For anybody who’s unaware of templating in C++ this post probably won’t be of much interest, but as a refresher of the syntax you can declare a class template like this:

1
2
3
4
5
6
7
8
template <typename Type>
class MyIncrementer
{
    Type increment(Type value)
    {
        return value + 1;
    }
};

Aside from having to define these classes in the header file (because the compiler requires the implementation of each method to be available to instantiate the template) this works more or less as you’d expect. You can also specialise templates, providing an alternate implementation for particular types:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <>
class MyIncrementer<std::string>
{
    std::string increment(std::string value)
    {
        unsigned long int v = strtoul(value.c_str(), NULL, 10);
        std::stringstream out;
        out << v + 1;
        return out.str();
    }
};

This works really well for classes, but you can also template functions and methods in the same way:

1
2
3
4
5
template <typename Type>
Type myIncrementFunction(Type value)
{
    return value + 1;
}

And finally, you can also specialise them in the same way1:

1
2
3
4
5
6
7
8
template <>
std::string myIncrementFunction(std::string value)
{
    unsigned long int v = strtoul(value.c_str(), NULL, 10);
    std::stringstream out;
    out << v + 1;
    return out.str();
}

So, that’s all there is to it right? Well, not quite. You see, function templates are slightly different beasts to class templates. For example, you can still overload templated functions just like you can with regular functions. The following are all quite valid at the same time:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Template on any type.
template <typename Type>
void myFunction(Type value)
{ /* ... */ }

// Overload previous template with a pointer version.
template <typename Type>
void myFunction(Type *value)
{ /* ... */ }

// A simple non-templated overload.
void myFunction(int* value)
{ /* ... */ }

// A template specialisation.
template <>
void myFunction<int*>(int *value)
{ /* ... */ }

Clearly there’s some potential for ambiguity here, and since this is all valid syntax then the compiler can’t just give you a warning. The first rule is that non-templated methods (the third one above) always take precedence over templated ones. So, if myFunction() is passed an int* then the third method above will always be called.

Essentially this means that the fourth method declaration will never be called, right? Well, almost — you can actually force it to be invoked by calling it as myFunction<int*>(...). But otherwise, the non-templated method is always counted as a better match.

However, there is still potential for more confusion here. Let’s omit the non-templated method and reorder the definitions slightly2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// The same basic template as above.
template <typename Type>
void myFunction(Type value)
{ /* ... */ }

// The same specialisation as above, but moved earier.
template <>
void myFunction<int*>(int* value)
{ /* ... */ }

// The same pointer template as above.
template <typename Type>
void myFunction(Type *value)
{ /* ... */ }

Now we call myFunction() with an int* again. Since we’ve removed the non-templated method, the specialisation will be called, right?

WRONG!3

Our simply re-ordering of the declarations means that the specialisation is now an explicit specialisation of the first template, since the second didn’t exist at the point it was defined. When the second template is added, it makes a better match for int* than the first template, so the specialisation of the first template isn’t even considered.

The general rule of overloading templates like this is that only generic templates themselves overload on each other, specialisations aren’t considered until a base template has been chosen, even if there’s a specialisation which would have made a better match.

Confusing, eh? Still, at least there’s an easy moral lesson here — don’t specialise methods. In general you shouldn’t have to, since you can just overload them with non-templated methods which will take precedence anyway, which is probably what you wanted in the first place.

Or just avoid templates entirely. And C++. Use Python. Or just take a walk in the park instead. Look, there’s a bird making a nest. Hopefully out of the shredded remains of the C++11 standard.


  1. Note that the <std::string> suffix to the function name can only be ommitted if the compiler can unambiguously determine the template type. 

  2. This code example is essentially one presented by Peter Dimov and Dave Abrahams — I found it in an article originally written about this which I’ve fairly shamelessly plagiarised here. 

  3. I can’t really blame you for getting it wrong, however. Especially if you didn’t — it’s all a bit presumptuous of me, really, Let’s move on. 

3 Apr 2013 at 1:12PM by Andy Pearce in Software  | Photo by rawpixel.com on Unsplash  | Tags: c++ c++-templates