RSS | GitHub |
While C++ does support partial specialization of templates, it does not do so for function templates. Instead, the general advice is to use function overloading instead. Sometimes that is not a feasible solution though, so we will see how to emulate partial specialization of function template with a some boiler-plate code.
Assume that we have a formatter
class that converts C++ values of different types into text, such as a JSON writer. We want this class to have a consistent API where any kind of value is written with the formatter::write
function.
The obvious solution to handle any kind of type is to use a member function template for formatter::write
.
As different types have to be formatted in different ways, we need specializations for the different types that our class is going to support. The int
case from the example above is easily added using function overloading.
Adding support for std::map
is more tricky, because we want to invoke different formatting depending on the key_type
of the map. For instance, when key_type
is a string then we want a JSON formatter to output the map as a JSON object, but for all other data types we want to output it as a JSON array of pairs. This is not an arbitrary restriction that I have dreamt up; this is how JSON is defined.
Ideally we would like to be able to write the following:
Unfortunately the above specializations of the write
function are partial and therefore not legal in C++. We need something else to resolve the different map cases. Function overloading cannot be used in this case either, because all types must be fully specialized but our Value
parameter is not specialized in either case.
As in so many other situations, we are going to overcome the limitation with another level of indirection. The basic idea is this:
Use partial specialization of templates to emulate partial specialization of function templates.
In our second attempt, our formatter::write
is a single template function that uses forwarding references, and consequently all function overloads have been removed.1 This function forwards any call to a helper class called formatter::overloader
, which will handle partial specialization for us.
The actual formatting implementations are added as the uniquely named private member functions write_integral
, write_map
, and write_string_map
in the formatter
class.2
The formatter::write
function forwards calls to the formatter::overloader<T>::write
function. We first need to define the general case. This should fail if our input type does not match any of our overloads.
Next we define the int
case. Let us extend this case to handle any integral type while we are at it. The write
function simply calls the appropriate private implementation function on the formatter
class.
Finally we add the two different std::map
cases.
And that is that. A lot of boiler-plate is needed, but is doable to emulate partial specialization of function templates in C++. The examples above used C++11 for convenience, but this technique can also be written in C++03 with the use of Boost type-traits.
Read Item 26: Avoid overloading on universal references in Scott Meyers ``Effective Modern C++’’ if you wonder why. ↩
These implementation functions are part of the boiler-plate code, not of the API. We could just as well have placed them in the formatter::overloader
class. That is simply an implementation detail. ↩