ACCU Buttons

Functional Programming Using C++ Templates (Part 1)

Template metaprogramming can initially seem baffling, but exploring its link to functional programming helps shed some light on things.

Introduction

Computing can be a surprisingly deep field at times. I find that the more I learn about it, the more I'm struck by quite how many similarities there are between different areas of the subject. I was browsing through Andrei Alexandrescu's fascinating book Modern C++ Design recently when I read about a connection which I thought was worth sharing.

As I suspect most of you will already be aware, C++ can be used for something called template metaprogramming, which makes use of C++'s template mechanism to compute things at compile time. If you take a look at a template metaprogram, however, you'll find that it looks nothing like a 'normal' program. In fact, anything but the simplest metaprogram can start to look quite intimidating to anyone who's unfamiliar with the idioms involved. This makes metaprogramming seem hard, and can put people off before they've even started.

Surprisingly, the key to template metaprogramming turns out to be functional programming. Normal programs are written in an imperative style: the programmer tells the computer to do things in a certain order, and it goes away and executes them. Functional programming, by contrast, involves expanding definitions of functions until the end result can be easily computed.

Programmers who have studied computer science formally at university are likely to have already come across some form of functional programming, perhaps in a language such as Haskell, but for many self-taught programmers the idioms of functional programming will be quite new. In this article, I hope to give a glimpse of how functional programming works, and the way it links directly to metaprogramming in C++.

For a more detailed look at functional programming, readers may wish to take a look at [Thompson] and [Bird]. Anyone who's interested in template metaprogramming in general may also wish to take a look at the Boost MPL library [Boost]. Finally, for a much deeper look at doing functional programming in C++, readers can take a look at [McNamara].

Compile-time lists

As a concrete example, I want to consider a simple list implementation. For those who are unfamiliar with them, Haskell lists are constructed recursively. A list is defined to be either the empty list, [], or an element (of the appropriate type) prefixed, using the : operator, to an existing list. The example [23,9,84] = 23:[9,84] = 23:9:[84] = 23:9:84:[] shows how they work more clearly. Working only with lists of integers (Integers in Haskell) for clarity at the moment, we can define the following functions to take the head and tail of a list:

