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)
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.