A modern, user friendly, generic, type-safe and fast C99 container library: String, Vector, Sorted and Unordered Map and Set, Deque, Forward List, Smart Pointers, Bitset and Random numbers.
i_TYPE
lets you define i_type
, i_key
, and i_val
all in one line.cvec.h
)cdeq.h
)clist.h
)cstack.h
)cqueue.h
)cpque.h
)cmap.h
)cset.h
)csmap.h
)csset.h
)forward.h
)STC is a modern, typesafe, fast and compact container and algorithms library for C99.
The API naming is similar to C++ STL, but it takes inspiration from Rust and Python as well.
The library handles everything from trivial to highly complex data using templates.
i_key
(+ i_val
for maps). In thisconst char*
cstr
(which may allocate memory) for the lookupvec_str_push(&vec, cstr_from("Hello"))
, you may callvec_str_emplace(&vec, "Hello")
, which is functionally identical, but more convenient.c_foreach (it, MyInts, myints) *it.ref += 42;
works for any container defined asMyInts
with int
elements.c_foreach (it, MyInts, it1, it2) *it.ref += 42;
iterates from it1
up to not including it2
.STC is a fast and memory efficient library, and code compiles fast:
Benchmark notes:
uint64_t
and pairs of uint64_t
for the maps.Non-templated container names are prefixed by c
, e.g. cstr
, cbits
, cregex
.
Public STC macros and “keywords” are prefixed by c_
, e.g. c_foreach
, c_init
.
Template parameter macros are prefixed by i_
, e.g. i_key
, i_type
.
All owning containers can be initialized with {0}
(also cstr
), i.e. no heap allocation initially.
Common types for a container type Cont:
Functions that are available for most all containers:
STC containers have similar functionality to the C++ STL standard containers. All containers except for a few,
like cstr and cbits are generic/templated. No type casting is used, so containers are type-safe like
templated types in C++. However, to specify template parameters with STC, you define them as macros prior to
including the container, e.g.
#define i_TYPE Floats, float // Container type (name, element type)
#include "stc/vec.h" // "instantiate" the desired container type
#include <stdio.h>
int main(void)
{
Floats nums = {0};
Floats_push(&nums, 30.f);
Floats_push(&nums, 10.f);
Floats_push(&nums, 20.f);
for (int i = 0; i < Floats_size(&nums); ++i)
printf(" %g", nums.data[i]);
c_foreach (i, Floats, nums) // Alternative and recommended way to iterate.
printf(" %g", *i.ref); // i.ref is a pointer to the current element.
Floats_drop(&nums); // cleanup memory
}
Note that i_val*
template parameters can be used instead of i_key*
for non-map containers.
Switching to a different container type, e.g. a sorted set (sset):
[ Run this code ]
#define i_TYPE Floats, float
#include "stc/sset.h" // Use a sorted set instead
#include <stdio.h>
int main(void)
{
Floats nums = {0};
Floats_push(&nums, 30.f);
Floats_push(&nums, 10.f);
Floats_push(&nums, 20.f);
// print the numbers (sorted)
c_foreach (i, Floats, nums)
printf(" %g", *i.ref);
Floats_drop(&nums);
}
Comparison/lookup functions are enabled by default for associative containers and priority queue (hmap, hset, smap, sset, pque). To enable it for the remaining containers, define i_cmp
or i_less
(and optionally i_eq
) on the element type. If the element is an integral type, simply define i_use_cmp
to use <
and ==
operators for comparisons.
Note that for #define i_key_class Type
, defining i_use_cmp
means that Type_cmp() function is expected to exist (along with Type_clone() and Type_drop()).
To summarize, i_use_cmp
is only needed to enable comparison (sort/search) functions when defining stack, vec, queue, deq, arc, box. With built-in types, it enables the comparison operators, whereas for keyclass types, it binds comparison to its Type_cmp() function.
If an element destructor i_keydrop
is defined, i_keyclone
function is required.
Alternatively #define i_opt c_no_clone
to disable container cloning.
Let’s make a vector of vectors, which can be cloned. All of its element vectors will be destroyed when destroying the Vec2D.
[ Run this code ]
#include <stdio.h>
#define i_TYPE Vec, float
#include "stc/vec.h"
#define i_type Vec2D
#define i_key_class Vec // Use i_key_class instead i_key when element type has "members" _clone(), _drop() and _cmp().
#include "stc/vec.h"
int main(void)
{
Vec* v;
Vec2D vec = {0}; // All containers in STC can be initialized with {0}.
v = Vec2D_push(&vec, Vec_init()); // push() returns a pointer to the new element in vec.
Vec_push(v, 10.f);
Vec_push(v, 20.f);
v = Vec2D_push(&vec, Vec_init());
Vec_push(v, 30.f);
Vec_push(v, 40.f);
Vec2D clone = Vec2D_clone(vec); // Make a deep-copy of vec
c_foreach (i, Vec2D, clone) // Loop through the cloned vector
c_foreach (j, Vec, *i.ref)
printf(" %g", *j.ref);
c_drop(Vec2D, &vec, &clone); // Cleanup all (6) vectors.
}
This example uses four different container types:
[ Run this code ]
#include <stdio.h>
#define i_key int
#include "stc/hset.h" // hset_int: unordered/hash set (assume i_key is basic type, uses `==` operator)
struct Point { float x, y; };
// Define cvec_pnt and enable linear search by defining i_eq
#define i_TYPE vec_pnt, struct Point
#define i_eq(a, b) (a->x == b->x && a->y == b->y)
#include "stc/vec.h" // vec_pnt: vector of struct Point
#define i_key int
#define i_use_cmp // enable sort/search. Use native `<` and `==` operators
#include "stc/list.h" // list_int: singly linked list
#define i_TYPE smap_int, int, int
#include "stc/smap.h" // sorted map int => int
int main(void)
{
// Define four empty containers
hset_int set = {0};
vec_pnt vec = {0};
list_int lst = {0};
smap_int map = {0};
c_defer( // Drop the containers at scope exit
hset_int_drop(&set),
vec_pnt_drop(&vec),
list_int_drop(&lst),
smap_int_drop(&map)
){
enum{N = 5};
int nums[N] = {10, 20, 30, 40, 50};
struct Point pts[N] = { {10, 1}, {20, 2}, {30, 3}, {40, 4}, {50, 5} };
int pairs[N][2] = { {20, 2}, {10, 1}, {30, 3}, {40, 4}, {50, 5} };
// Add some elements to each container
for (int i = 0; i < N; ++i) {
hset_int_insert(&set, nums[i]);
vec_pnt_push(&vec, pts[i]);
list_int_push_back(&lst, nums[i]);
smap_int_insert(&map, pairs[i][0], pairs[i][1]);
}
// Find an element in each container
hset_int_iter i1 = hset_int_find(&set, 20);
vec_pnt_iter i2 = vec_pnt_find(&vec, (struct Point){20, 2});
list_int_iter i3 = list_int_find(&lst, 20);
smap_int_iter i4 = smap_int_find(&map, 20);
printf("\nFound: %d, (%g, %g), %d, [%d: %d]\n",
*i1.ref, i2.ref->x, i2.ref->y, *i3.ref,
i4.ref->first, i4.ref->second);
// Erase all the elements found
hset_int_erase_at(&set, i1);
vec_pnt_erase_at(&vec, i2);
list_int_erase_at(&lst, i3);
smap_int_erase_at(&map, i4);
printf("After erasing the elements found:");
printf("\n set:");
c_foreach (i, hset_int, set)
printf(" %d", *i.ref);
printf("\n vec:");
c_foreach (i, vec_pnt, vec)
printf(" (%g, %g)", i.ref->x, i.ref->y);
printf("\n lst:");
c_foreach (i, list_int, lst)
printf(" %d", *i.ref);
printf("\n map:");
c_foreach (i, smap_int, map)
printf(" [%d: %d]", i.ref->first, i.ref->second);
}
}
Output
Found: 20, (20, 2), 20, [20: 2]
After erasing the elements found:
set: 40 10 30 50
vec: (10, 1) (30, 3) (40, 4) (50, 5)
lst: 10 30 40 50
map: [10: 1] [30: 3] [40: 4] [50: 5]
STC is primarily a “headers-only” library, so most headers can simply be included in your program. By default,
all templated functions are static (many inlined). This is often optimal for both performance and compiled
binary size. However, if container type instances, e.g. a vec_int
is used used in several translation units,
(e.g. more than 3-4 TUs), consider creating a separate header file for them and link it shared as described here. In this case, one (of the) c-file must implement the templated container, e.g.:
#define i_implement // define shared symbols
#include "vec_int.h"
Note that the non-templated string types cstr, csview uses shared linking by default (may use static linking by
#define i_static
before include). Most functions in csview are inlined though, and the zero-terminated string view,
czview is fully inlined.
Conveniently, src\libstc.c
implements all the non-templated functions with shared linking for cstr,
csview, cregex, utf8, and crand.
Additionally, #define i_import
works as i_implement
for cregex or cstr, but it will also implement
the dependent utf8 functions (utf8 case conversions, etc.). Or you can simply link with libstc.
Each templated type requires one #include
, even if it’s the same container base type, as described earlier.
The template parameters are given by a #define i_xxxx
statement, where xxxx is the parameter name.
The list of template parameters:
i_TYPE
ConType, KeyType[, ValType] is a shorthand for defining i_type
, i_key
and i_val
on one line.i_type
ConType - Custom container type name.i_key
Type - Element key type. [required]. Note: i_val
may be used instead for non-maps (not recommended).i_val
Type - Element value type. [required for] hmap/smap as the mapped value type.i_cmp
Func - Three-way comparison of two i_keyraw* - [required for] non-integral i_keyraw elements, but also see i_use_cmp
.i_hash
Func - Hash function taking i_keyraw* - defaults to c_default_hash. [required for] hmap/hset with non-POD i_keyraw elements.i_eq
Func - Equality comparison of two i_keyraw* - defaults to !i_cmp. Companion with i_hash.Properties:
i_opt
Flags - Boolean properties: may combine c_no_clone, c_no_atomic, c_is_forward, c_static, c_header with the | separator.Key:
i_keydrop
Func - Destroy map/set key func - defaults to empty destructor.i_keyclone
Func - [required if] i_keydrop is defined (exception for arc, as it shares).i_keyraw
Type - Convertion “raw” type - defaults to i_key.i_keyfrom
Func - Convertion func i_key <= i_keyraw.i_keyto
Func - Convertion func i_key* => i_keyraw. [required if] i_keyraw is definedVal: (hmap/smap mapped value only)
i_valdrop
Func - Destroy mapped or value func - defaults to empty destruct.i_valclone
Func - [required if] i_valdrop is defined.i_valraw
Type - Convertion “raw” type - defaults to i_val.i_valfrom
Func - Convertion func i_val <= i_valraw.i_valto
Func - Convertion func i_val* => i_valraw.Specials: Meta-template parameters. Use instead of i_key
/ i_val
.
i_key_class
Type - Auto-set standard named functions: Type_clone(), Type_drop(), Type_cmp(), Type_eq(), Type_hash().i_keyraw
is defined, it sets i_keyto
= Type_toraw() and i_keyfrom
= Type_from().i_key_str
- Sets i_key_class
= cstr, i_tag
= str, and i_keyraw
= const char*. Defines both type convertioni_keyfrom
, i_keyto
, and sets i_cmp
, i_eq
, i_hash
functions with const char** as argument.i_key_ssv
- Sets i_key_class
= cstr, i_tag
= ssv, and i_keyraw
= csview*. Defines both type convertioni_keyfrom
, i_keyto
, and sets i_cmp
, i_eq
, i_hash
functions with csview* as argument.i_key_arcbox
Type - Use when Type is a smart pointer arc or box. Defines i_key_class = Type, and i_keyraw = Type*.i_val_class
Type, i_val_str
, i_val_ssv
, i_val_arcbox
- Similar rules as for key.Notes:
i_*clone
, you may define i_opt c_no_clone to disable clone functionality.i_key_class
, if i_keyraw is defined along with it, i_keyfrom may also be defined to enable the emplace-functions. NB: the signature for cmp, eq, and hash uses i_keyraw as input.The table below shows the template parameters which must be defined to support element search and sort for various containers versus element types.
For the containers marked optional, the features are disabled if the template parameter(s) are not defined. Note that the (integral type) columns also applies to “special” types, specified with i_key_str
, i_key_arcbox
, and i_key_class
, and not only true integral types like int
or float
.
Container | find (integral type) | sort (integral type) | | | find (struct elem) | sort (struct elem) | optional |
---|---|---|---|---|---|---|
stack, queue | n/a | n/a | n/a | n/a | n/a | |
vec, deq, list | i_use_cmp |
i_use_cmp |
i_eq |
i_cmp / i_less |
yes | |
box, arc | i_use_cmp |
i_use_cmp |
i_eq + i_hash |
i_cmp / i_less |
yes | |
hmap, hset | n/a | i_eq + i_hash |
n/a | no | ||
smap, sset | i_cmp / i_less |
i_cmp / i_less |
no | |||
pque | n/a | n/a | i_cmp / i_less |
no |
STC, like c++ STL, has two sets of methods for adding elements to containers. One set begins
with emplace, e.g. vec_X_emplace_back(). This is an ergonimic alternative to
vec_X_push_back() when dealing non-trivial container elements, e.g. strings, shared pointers or
other elements using dynamic memory or shared resources.
The emplace methods constructs / clones the given element when they are added
to the container. In contrast, the non-emplace methods moves the element into the
container.
Note: For containers with integral/trivial element types, or when neither i_keyraw/i_valraw
is defined,
the emplace functions are not available (or needed), as it can easier lead to mistakes.
non-emplace: Move | emplace: Embedded copy | Container |
---|---|---|
insert(), push() | emplace() | hmap, smap, hset, sset |
insert_or_assign() | emplace_or_assign() | hmap, smap |
push() | emplace() | queue, pque, stack |
push_back(), push() | emplace_back() | deq, list, vec |
push_front() | emplace_front() | deq, list |
Strings are the most commonly used non-trivial data type. STC containers have proper pre-defined
definitions for cstr container elements, so they are fail-safe to use both with the emplace
and non-emplace methods:
#define i_implement // define in ONE file to implement longer functions in cstr
#include "stc/cstr.h"
#define i_key_str // special macro to enable container of cstr
#include "stc/vec.h" // vector of string (cstr)
...
vec_str vec = {0};
cstr s = cstr_lit("a string literal");
const char* hello = "Hello";
vec_str_push(&vec, cstr_from(hello); // make a cstr from const char* and move it onto vec
vec_str_push(&vec, cstr_clone(s)); // make a cstr clone and move it onto vec
vec_str_emplace(&vec, "Yay, literal"); // internally make a cstr from const char*
vec_str_emplace(&vec, cstr_clone(s)); // <-- COMPILE ERROR: expects const char*
vec_str_emplace(&vec, cstr_str(&s)); // Ok: const char* input type.
cstr_drop(&s)
vec_str_drop(&vec);
This is made possible because the type configuration may be given an optional
conversion/“rawvalue”-type as template parameter, along with a back and forth conversion
methods to the container value type.
Rawvalues are primarily beneficial for lookup and map insertions, however the
emplace methods constructs cstr
-objects from the rawvalues, but only when required:
hmap_str_emplace(&map, "Hello", "world");
// Two cstr-objects were constructed by emplace
hmap_str_emplace(&map, "Hello", "again");
// No cstr was constructed because "Hello" was already in the map.
hmap_str_emplace_or_assign(&map, "Hello", "there");
// Only cstr_lit("there") constructed. "world" was destructed and replaced.
hmap_str_insert(&map, cstr_lit("Hello"), cstr_lit("you"));
// Two cstr's constructed outside call, but both destructed by insert
// because "Hello" existed. No mem-leak but less efficient.
it = hmap_str_find(&map, "Hello");
// No cstr constructed for lookup, although keys are cstr-type.
Apart from strings, maps and sets are normally used with trivial value types. However, the
last example on the hmap page demonstrates how to specify a map with non-trivial keys.
Name | Description | Container |
---|---|---|
erase() | key based | smap, sset, hmap, hset, cstr |
erase_at() | iterator based | smap, sset, hmap, hset, vec, deq, list |
erase_range() | iterator based | smap, sset, vec, deq, list |
erase_n() | index based | vec, deq, cstr |
remove() | remove all matching values | list |
Define i_type
instead of i_tag
, or i_TYPE
to define both i_type
and i_key
:
#define i_type MyVec
#define i_key int
// #define i_TYPE MyVec,int // shorthand
#include "stc/vec.h"
MyVec vec = {0};
MyVec_push(&vec, 42);
...
There are two ways to pre-declare templated containers in header files:
Create a dedicated header for the container type instance:
#ifndef PointVec_H_
#define PointVec_H_
// Do not to include user defined headers here if they use templated containers themselves
// NB! struct Point must be complete at this point!
#define i_TYPE PointVec,struct Point
#define i_header // Do not implement, only expose API
#include "stc/vec.h"
#endif
Usage from e.g. other headers is trivial:
#ifndef Dataset_H_
#define Dataset_H_
#include "Point.h" // include element type separately
#include "PointVec.h"
typedef struct Dataset {
PointVec vertices;
PointVec colors;
} Dataset;
...
#endif
Implement PointVec in a c-file:
#include "Point.h"
#define i_implement // define immediately before PointVec.h
#include "PointVec.h"
...
// Dataset.h
#ifndef Dataset_H_
#define Dataset_H_
#include "stc/types.h" // include various container data structure templates
// declare PointVec. Note: struct Point may be an incomplete/undeclared type.
forward_vec(PointVec, struct Point);
typedef struct Dataset {
PointVec vertices;
PointVec colors;
} Dataset;
void Dataset_drop(Dataset* self);
...
#endif
Define and use the “private” container in the c-file:
// Dataset.c
#include "Dataset.h"
#include "Point.h" // Point must be defined here.
#define i_is_forward // flag that the container was forward declared.
#define i_type PointVec
#define i_val struct Point
#include "stc/vec.h" // Implements PointVec with static linking by default
...
Sometimes it is useful to extend a container type to store extra data, e.g. a comparison
or allocator function pointer or a context which the function pointers can use. Most
libraries solve this by adding an opaque pointer (void*) or function pointer(s) into
the data structure for the user to manage. This solution has a few disadvantages: the
pointers are not typesafe, and they take up space when not needed. STC solves this by letting
the user create a container wrapper struct where both the container and extra data fields can
be stored. The template parameters may then access the extra data using the “container_of”
technique.
The example below shows how to customize containers to work with PostgreSQL memory management.
It adds a MemoryContext to each container by defining the i_extend
template parameter followed
the by inclusion of "stc/extend.h"
. Note that pgs_realloc
and pgs_free
is also passed the
allocated size of the given pointer, unlike standard realloc
and free
.
// stcpgs.h
#define pgs_malloc(sz) MemoryContextAlloc(c_extend()->memctx, sz)
#define pgs_calloc(n, sz) MemoryContextAllocZero(c_extend()->memctx, (n)*(sz))
#define pgs_realloc(p, old_sz, sz) (p ? repalloc(p, sz) : pgs_malloc(sz))
#define pgs_free(p, sz) (p ? pfree(p) : (void)0) // pfree/repalloc does not accept NULL.
#define i_allocator pgs
#define i_no_clone
#define i_extend MemoryContext memctx;
#include "stc/extend.h"
To use it, define both i_type
and i_base
(the container type) before including the custom header:
#define i_type IMap
#define i_base smap
#define i_key int
#define i_val int
#include "stcpgs.h"
// Note the wrapper struct type is IMap_ext. IMap is accessed by .get
void maptest()
{
IMap_ext map = {.memctx=CurrentMemoryContext};
c_forrange (i, 1, 16)
IMap_insert(&map.get, i*i, i);
c_foreach (i, IMap, map.get)
printf("%d:%d ", i.ref->first, i.ref->second);
IMap_drop(&map.get);
}
STC is generally very memory efficient. Memory usage for the different containers:
level
, but has no parent node.i_implement
or i_static
before including."stc/algorithm.h"
"stc/coroutine.h"
"stc/crand.h"
with the new API.
_uniform
and _normal
.i_use_cmp
to enable comparison for built-in i_key types (<, ==).i_key_class
still expects comparison functions to be defined.i_key_arcbox
compares stored pointers instead of pointed to values if comparison not defined.i_import
(i_extern deprecated).
i_import
before #include "stc/cstr.h"
will also define full utf8 case conversions.i_import
before #include "stc/cregex.h"
will also define cstr + utf8 tables.c_const_cast()
typesafe macro.c_flt_counter(i)
c_flt_getcount(i)
Major changes:
c_ARGSV()
=> c_SV()
: csview print arg. Note c_sv()
is shorthand for csview_from().c_forfilter
: container iteration with “piped” filtering using && operator. 4 built-in filters.c_forlist
: macro replacing c_forarray and c_apply. Iterate a compound literal list.*_range*
function names.cstr_replace_at*(self, pos, len, repl)
: Replace at specific position.cstr_replace_all() cstr_replace*(self, search, repl, count)
: Replace count occurences.cstr_find_from()
=> cstr_find_at()
cstr_*_u8()
=> cstr_u8_*()
csview_*_u8()
=> csview_u8_*()
csview_from_s()
: Use cstr_sv(s)
instead.CNT_size(const CNT *self)
CNT_capacity(const CNT *self)
CNT_empty(const CNT *self)
i_capacity
parameter: #define i_capacity <NUM>
. They then use fixed sized arrays, and no heap allocated memory.smap.h
: begin() on empty map was not fully initialized.cstr_str(&s)
must be used, s.str
is no longer usable.i_hash
template parameter and c_default_hash
. Please update your code.i_keyclone/i_valclone
template parameter: containers of smart pointers (arc, box) now correctly cloned.i_key*
template parameters instead of i_val*
for all containers, not only for hset and sset.