The head function takes a list of integers and returns an integer (namely, the first element in the list). The tail function returns the list of integers remaining after the head is removed. So far, so mundane (at least if you're a regular functional programmer).

Now for the interesting bit. It turns out that you can do exactly the same thing in C++, using templates. (This may or may not make you think 'Aha!', depending on your temperament.) The idea (à la Alexandrescu) is to store the list as a type. We declare lists of integers as follows:

struct NullType;
template <int x, typename xs> struct IntList;

The NullType struct represents the empty list, []; the IntList template represents non-empty lists. Using this scheme, our list [23,9,84] from above would be represented as the type IntList<23, IntList<9, IntList<84, NullType> > >. A key point here is that neither of these structs will ever be instantiated (that's why they're just declared rather than needing to be defined): lists are represented as types here rather than objects.

Given the above declarations, then, we can implement our head and tail functions as shown in Listing 1.

Already some important ideas are emerging here. For a start, if we ignore the fact that the C++ version of the code is far more verbose than its Haskell counterpart (largely because we're using C++ templates for a purpose for which they were never designed), the two programs are remarkably similar. We're using partial template specialization in C++ to do the job done by pattern-matching in Haskell. Integers are being defined using enums and lists are defined using typedefs (remember once again that lists are represented as types).

Using these constructs is rather clumsy. A program outputting the head of the list [7,8], for example, currently looks like:

To improve this sorry state of affairs, we'll use macros (this is one of those times when the benefits of using them outweigh the disadvantages). In a manner analogous to that used for 'typelists' in Modern C++ Design, we define the macros in Listing 2 to help with list creation.

The improvement in the readability and brevity of the code above is striking:

std::cout << HEAD(INTLIST2(7,8)) << std::endl;

From now on, we will assume that when we define a new construct, we will also define an accompanying macro to make it easier to use.

Outputting a list

Before implementing some more interesting list algorithms, it's worth briefly mentioning how to output a list. It should come as no surprise that the form of our output template differs from the other code in this article: output is clearly done at runtime, whereas all our other list manipulations are done at compile-time. We can output lists using the code in Listing 3.

Sorting

Computing the head and tail of a list constructed in a head:tail form may seem a relatively trivial example. Our next step is to try implementing something a bit more interesting: sorting. Perhaps surprisingly, this isn't actually that difficult. The analogy between functional programming in Haskell and compile-time programming in C++ is extremely deep, to the extent that you can transform Haskell code to C++ template code almost mechanically. For this article, we'll consider two implementations of sorting, selection sort and insertion sort (it would be just as possible, and not a great deal harder, to implement something more efficient, like quicksort: I'll leave that as an exercise for the reader). I've confined my implementation to ordering elements using operator<, but it can be made more generic with very little additional effort.

A simple selection sort works by finding the minimum element in a list, moving it to the head of the list and recursing on the remainder. We're thus going to need the following: a way of finding the minimum element in a list, a way of removing the first matching element from a list and a sorting implementation to combine the two. Listing 4 shows how we'd do it in Haskell.

The important things to note here are that each function in the Haskell code corresponds to a C++ template declaration, and each pattern-matched case in the Haskell code corresponds to a specialization of one of the C++ templates.

Implementing insertion sort is quite interesting. The essence of the algorithm is to insert the elements one at a time into an ordered list, preserving the sorted nature of the list as an invariant.

Translating the insert function to C++ is not entirely trivial. The problem is that we need to generate one of two different types depending on the value of a boolean condition, which is non-obvious. There are (at least) two solutions to this: we can either rewrite the Haskell function to avoid the situation, or we can write a special C++ template to select one of two typedefs based on a boolean condition.

It turns out that in C++ this still isn't as efficient as it could be. The culprit is in the second specialization of Insert - by defining the before and aftertypedefs in the specialization itself, we force them both to be instantiated even though only one is actually needed. The solution is to introduce an extra level of indirection (Listing 7).

This solves the problem, because now the chosen IntList template only gets instantiated if it is actually needed.

Maps and filters

One of the best things about writing in a functional language has traditionally been the ability to express complicated manipulations in a simple fashion. For example, to apply the same function f to every element of a list xs in Haskell is as simple as writing map f xs. Similarly, filtering the list for only those elements satisfying a boolean predicate p would simply be filter p xs. A definition of these functions in Haskell is straightforward enough:

Achieving the same thing in C++ initially seems simple, but is actually slightly subtle. The trouble is in how to define f and p. It turns out that what we need here are template template parameters. Both f and p are template types which yield a different result for each value of their template argument. For instance, a 'function' to multiply by two could be defined as:

Note that we again make use of the Select template to choose between the two different result types in Filter.

Extensions

So far, we've only seen how to implement integer lists. There's a good reason for this - things like doubles, for example, can't be template parameters. All isn't entirely lost, however. It turns out that we can make lists of anything that can be represented by integers at compile-time! The code looks something like Listing 9.

The important change is in how we treat the head of the list - now we write typename x wherever we had int x before, and use the type's value field to get its actual value if we need it. The rest of the code can be transformed to work for generic lists in a very similar fashion. There's something to be said about how we handle ordering, but that's a topic for the next article!

Conclusion

In this article, we've seen how template metaprogramming is intrinsically related to functional programming in languages like Haskell, and implemented compile-time lists using C++ templates. Next time, I'll show one way of implementing ordering in generic lists, and consider how to implement compile-time binary search trees.

So what are the uses of writing code like this? One direct use of compile-time BSTs would be to implement a static table that is sorted at compile time. This can prove extremely helpful, particularly in embedded code. There are also indirect benefits derived from learning more about template metaprogramming in general. Writing code like this can be seen as a useful stepping stone towards understanding things like the typelists described in Andrei's book. The capabilities these provide are quite astounding and can provide us with real benefits to the brevity and structure of our code.

Till next time...

Acknowledgements

Thanks to the Overload review team for the various improvements they suggested for this article.