Templates in C Language??!!!

04/06/2013 08:40

Templates feature is available in C++ and Generics in C# and Java. C does not have Templates and C11 is the current standard for C Programming.

But in this blog post we shall try implementing the Templates feature in C and I hope that you find this article interesting irrespective of the programming language you use.

Why do we need Templates kind of feature in C?

In my previous C project, the application had to work on both Linux 32-bit and 64-bit versions. Among several data structure utilities we had implemented, we also had Vector which used to provide dynamic array functionality, expanding its size when more elements are added and collapsing when sufficient numbers of elements are deleted. TheVector was used to store 32-bit data in each slot.

Then a requirement arose where we needed a Vector to store 64-bit data also irrespective of the Linux version. The existing Vector was renamed to Vector32 and all the interface functions such as Vec_getAt()Vec_setAt(), etc. were also renamed to Vec32_getAt() and Vec32_setAt. Then we needed a similar structure and interface for 64-bit Vectorso that we could use the structure Vector64 with methods such as Vec64_getAt()Vec64_setAt(), etc.

Instead of copying the existing code of Vector32 into another file and substituting all occurrences of Vector32 withVector64, I borrowed the knowledge obtained from using Templates in C++/Java and decided to use it in C to avoid copying and rewriting the code.

I do have few interesting things to share on the common data structure utilities such as VectorHashmapLinkList, etc., but that could be topics for some other blog posts. Here let me stick to the subject of Templates.

For the sake of understanding, instead of Vector, let us consider a simple C structure, Object32 that can be used to store and retrieve 32-bit data. Here is the header file object32.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef _OBJECT32_H
#define _OBJECT32_H
////////////////////////////////////////////////////////////////////
 
// Define the payload
#ifndef OBJECT32_DATA
#define OBJECT32_DATA uint32_t
#endif
 
// Permit C++ class name like usage without the keyword "struct"
typedef struct _Object32 Object32;
 
// Main structure definition
struct _Object32
{
    OBJECT32_DATA m_data;
}
 
// Interface Methods
OBJECT32_DATA Obj32_get( Object32* this );
void Obj32_set( Object32* this, OBJECT32_DATA data );
 
////////////////////////////////////////////////////////////////////
#endif // _OBJECT32_H

 

 

 

 

 

  • The first line prevents multiple inclusions of the header file. This is not just done here for the sake of best practice. For implementing the Templates feature, this is very much necessary as we shall see it later.
  • Then the object’s payload is defined which is of size 32 bits.
  • The typedef uint32_t has been omitted here, but in some global header file say global_defs.h, it could be defined as “typedef unsigned int uint32_t;“.
  • Next typedef allows the structure name to be used like a class name without having to use the keyword “struct”.
  • The structure itself is defined next which is pretty simple and just stores the payload.
  • We are just defining two interface functions which are getter and setter methods of the structure.

Now let us see the contents of the source file object32.c.

1
2
3
4
5
6
7
8
9
10
11
12
#include "global_defs.h" // Global Type Definitions
#include "object32.h"
 
OBJECT32_DATA Obj32_get( Object32* this )
{
    return this->m_data;
}
 
void Obj32_set( Object32* this, OBJECT32_DATA data )
{
    this->m_data = data;
}

The source file is pretty simple and nothing complicated there. I had mentioned in the previous post that the knowledge of C++/Java/C# could be used to write better C code and we could also make use of OOP concepts. Please notice the use of ‘this‘ pointer, we have it as the first parameter for all the methods and because we are programming in C, we are privileged to have it as a non-hidden parameter ;-)

We do not have to go looking for it as we are not trying to convert C to C++, but when the opportunity presents itself we could have inheritance as well as polymorphism in C and I used them while implementing “Expression Evaluator” as part of XPath processing engine. I shall definitely share that experience with you in some future blog posts.

Normally the getter and setter functions are implemented as inline functions, but here we are pretending that they contain some complex and elaborate code and hence they need to be implemented as non-inline methods in a separate source file.

Now let us get to the important step of implementing Object64 providing the equivalent functionality without actually rewriting the source code. To use Object64 one has to include its header file object64.h in a normal way just like any other header file.

Here is the header file object64.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#ifndef _OBJECT64_H
#define _OBJECT64_H
////////////////////////////////////////////////////////////////////
 
// Payload requires some careful handling. Temporarily alter its size.
#ifdef OBJECT32_DATA
#undef OBJECT32_DATA
#endif
#define OBJECT32_DATA uint64_t
 
// Define the payload for 64bit version
#define OBJECT64_DATA uint64_t
 
 
// Provide 64 bit versions for all typedefs and structures
#define _Object32 _Object64
#define Object32 Object64
 
// Provide 64 bit versions for all the methods
#define Obj32_get Obj64_get
#define Obj32_set Obj64_set
 
 
// In case object32.h has already been included, allow that file to be included
// again now so that Object32 interface could be redfined to Object64 using
// the macros defined above
#ifndef _OBJECT32_H
#define _OBJECT32_H_REDEFINE // Make note that 32bit version is already included
#undef _OBJECT32_H
#endif
 
// include Object32 and redefine its interfaces to 64bit
#include "object32.h"
 
 
// After redefining and using the 64bit interface, undef the definitions so that
// both Object32 and Object64 could co-exist in a single source file
#ifndef _OBJECT64_C
 
