Containers Library

April 7, 2026 · View on GitHub

Defined in header opencstl.h

#include "opencstl.h"

The OpenCSTL Containers library is a collection of generic macros and functions that allow C programmers to use common data structures — dynamic arrays, linked lists, trees, hash tables, stacks, queues — with an interface identical to C++ STL. No build steps, no linking, no type registration. Just #include "opencstl.h".

There are three classes of containers:

  • Sequence containers — data structures accessible sequentially.
  • Associative containers — sorted data structures searchable in O(log n).
  • Unordered associative containers — hash-based data structures searchable in O(1) average.

In addition, container adaptors provide a restricted interface over the sequence containers.

The container manages its own storage and provides functions to access elements directly or through iterators (typed pointers with properties similar to C++ iterators). Most containers share a common set of function names, following the same semantics as their C++ STL equivalents.


Contents

  1. Installation
  2. Sequence Containers
  3. Associative Containers
  4. Unordered Associative Containers
  5. Container Adaptors
  6. Iterator Model
  7. Iterator Invalidation
  8. Built-in Comparators
  9. stdlib Compatibility
  10. Function Table
  11. Choosing the Right Container

Installation

curl -LO "https://raw.githubusercontent.com/springkim/OpenCSTL/refs/heads/master/opencstl.h"

Then in your source file:

#include "opencstl.h"

No additional build configuration required. Supports C99, C11, C17, and C23.


Sequence Containers

Sequence containers implement data structures that can be accessed sequentially. Elements are stored in a specific order determined by the programmer, not by any key or comparator.

ContainerMacro / ConstructorDescription
VECTORVECTOR(T) / new_vector(T)Resizable contiguous array. Supports [], begin/end, push_back, pop_back. Compatible with qsort and bsearch.
DEQUEDEQUE(T) / new_deque(T)Double-ended resizable array. Supports [] and O(1) push_front / pop_front in addition to all VECTOR operations.
LISTLIST(T) / new_list(T)Doubly-linked list. O(1) insert and erase anywhere given an iterator. No [] access; iterate with next() / prev().

Handle Types

ContainerHandle TypeIterator Advance
VECTORT*it++
DEQUET*it++
LISTT**it = next(it)

Associative Containers

Associative containers implement sorted data structures that can be searched in O(log n). Internally backed by self-balancing binary search trees. Iteration always visits elements in ascending sorted order.

ContainerMacro / ConstructorDescription
SETSET(T) / new_set(T [, cmp])Collection of unique keys, sorted by a comparator. Duplicate inserts are silently ignored.
MAPMAP(K) / new_map(K, V [, cmp])Collection of unique key-value pairs, sorted by key. Access values via first(iter) and second(iter, V).

The comparator argument is optional. When omitted, a built-in default comparator for the type is used.

Handle Types

ContainerHandle TypeIterator Advance
SETT**it = next(it)
MAPK**it = next(it)

Unordered Associative Containers

Unordered associative containers implement hash-based data structures. Average O(1) lookup, insertion, and removal. Reverse iteration is supported via rbegin / rend / prev, visiting elements in reverse bucket traversal order.

ContainerMacro / ConstructorDescription
UNORDERED_SETUNORDERED_SET(T) / new_unordered_set(T [, hash_fn])Collection of unique keys, hashed. Supports [] bucket access, capacity, rbegin/rend/prev.
UNORDERED_MAPUNORDERED_MAP(K) / new_unordered_map(K, V [, hash_fn])Collection of unique key-value pairs, hashed. Supports rbegin/rend/prev.

The hash_fn argument is optional. When omitted, a built-in default hash for the type is used.

Handle Types

ContainerHandle TypeIterator Advance
UNORDERED_SETT**it = next(it) / it = prev(it)
UNORDERED_MAPK**it = next(it) / it = prev(it)

Container Adaptors

Container adaptors provide a specialized, restricted interface over an underlying sequence container. They do not expose iterators or direct element access beyond what their interface specifies.

ContainerMacro / ConstructorDescription
STACKSTACK(T) / new_stack(T)LIFO (last-in, first-out). Access via top. Supports push and pop.
QUEUEQUEUE(T) / new_queue(T)FIFO (first-in, first-out). Access via front and back. Supports push and pop.
PRIORITY_QUEUEPRIORITY_QUEUE(T) / new_priority_queue(T [, cmp])Max-heap by default. The element with highest priority is always at top. O(log n) push and pop.

The comparator argument for PRIORITY_QUEUE is optional.


Iterator Model

OpenCSTL uses typed pointers as iterators, mirroring C++ iterator semantics in plain C.

Contiguous containers (VECTOR, DEQUE)

The iterator is a TYPE*. Standard pointer arithmetic applies:

VECTOR(int) v = new_vector(int);
for (int i = 0; i < 5; i++) push_back(v, i);

for (int *it = begin(v); it != end(v); it++)       // forward
    printf("%d ", *it);

for (int *it = rbegin(v); it != rend(v); it--)     // reverse
    printf("%d ", *it);

