|
cvx
|
A modern C library of generic data structures built on templating header files.
There are two main types of data structures:
Implementations are data structures that provide a wide range of operations, even if they are sometimes slow. These are generalist containers.
Interfaces are abstractions that can be implemented by multiple data structures. They define a set of functions in a vtable and you can "cast" an implementation type to an interface type.
Check out the examples folder to see more examples.
| Header | Description | Interfaces | Iterators |
|---|---|---|---|
cvx/dynamic_array.h | Growable contiguous array. O(1) amortized push/pop at back, O(n) at front. | stack | random_access_iterator |
cvx/slinked_list.h | Singly linked list with head and tail pointers. O(1) push/pop at front; O(n) at back. | stack, queue | forward_iterator |
| Header | Operations |
|---|---|
cvx/interface/stack.h | push, pop, peek, replace, count, new, clone, drop |
cvx/interface/queue.h | enqueue, dequeue, count, new, clone, drop |
| Header | Operations |
|---|---|
cvx/iter/forward_iterator.h | start, drop, at_start, at_end, count, to_start, next, forward, value, index |
cvx/iter/bidirectional_iterator.h | start, end, drop, at_start, at_end, count, to_start, to_end, next, prev, forward, backward, value, index |
cvx/iter/random_access_iterator.h | start, end, drop, at_start, at_end, count, to_start, to_end, next, prev, forward, backward, go_to, value, index |
An implementation is instantiated by defining a set of macros and then including a header. Each #include produces a new and independent concrete type.
| Macro | Required | Description |
|---|---|---|
V | yes | The value type stored in the container (e.g. int, char *, struct point) |
K | key-value only | The key type (same as V but for key-value containers) |
SNAME | yes | The name of the generated struct (e.g. my_list becomes struct my_list) |
PFX | yes | Prefix for all generated functions (e.g. ml becomes ml_push_back(...)) |
TAG | yes | A unique integer that identifies this container type at runtime. Must be distinct from every other TAG in the same program. |
All macros are automatically undefined after the #include via undef.h, so there is no risk of them leaking into subsequent templates.
Each instantiated container generates a struct <SNAME>_vtabv type that holds optional (some times mandatory) function pointers for element operations. You create one and pass it to the constructor. A NULL value is allowed when no callbacks are needed.
Example - dynamic array of heap-allocated strings:
For key-value structures a matching struct <SNAME>_vtabk is generated for the key type, with the same set of fields.
An interface is a struct type consisting of an instance pointer and a vtable pointer. Multiple data structures can implement one interface.
Usually, when working with this pattern, you have void * pointers. This library instead uses a cvx_container type, which holds extra information for error handling.
Define IMPL_<INTERFACE> when you want to be able to cast an implementation to an interface:
Then cast at construction time:
The same interface can back different implementations simultaneously:
It is annoying to call functions from interfaces via the vtable. For example, given that s is a stack, to push an item you need to: s->vtable->push(s->instance, item).
Instead you can use these macros and simply: cvx_push(s, item). Neat!
Every cvx_container carries a flag field of type enum cvx_flags. Functions set this field instead of returning error codes, so the caller can inspect it after any operation.
| Flag | Value | Meaning |
|---|---|---|
CVX_FLAG_OK | 0 | No error |
CVX_FLAG_WRONG_TAG | 1 | A container was passed to a function for a different type |
CVX_FLAG_ALLOC | 2 | A memory allocation failed |
CVX_FLAG_EMPTY | 3 | Operation requires elements but the container is empty |
CVX_FLAG_FULL | 4 | Container has reached capacity and does not resize |
CVX_FLAG_RANGE | 5 | Index is out of bounds |
CVX_FLAG_NOT_FOUND | 6 | Key or value not found |
CVX_FLAG_INVALID | 7 | Invalid argument or operation |
CVX_FLAG_DUPLICATE | 8 | Duplicate key or value |
CVX_FLAG_ERROR | 9 | Generic or unknown error |
Every implementation must be given a unique TAG integer. The tag is embedded in cvx_container.tag at construction and checked at the start of every function. Passing the wrong container to a function (e.g. a linked list where a dynamic array is expected) sets CVX_FLAG_WRONG_TAG and returns immediately rather than corrupting memory.
Required tooling
pip3 install gcovrbrew install watchexeccargo install watchexecbrew install bearThere is no build process. This is a header-only library.
The project uses just as a command runner and watchexec for live reloading.
Tests live in tests/ as header-only files and are included in tests.c.
The test framework is located in tests/cvxtest.h. Check it out if you want to know more.