Templates in C (yes! plain C! Not C++!)
Why using templates?
Imagine you have to write a set of functions in C, which differ only by a few keywords (typically type keywords). For instance, let's say we want to write a function computing the sum of two arrays of length n, for floats, doubles, and ints (null pointers tests are left out for the sake of clarity):
void sum_float(int n, float *a, float *b) { /* computes a:=a+b where a and b are two arrays of length n */ int i; for(i=0;i<n;i++) a[i]+=b[i]; } void sum_double(int n, double *a, double *b) { /* computes a:=a+b where a and b are two arrays of length n */ int i; for(i=0;i<n;i++) a[i]+=b[i]; } void sum_int(int n, int *a, int *b) { /* computes a:=a+b where a and b are two arrays of length n */ int i; for(i=0;i<n;i++) a[i]+=b[i]; }
Wouldn't it be nicer to type the function just once, by specifying what keyword is "variable", then "instanciating" the function for each possible value of the "variable", and then having a way to call the exact variant of the function? Of course, for a simple function like this one, it wouldn't make much sense. But think about a much, much longer function. Or set of functions...
This is what templates do. In C++ there is a template
keyword that allows to do just that. The downsides is that it is sometimes hard to port. And it does not exist in C.
Here's a technique to emulate the use of templates in C. It only uses the standard C preprocessor and it is, as far as I know, ANSI C89-compliant. And it works in C++, too. It is also very useful for OpenCL, which has a fully working pre-processor but no templates.
Note: Over the year, this simple blog post has been cited in many places, translated into Russian, quoted on Greek, Chinese, Finnish forums. Wow! I was contacted and asked whether I "discovered' this technique. No, I do not claim to have invented this trick, because I saw it while digging in code. However, I have found that it is not widely known despite its obvious usefulness, hence the tutorial.
Templates in C
Ingredient 1: a concatenation macro
First, we need to declare a couple of macros. Those macros need to be included in every file that makes use of templates. To make things easier we will declare them in a .h file called "templates.h":
templates.h |
#ifndef TEMPLATES_H_ #define TEMPLATES_H_ #define CAT(X,Y) X##_##Y #define TEMPLATE(X,Y) CAT(X,Y) #endif |
The goal of macro TEMPLATE(X,Y)
is to have a keyword that enables us to concatenate X and Y with an underscore in between, like this: X_Y, so that writing TEMPLATE(function,type)
may translate to function_type
.
The ##
operator is a C preprocessor directive which allows to concatenate two tokens. The reason we can't use only a single #define TEMPLATE(X,Y) X##Y
macro is that if X is itself a #def'd constant, it will not be replaced by its value (the details of this question and the details behind the hack to make it work anyway may be found here (self-referential macros) and here (argument prescan)).
Ingredient 2: the functions to "templatize"
Ok, so now we want to write a .c and a .h corresponding to the functions we'd like to have as templates, right? Let's write them. To denote the variable type keyword, we use letter 'T'. This will be #defined later on in the tutorial.
First the .h:
sum_as_template.h |
#ifdef T |
Notice we don't guard the .h against multiple inclusion by using the standard #ifndef HEADER_H_
stuff. This is intentional. We'll see later why. On the other hand, the #ifdef T
test is optional, but very useful to guard against any unlawful inclusion in the case T isn't defined, so the compiler doesn't throw a fit and starts hissing at you.
And now the .c:
sum_as_template.c |
#ifdef T |
Mix everything in a bowl...
Now we really want to instanciate the sum
function so that all its variants exist (sum_float
, sum_double
, etc). We create another set of .h and .c files:
The following .c file is the one we will compile just as another .c in the project:
all_possible_sums.c |
#include "templates.h" |
Note: on GCC, #undef T
would have been enough without the #ifdef T
/ #endif
around it; but Visual C++ (at least up to version 7) does not like it...
The following .h is the one we'll include in any .c where a variant of the sum_...
function is used.
all_possible_sums.h |
#ifndef ALL_POSSIBLE_SUMS_H_ |
This time we understand why we didn't guard sum_as_template.h against multiple inclusions: it is included once per type...
...and serve as it is!
We're all set now! Let's use the templates in an example:
main.c |
#include "all_possible_sums.h" |
That's all, folks!
The last word
What about complex types, you may ask? What if I want to make a template where "T" is to be replaced by, say, "unsigned long long"? Wouldn't the compiler complain that it does not know about "void sum_unsigned long long()
"?
The answer is yes, but there's a workaround: typedef the offending, "multi-worded" type into a "single-worded" type, like this:
typedef unsigned long long uint64;
Then use TEMPLATE(sum,uint64)
instead of TEMPLATE(sum,unsigned long long)
. Voilą!