I'm trying to implement a simple type-generic Vector with macros using the newest features of C23. Looking for any advice about macro design and pitfalls because I generally avoid macros at all costs. Wondering how to extend this code to handle more complex data types as well.
Also, I'm not particularly happy with the hacky int vector_abort() used in the vector_at() ternary. Any portable advice here is greatly appreciated. Furthermore, if anyone has a way to make opaque structures with macros I would be very interested.
A major problem that I immediately notice is that there isn't an easy way to tell if vector_push() was successful. However, return values aren't permitted for macros and calling abort() or exit() doesn't seem great in this instance.
C23 features used:
nullptrconstant from<stddef.h>trueandfalsekeywordsfree_sized()from<stdlib.h>= {}empty initializerfunction()empty function argument declarationtypeofoperatorN3003Improved Rules for Tag Compatibility
vector.h
#ifndef VECTOR_H
#define VECTOR_H
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int vector_abort(void) {
errno = EINVAL;
fprintf(stderr, "ERROR: %s.\n" "Invalid vector index.\n", strerror(errno));
errno = 0;
abort();
return 0;
}
#define vector(TYPE) struct vector_##TYPE {size_t length; size_t capacity; TYPE* array;}
#define vector_new(TYPE, CAPACITY) {.length = 0, .capacity = CAPACITY, .array = calloc(CAPACITY, sizeof(TYPE))}
#define vector_delete(VECTOR) \
do { \
VECTOR.length = 0; \
VECTOR.capacity = 0; \
free_sized(VECTOR.array, (VECTOR.length * sizeof(*(VECTOR.array)))); \
VECTOR.array = nullptr; \
} while (false)
#define vector_size(VECTOR) VECTOR.length
#define vector_at(VECTOR, INDEX) (VECTOR.length > INDEX) ? VECTOR.array[INDEX] : vector_abort()
#define vector_pop(VECTOR) if (VECTOR.length > 0) VECTOR.length--
#define vector_push(VECTOR, VALUE) \
do { \
if (VECTOR.length == VECTOR.capacity) { \
typeof(VECTOR.array) new_array = realloc(VECTOR.array, (VECTOR.capacity * 2 * sizeof(*(VECTOR.array)))); \
if (!new_array) break; \
VECTOR.capacity *= 2; \
VECTOR.array = new_array; \
} \
VECTOR.array[VECTOR.length] = VALUE; \
VECTOR.length++; \
} while (false)
#endif
main.c
#include "./vector.h"
#include <stdio.h>
int main(void) {
vector(int) v = {}; // or use = vector_new(type, capacity)
vector_push(v, 1);
vector_push(v, 2);
vector_push(v, 3);
for (size_t i = 0; i < vector_size(v); ++i) {
printf("Test Push %d\n", vector_at(v, i));
}
vector_pop(v);
for (size_t i = 0; i < vector_size(v); ++i) {
printf("Test Pop %d\n", vector_at(v, i));
}
vector_delete(v);
for (size_t i = 0; i < vector_size(v); ++i) {
printf("Test Delete %d\n", vector_at(v, i));
}
return EXIT_SUCCESS;
}
Polyfill
Here is a free_sized() polyfill if your implementation doesn't support it yet.
void free_sized(void* ptr, size_t /*size*/) {
free(ptr);
}
Compiler Flags
-Wall -Wextra -Werror -pedantic-errors -std=c2x
vector_deleteappears to use thelengthfield after it is set to 0 in a call tofree_sized. I believe it should be usingcapacitynotlength, and should be using them before setting them to zero. (Which, if they're both set to zero it doesn't really matter which you use, does it? But still...) \$\endgroup\$