Node-based containers (LIST, SET, MAP)

The iterator is TYPE* but pointer arithmetic does not work. Use next() and prev():

LIST(int) lst = new_list(int);
for (int i = 0; i < 5; i++) push_back(lst, i);

for (int *it = begin(lst); it != end(lst); it = next(it))    // forward
    printf("%d ", *it);

for (int *it = rbegin(lst); it != rend(lst); it = prev(it))  // reverse
    printf("%d ", *it);

Hash-based containers (UNORDERED_SET, UNORDERED_MAP)

Same rules as node-based: use next() and prev(). Both forward and reverse iteration are supported:

UNORDERED_SET(int) h = new_unordered_set(int);
for (int i = 1; i <= 5; i++) insert(h, i);

for (int *it = begin(h); it != end(h); it = next(it))   // forward (bucket order)
    printf("%d ", *it);

for (int *it = rbegin(h); it != rend(h); it = prev(it)) // reverse (reverse bucket order)
    printf("%d ", *it);

Summary

FunctionVECTOR, DEQUELIST, SET, MAPUNORDERED_SET, UNORDERED_MAP
begin
end
rbegin
rend
next(use it++)✅ required✅ required
prev(use it--)✅ required✅ required

Iterator Invalidation

Read-only operations never invalidate iterators. The table below summarizes invalidation behavior for mutating operations.

ContainerAfter insertionAfter erasure
VECTORIf reallocation: all. Otherwise: at and after insertion pointErased element and all after it
DEQUEIf reallocation: all. Otherwise: at and after insertion pointErased element and all after it
LISTNoneOnly the erased node
SETNoneOnly the erased node
MAPNoneOnly the erased node
UNORDERED_SETIf rehash: all. Otherwise: noneOnly the erased node
UNORDERED_MAPIf rehash: all. Otherwise: noneOnly the erased node
STACKN/AN/A
QUEUEN/AN/A
PRIORITY_QUEUEN/AN/A

Built-in Comparators

Comparators are required by SET, MAP, and PRIORITY_QUEUE but are optional — omitting the argument uses the built-in default for the type.

ComparatorTypeOrder
COMPARE(TYPE)any primitiveAscending (default)
IntCmpintDescending (max at top for PRIORITY_QUEUE)
FloatCmpfloatDescending
DoubleCmpdoubleDescending
StringCmpchar*Descending lexicographic
SET(int) s1 = new_set(int);              // default comparator (optional)
SET(int) s2 = new_set(int, NULL);        // same
SET(int) s3 = new_set(int, COMPARE(int)); // explicit ascending

MAP(int) m = new_map(int, double);       // default comparator (optional)

PRIORITY_QUEUE(int) pq = new_priority_queue(int);        // default (max-heap)
PRIORITY_QUEUE(int) pq2 = new_priority_queue(int, IntCmp); // explicit max-heap

stdlib Compatibility

Contiguous containers (VECTOR, DEQUE) store elements in a single flat memory block, making them directly compatible with standard C library functions:

#include "opencstl.h"

int IntAscCmp(const void *a, const void *b) {
    return *(int *)a - *(int *)b;
}

int main() {
    VECTOR(int) v = new_vector(int);
    push_back(v, 5);
    push_back(v, 1);
    push_back(v, 3);

    qsort(v, size(v), sizeof(int), IntAscCmp);

    int key = 3;
    int *result = bsearch(&key, v, size(v), sizeof(int), IntAscCmp);
    if (result)
        printf("found %d at index %td\n", *result, result - v);

    destroy(v);
    return 0;
}

Node-based containers (LIST, SET, MAP, UNORDERED_SET, UNORDERED_MAP) are not compatible with qsort or bsearch.


Function Table

✅ = supported, ❌ = not supported

Constructor / Destructor

FunctionVECTORDEQUELISTSETMAPUNORDERED_SETUNORDERED_MAPSTACKQUEUEPRIORITY_QUEUE
new_*
destroy

Element Access

FunctionVECTORDEQUELISTSETMAPUNORDERED_SETUNORDERED_MAPSTACKQUEUEPRIORITY_QUEUE
v[i] (operator[])✅ (bucket)
front
back
top
first
second

Iterators

FunctionVECTORDEQUELISTSETMAPUNORDERED_SETUNORDERED_MAPSTACKQUEUEPRIORITY_QUEUE
begin
end
rbegin
rend
next(use it++)(use it++)
prev(use it--)(use it--)

Capacity

FunctionVECTORDEQUELISTSETMAPUNORDERED_SETUNORDERED_MAPSTACKQUEUEPRIORITY_QUEUE
empty
size
capacity

Modifiers

FunctionVECTORDEQUELISTSETMAPUNORDERED_SETUNORDERED_MAPSTACKQUEUEPRIORITY_QUEUE
assign
push_back
pop_back
push_front
pop_front
push
pop
insert
erase
resize
clear

Lookup

