Chris Gregory

Home Blog LinkedIn Github

Java to C++ - template metaprogramming

10/1/2015

Template metaprogramming has to be one of the coolest ways of abstracting your code. It is very similar to polymorphism because it is polymorphism! The only difference is that template metaprogramming happens at compile time rather than runtime time. Template metaprogramming can be used to mimic functional programming languages or to build abstract ways of using generic code.

So why do we need template metaprogramming? The real reason is that we want to remove stupid duplicate code. Java allows you to use polymorphism and generic programming but these methods only work on classes and not on builtin data types. I implemented a small example in to show how code repetition is bad (underlined are the only changes):

int max(int x, int y) {
    return x > y ? x : y;
}
float max(float x, float y) {
    return x > y ? x : y;
}
double max(double x, double y) {
    return x > y ? x : y;
}

As you can see, we have a lot of uninteresting code that is honestly pretty useless. C++ allows us to abstract over this using a template. This template will work on any type that implements the greater than operator!

template<class T>
T max(const T& x, const T& y) {
    return x > y ? x : y;
}

Don't be fooled when I write class T! It works perfectly well with ints, floats, etc!

Since the template is resolved at compile time, you can even create specializations of a function that will act differently! Let's take all integral types (char, int, double etc) as values rather than references: (note that this only works in C++11 and beyond because of the time that the standard library incorporated it in.)

#include <type_traits>

template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value, T>::type
max(const T& x, const T& y) {
    return x > y ? x : y;
}

template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
max(T x, T y) {
    return x > y ? x : y;
}

Let's look at the line typename std::enable_if<std::is_arithmetic<T>::value, T>::type. The std::is_arithmetic<T> part creates a class at compile time that has one field: value. This field is true if the type given is an arithmetic type (int, float, char, bool, double, unsigned long, etc.), and false if it is not. The ::value then will extract this bool. This means that the std::enable_if<..., T>::type line tells the compiler to only use this implementation only if the bool given in the first parameter is true. Then the ::type extracts the T from it if it is true and allows you to use that as the return type by putting typename before it! All of this functionality is not builtin to the language, allowing you to make your own methods that have their own specifications.

The following code will swap two references of any type implementing a copy assignment operator and copy constructor.

template<class T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

A lot of really cool things are possible with template metaprogramming! I suggest watching these videos if you are interested in learning more. Before I end it though, I will show you a side by side comparison of template meta programming with interfaces in C++.

class incrementable {
    virtual incrementable& operator++() = 0;
    virtual incrementable& operator--() = 0;
};

class ex_1 : public incrementable {
    // private by default!
    int i;

    public:
    incrementable& addOne() { i++; return *this; }
    incrementable& subOne() { i--; return *this; }
};

void add_five_times(incrementable& a) {
    for(int i = 0; i < 5; i++) {
        // use prefix with classes!
        ++a;
    }
}

This method works fine for basic operations but breaks down if you want to intersperse classes and arithmetic types. Let's implement something similar using templates.

#include <type_traits>

template<class T, class = void>
    struct incrementable : std::false_type { };

template<class T>
    struct incrementable
        <T, typename std::enable_if<
                std::is_same<T&, decltype(++std::declval<T>())>::value &&
                std::is_same<T&, decltype(--std::declval<T>())>::value >::type
        > : std::true_type { };

template<class T>
    struct add_able<T, typename std::enable_if<std::is_arithmetic<T>::value>::type>
    : std::true_type { };

template<class T>
    typename std::enable_if< incrementable<T>::value, void >::type
    add_five_times(T& a) {
    for(int i = 0; i < 5; i++)
        ++a;
}

The second and third lines dictate the base case: if none of the other more specialized cases match, then we extend false_type: a class that aliases false to the value type. This means that we only get type as true if the absurd case occurs: the class fits our interface.

Now we have to define under what situations we will have a true_type. The second struct initialization will only trigger if the class defines a prefix ++ and -- operator and both of those operators return a reference to the class. enable_if only defines the type member if the condition is true, meaning that the code won't evaluate at all if it doesn't fit the description of the interface.

The third case will enact on any type that is considered arithmetic: int, float, bool, char, long, etc. For some reason they don't work with the second condition but they should still be considered incrementable.

We then use incrementable by declaring the return type as void if the type is incrementable, and not implementing the function at all if it isn't incrementable. This new function works over all types that worked on the interface example as well as all numeric types! All of this happens at compile time, so there is also no overhead of a virtual function.

As you have seen, template metaprogramming can be very complicated, but it allows you to generalize your code more and push polymorphism to compile time rather than runtime.