DEV Community

Noah11012
Noah11012

Posted on

Templates in C

What?

You may be asking this question to yourself. Don't worry, you're not dyslexic.

This is madness. How will we do this?

Easy, by using macros.

For this example, we'll be creating a generic vector using this macro templating mechanism.

list.h

#pragma once

#include <stdlib.h>

typedef struct _BaseList
{
    void *buffer;
    int   index;
    int   length;
    int   capacity;
} BaseList;

#define List(type)         \
    struct                  \
    {                       \
        type *buffer;       \
        int   element_size; \
        int   index;        \
        int   length;       \
        int   capacity;     \
    } *



BaseList *list_new_  (int size_of_each_element);
void      list_delete(BaseList *list);
void      list_resize(BaseList *list, int new_size);

#define list_new(type) (List (type) ) list_new_(sizeof(type));

#define list_push(list, data)    \
    if(list->index > list->length) \
        list_resize(list, list->capacity * 2); \
    list->buffer[list->index++] = data;        \
    list->length++;

We define a macro called List. It takes a parameter called type and it will create an anonymous struct that will be made for each instance. We need a named structure for the functions we'll be using. I'll call it BaseList and this is what the functions will be using.

Notice at the end of the List macro definition, there is an asterisk. That's important if we want this to work. C wouldn't like it if we tried to convert from a BaseList to an anonymous structure. However, if we can convert from a pointer to a BaseList to a pointer to an anonymous structure it will do it but probably give you a warning.

For the push function, it was easier to create a simple macro. If we run out of space, we allocate twice as much. We store the data in the next available spot.

list.c:

#include "list.h"

BaseList *list_new_(int size_of_each_element)
{
    BaseList *new_list = malloc(sizeof(*new_list));
    new_list->buffer = malloc(size_of_each_element * 8);

    new_list->index = 0;
    new_list->length = 0;
    new_list->capacity = 8;

    return new_list;
}

void list_resize(BaseList *list, int new_size)
{
    list->buffer = realloc(list->buffer, new_size);
}

void list_delete(BaseList *list)
{
    free(list->buffer);
    free(list);
}

To make the code simpler, I've decided to remove the error checking.

Now to test this baby:

main.cpp

#include <stdio.h>
#include "list.h"


int main(void)
{
    List (int) list_of_ints = list_new(int);

    list_push(list_of_ints, 100);
    list_push(list_of_ints, 200);
    list_push(list_of_ints, 300);

    printf("%d\n%d\n%d\n", list_of_ints->buffer[0], list_of_ints->buffer[1], list_of_ints->buffer[2]);
}

Compile and run.

It works!

Of course, you can expand on this and improve it. I hope you enjoyed the article and consider it a peace offering for not remembering to use SDL_Quit() and not uploading the next installment of Using SDL2.

Top comments (3)

Collapse
 
tux0r profile image
tux0r

If you're using C++ anyway, why do you need a C library to imitate a C++ standard feature? ;-)

Collapse
 
noah11012 profile image
Noah11012

For my series, Using SDL2, we do use standard C++. However, for projects that require only C then this might be something that is practical for use.

Collapse
 
tux0r profile image
tux0r

You have a point. Thank you for your series. SDL is among the techniques I haven't taken a look at just yet.