FunctionVECTORDEQUELISTSETMAPUNORDERED_SETUNORDERED_MAPSTACKQUEUEPRIORITY_QUEUE
find✅ O(n)✅ O(n)✅ O(n)✅ O(log n)✅ O(log n)✅ O(1) avg✅ O(1) avg

Choosing the Right Container

Do you need key-value pairs?
├── YES → Do you need sorted order?
│         ├── YES  → MAP              (O(log n), sorted iteration)
│         └── NO   → UNORDERED_MAP   (O(1) avg, rbegin/rend supported)
└── NO  → Do you need only unique keys (set-like)?
          ├── YES → Do you need sorted order?
          │         ├── YES  → SET              (O(log n), sorted iteration)
          │         └── NO   → UNORDERED_SET    (O(1) avg, rbegin/rend/[] supported)
          └── NO  → What kind of sequence?
                    ├── LIFO (stack behavior)            → STACK
                    ├── FIFO (queue behavior)            → QUEUE
                    ├── Priority-ordered access           → PRIORITY_QUEUE
                    ├── Fast front AND back operations    → DEQUE
                    ├── O(1) insert/erase anywhere        → LIST
                    └── Random access + stdlib compat     → VECTOR (default choice)

Quick Comparison

RequirementBest choice
Fast random access (v[i])VECTOR, DEQUE
qsort / bsearch compatibleVECTOR, DEQUE
O(1) insert/erase at frontDEQUE, LIST
O(1) insert/erase anywhere (with iterator)LIST
Sorted unique keysSET
Sorted key-value pairsMAP
Fast membership test + bucket inspectionUNORDERED_SET
Fast key-value lookupUNORDERED_MAP
LIFO semanticsSTACK
FIFO semanticsQUEUE
Always access largest elementPRIORITY_QUEUE

Example

#include "opencstl.h"

int main() {
    // VECTOR: resizable array with [] access
    VECTOR(int) v = new_vector(int);
    for (int i = 0; i < 5; i++) push_back(v, i);
    v[2] = 99;
    for (int *it = begin(v); it != end(v); it++) printf("[%d]", *it);
    puts("");
    destroy(v);

    // LIST: doubly-linked, O(1) insert/erase anywhere
    LIST(int) lst = new_list(int);
    push_front(lst, 1);
    push_back(lst, 2);
    push_front(lst, 0);
    for (int *it = begin(lst); it != end(lst); it = next(it)) printf("[%d]", *it);
    puts("");
    destroy(lst);

    // SET: sorted unique keys (comparator optional)
    SET(int) s = new_set(int);
    int vals[] = {5, 3, 8, 3, 1};
    for (int i = 0; i < 5; i++) insert(s, vals[i]);
    for (int *it = begin(s); it != end(s); it = next(it)) printf("[%d]", *it);
    puts("");
    destroy(s);

    // MAP: sorted key-value pairs (comparator optional)
    MAP(int) m = new_map(int, char*);
    insert(m, 2, "two");
    insert(m, 1, "one");
    insert(m, 3, "three");
    for (int *it = begin(m); it != end(m); it = next(it))
        printf("[%d:%s]", first(it), second(it, char*));
    puts("");
    destroy(m);

    // UNORDERED_SET: hash set with rbegin/rend/prev support
    UNORDERED_SET(int) h = new_unordered_set(int);
    for (int i = 1; i <= 5; i++) insert(h, i);
    // Reverse bucket order traversal
    for (int *it = rbegin(h); it != rend(h); it = prev(it)) printf("[%d]", *it);
    puts("");
    destroy(h);

    // STACK + QUEUE: adaptors
    STACK(int) stk = new_stack(int);
    QUEUE(int) que = new_queue(int);
    for (int i = 0; i < 4; i++) { push(stk, i); push(que, i); }
    while (!empty(stk)) { printf("[%d]", top(stk));   pop(stk); } puts("");
    while (!empty(que)) { printf("[%d]", front(que));  pop(que); } puts("");
    destroy(stk);
    destroy(que);

    // PRIORITY_QUEUE: max-heap (comparator optional)
    PRIORITY_QUEUE(int) pq = new_priority_queue(int);
    for (int i = 0; i < 5; i++) push(pq, i);
    while (!empty(pq)) { printf("[%d]", top(pq)); pop(pq); } puts("");
    destroy(pq);

    return 0;
}

Output:

[0][1][99][3][4]
[0][1][2]
[1][3][5][8]
[1:one][2:two][3:three]
[reverse bucket order]
[3][2][1][0]
[0][1][2][3]
[4][3][2][1][0]

See Also

DocumentDescription
VECTORResizable contiguous array
DEQUEDouble-ended resizable array
LISTDoubly-linked list
SETSorted unique keys
MAPSorted key-value pairs
UNORDERED_SETHash-based unique keys; rbegin/rend/[]/capacity supported
UNORDERED_MAPHash-based key-value pairs; rbegin/rend/prev supported
STACKLIFO adaptor
QUEUEFIFO adaptor
PRIORITY_QUEUEMax-heap adaptor
GitHubSource code and issue tracker