// Remove the temporary alteration of the 32bit payload size
#undef OBJECT32_DATA
 
 
// If 32bit version has been already included, then try to leave the same state for the user
#ifdef _OBJECT32_H_REDEFINE
#undef _OBJECT32_H_REDEFINE
 
// Put back the original definition of the payload
#define OBJECT32_DATA uint32_t
 
// Prevent multiple inclusions of the 32bit version header file
#define _OBJECT32_H
#endif
 
 
// Remove the mappings of typedefs and structures
#undef _Object32
#undef Object32
 
// Remove the mappings of methods
#undef Obj32_get
#undef Obj32_set
 
#endif // _OBJECT64_C
 
////////////////////////////////////////////////////////////////////
#endif // _OBJECT64_H

 

 

 

  • The end user can consider that the header file object64.h is just another header file required to use Object64. But the contents of the header file do contain some nifty jugglery to templatize the 32-bit version and bring a 64-bit version into existence.
  • First the size of the 32-bit payload type OBJECT32_DATA is temporarily altered to 64 bits and a 64-bit payload type OBJECT64_DATA is also explicitly defined.
  • Then the typedefs, structure names and all the method names are mapped to the 64-bit versions before including the 32-bit header file object32.h. When included, because of the mappings all its contents get redefined to 64-bit version.
  • After that since all the 64-bit versions have actually come into existence, we do not require the mappings any more and hence all the mappings done are removed at the end of the header file. This is an important step. If the mappings are not removed users will not be able to use Object32 as it will always get substituted with Object64.

Now we have just the “declaration” of the 64-bit version of all the components, but they have not been implemented yet. We shall implement them in the source file object64.c.

Here is the source file object64.c.

1
2
3
4
5
6
7
8
9
10
#include "global_defs.h" // Global Type Definitions
 
// Let our header file know that it is being included from the source file
#define _OBJECT64_C
 
// Include the 64-bit version header file which will provide the new mappings
#include "object64.h"
 
// Include the SOURCE file with the new mappings being in effect
#include "object32.c"

 

 

 

  • In the source file, after the global definitions which provide types like uint32_tuint64_t, etc. the main line is the definition of _OBJECT64_C. Only at this one location we do not want to remove the mappings as they are needed until the end of THIS source file.
  • After including object64.h, the next line actually includes a SOURCE file. There are not many occasions where we use #include to include not a header file, but a source file. Trust me, when we get to do it, it is just a blast.
  • Only other occasion where I have used #include to include one or more source files is to just pull only few files from some other module into the current module or DLL. There are many ways to do it such as using the makefile, Visual Studio Projects, etc. but having a source file and including all the required source files is also one of the methods. This method also allows some tweaking by defining or undefining few things before including the source files.
  • So in the last line when we include the source file, it is equivalent to actually implementing all the contents of the source file without actually re-typing it. That means maintenance is easy as any bug fix has to be done in only one place.

That’s it. We just completed templatizing Object32, now we have Object64 and both are ready to rock. Let us see some example usage of them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "global_defs.h" // Global Type Definitions
 
// The order of the files do not matter and one is free to include them in any order
#include "object32.h"
#include "object64.h"
 
void foo()
{
    // Both Object32 & Object64 and all their associated typedefs and methods are available
    // and one can use them freely.
    Object32 obj32;
    Object64 obj64;
    OBJECT32_DATA data32 = 1;
    OBJECT64_DATA data64 = 2;
 
    Obj32_set( &obj32, data32 );
    Obj64_set( &obj64, data64 );
 
    data64 = Obj64_get( &obj64 );
    data32 = Obj32_get( &obj32 );
}

Templates are definitely a convenient feature and who said that it is not available in C? :P

But whenever we use the Template feature, we just need to be aware that the machine/object/byte/runtime code size increases though we do not explicitly re-type the source code.

Now a days it has become common to deal with both 32-bit and 64-bit datatypes and who knows that the requirement to implement the Template feature in C may just be lurking around the corner for you. When such an opportunity arises, please do not miss it and just have fun.

I would like to dedicate this article to Dennis Ritchie, who is no longer with us, but “The C Programming Language” he created along with Brian Kernighan, would ever remain with us and would continue to delight generations of aspiring programmers.

Please note that I implemented it in this way so as not to disturb the existing code of Object32 (It was Vector32 in my previous project) and still come up with a 64-bit version of it. I was wondering if that constraint is not there could we adopt much more generic approach.

It did not take long as one of our readers pointed out this article. Many thanks to Bogdan Maslovskiy and you can view our conversation here. Definitely the solution looks generic. But would it make the header files too complex to understand the interface methods clearly? For example, the function declarations could be written as shown below.

  • OBJECT32_DATA TEMPLATE( Obj32, get )( T* this );
  • void TEMPLATE( Obj32, set )( T* this, OBJECT32_DATA data );

This is for the just two simple get and set methods of our example Object. But a Vector or HashMap could easily contain more than 15 methods and in that case would it not make it more difficult to understand and use them?

Please feel free to leave your opinion and suggest any corrections or improvements for the benefit of all our beloved readers.

If you enjoyed reading this, kindly “share” using the social buttons provided below this post and join me in my efforts to reach and provide joy to as many people who might be interested and enjoy reading this kind of articles.