Function Templates Partial Specialization in C++
Why doesn’t C++ allow partial specialization on function templates? Such was the question I asked to you, readers of Fluent C++, in the post covering Template Partial Specialization. Not because I wanted to test you, but simply because I couldn’t find the answer.
And oh boy did I get an answer.
The post received comments, questions, suggestions and discussions, and even if the article covered template partial specialization in general, most of the reactions revolved around the subject of function template partial specialization. And I want to thank /u/sphere991, /u/quicknir, rr0ki and Simon Brand in particular for their contributions.
Each comment pointed to a particular facet of the subject. Now what I want to share with you is how, put together, they allowed me to take a step back and have a wider vision on function template partial specialization. I’m excited to share this big picture with you because it’s exactly what I wished I could read when I was searching for more info on this, and that I couldn’t find anywhere.
Function specialization? Just overload!
When you think about it, template specialization comes down to picking the right implementation for a given type. And the resolution occurs at compile time.
Now compare this with function overloading: it consists in picking the right function for a given type of argument. And the resolution also occurs at compile time.
In this perspective, these two features look very similar. Therefore, it is only normal that you can achieve something equivalent to function template (partial or total) specialization with function overloading. Let’s illustrate with an example.
Consider the following template function f
:
template <typename T> void f(T const& x) { // body of f }
Say that we want a specific implementation when T
is std::string
.
We could either specialize f
:
template <> void f<std::string>(std::string const& x) { // body of f for std::string }
or we could simply overload:
void f(std::string const& x) { // body of f for std::string }
Either way, the execution will go through the specific implementation when you pass f
a string.
The same goes for partial specialization. Say we want a specific implementation of f
for vectors. We can’t write it with partial specialization since the following would be illegal:
// Imaginary C++ template <typename T> void f<std::vector<T>>(std::vector<T> const& v) { // body of f or vectors }
But we can write it with overloading instead:
template <typename T> void f(T const& x) // #1 { // body of f } template <typename T> void f(std::vector<T> const& v) // #2 { // body of f for vectors } f(std::vector<int>{}); // this call goes to #2
and we get the desired effect just as well.
What if you can’t overload
Is this to say that there is no case for partial specialization on template functions? No! There are cases where overloading won’t do.
Overloading works for template arguments, which arguably represent a fair share of use cases for template functions. But what if the template is not in the argument? For instance, it could be in the return type of the function:
template <typename T> T f(int i, std::string s) { // ... }
Or it could even be nowhere in the function prototype:
template <typename T> void f() { // ... }
The common point between those cases is that you have to specify the template type explicitly at call site:
f<std::string>();
In such cases, overloading can’t do anything for us, so for those cases I believe there is a case for partial specialization on function templates. Let’s review our options for working around the fact that C++ does not support it natively.
Fall back on overloading
This is the technique that Simon Brand proposed. It consists in adding a parameter that carries the information about what type T
is. This parameter, type
, is something that just carries another type T
:
template <typename T> struct type{};
(we could also omit the name T
here since it isn’t used the body of the template.)
This allows to return to the case where we can use overloading instead of specialization.
Consider the following example to illustrate. We want to design a template function create
that returns an object of type T
initialized by default:
return T();
except when the type to be returned is a vector, in which case we want to allocate a capacity of 1000 to anticipate for repeated insertions:
std::vector<T> v; v.reserve(1000); return v;
So we want a default implementation, and one for all vector<T>
for all T
. In other terms, we need to partially specialize f
with vector<T>
.
Here is how to achieve this with this technique:
template <typename T> struct type{}; template <typename T> T create(type<T>) { return T(); } template <typename T> std::vector<T> create(type<std::vector<T>>) { std::vector<T> v; v.reserve(1000); return v; } template <typename T> T create() { return create(type<T>{}); }
Fall back on class template partial specialization
Even if we can’t do partial specialization on function template, we can do it for class templates. And there is a way to achieve the former by reusing the latter. To see the cleanest way to do this, you can refer to the post Template Partial Specialization where I get into this (and more) in details.
Whatever you do, don’t mix specialization and overloading
When you use a set of overloads for several implementations of a function, you should be able to understand what’s going on.
When you use a set of (total) specializations for several implementations of a function, you still should be able to understand what’s going on.
But when you mix both overloading and specializations for the same function, you’re entering the realm where the magic, the voodoo and the Bermuda Triangle live, a world where things behave in an inexplicable way, a world where you’re better off not knowing too many explanations about because they could suck you in it and your mind will end up in a plane cemetery and pierced with spikes carrying dark magic spells.
To illustrate, consider this insightful example given by /u/sphere991 that says it all:
template <typename T> void f(T ); // #1 template <typename T> void f(T* ); // #2 template <> void f<>(int* ); // #3 f((int*)0); // calls #3
but:
template <typename T> void f(T ); // #1 template <> void f<>(int* ); // #3 template <typename T> void f(T* ); // #2 f((int*)0); // calls #2 !!
The mere order of declarations of the overloads and the specializations determines the behaviour of the call site. Reading this piece of code sends a chill down my spine. Brrrr.
Whichever technique you choose, don’t mix function overloading and function specialization for the same function name.
Will C++ support function template partial specialization?
We’ve seen when we need partial specialization for function templates and how to emulate it. But we haven’t answered our original question: Why doesn’t C++ allow partial specialization on function templates?
The only element of answer that I got was provided to me by rr0ki in this comment, referring to an old document written by Alistair Meredith. If I summarize what this document says, the feature has been considered a while ago, and left out because the concept_maps could do the job instead. But concept_maps are part of the version of concepts that was abandoned since then!
The way I interpret this is that there is nothing wrong in allowing function template partial specialization in C++, but we don’t know if it will be in the language one day.
Related articles:
Don't want to miss out ? Follow:   Share this post!