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.
STC is a comprehensive, high performance, typesafe and generic general purpose container and algorithms
library for C99 with excellent ergonomics and ease of use.
#define i_aux { ... }
.algorithm.h
i_type
lets you define container type plus i_key
and i_val
(or i_opt
) all in one line.i_keyclass
and i_valclass
to specify types with _drop()
and _clone()
functions defined.i_keypro
and i_valpro
to specify cstr
, box
and arc
types (users may also define pro-types).c_filter
(ranges-like), c_shuffle
, c_reverse
.See also version history for breaking changes in V5.0.
c_static_assert
, c_const_cast
, c_safe_cast
and macros for safe integer type casting.STC uses meson build system. Make sure to have meson and ninja installed, e.g. as a python pip package from a bash shell:
pip install meson ninja
export LIBRARY_PATH=$LIBRARY_PATH:~/.local/lib
export CPATH=$CPATH:~/.local/include
export CC=gcc
To create a build folder and to set the install folder to e.g. ~/.local:
meson setup --buildtype debug build --prefix ~/.local
cd build
ninja
ninja install
STC is mixed “headers-only” / traditional library, i.e the templated container headers (and the sort/lower_bound
algorithms) can simply be included - they have no library dependencies. By default, all templated functions are
static (many inlined). This is often optimal for both performance and compiled binary size. However, for frequently
used container type instances (more than 2-3 TUs), consider creating a separate header file for them, e.g.:
// intvec.h
#ifndef INTVEC_H_
#define INTVEC_H_
#define i_header // header definitions only
#define i_type intvec, int
#include "stc/vec.h"
#endif
So anyone may use the shared vec-type. Implement the shared functions in one C file (if several containers are shared,
you may define STC_IMPLEMENT on top of the file once instead):
// shared.c
#define i_implement // implement the shared intvec.
#include "intvec.h"
The non-templated types cstr, csview, cregex, cspan and random, are built as a library (libstc),
and is using the meson build system. However, the most common functions in csview and random are inlined.
The bitset cbits, the zero-terminated string view zsview and algorthm are all fully inlined and need no
linking with the stc-library.
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++. 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]);
for (c_each(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
}
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)
for (c_each(i, Floats, nums))
printf(" %g", *i.ref);
Floats_drop(&nums);
}
For associative containers and priority queues (hmap, hset, smap, sset, pqueue), comparison/lookup functions
are assumed to be defined. I.e. if they are not specified with template parameters, it assumes default
comparison operators works. To enable search/sort for the remaining containers (stack, vec, queue, deque),
define i_cmp
or i_eq
and/or i_less
for the element type. If the element type is an integral type,
just define i_use_cmp
(will use ==
and <
operators for comparisons).
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>
#include "stc/algorithm.h"
#define i_type Vec, float
#define i_use_cmp // enable default ==, < and hash operations
#include "stc/vec.h"
#define i_type Vec2D
#define i_keyclass Vec // Use i_keyclass when key type has "members" _clone() and _drop().
#define i_use_eq // vec does not have _cmp(), but it has _eq()
#include "stc/vec.h"
int main(void)
{
Vec* v;
Vec2D vec_a = {0}; // All containers in STC can be initialized with {0}.
v = Vec2D_push(&vec_a, 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_a, Vec_init());
Vec_push(v, 30.f);
Vec_push(v, 40.f);
Vec2D vec_b = c_make(Vec2D, {
c_make(Vec, {10.f, 20.f}),
c_make(Vec, {30.f, 40.f}),
});
printf("vec_a == vec_b is %s.\n", Vec2D_eq(&vec_a, &vec_b) ? "true":"false");
Vec2D clone = Vec2D_clone(vec_a); // Make a deep-copy of vec
for (c_each(i, Vec2D, clone)) // Loop through the cloned vector
for (c_each(j, Vec, *i.ref))
printf(" %g", *j.ref);
c_drop(Vec2D, &vec_a, &vec_b, &clone); // Free all 9 vectors.
}
This example uses four different container types:
[ Run this code ]
#include <stdio.h>
#define i_type hset_int, int
#include "stc/hset.h" // 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" // vector of struct Point
#define i_type list_int, int
#define i_use_cmp // enable sort/search. Use native `<` and `==` operators
#include "stc/list.h" // 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:");
for (c_each(i, hset_int, set))
printf(" %d", *i.ref);
printf("\n vec:");
for (c_each(i, vec_pnt, vec))
printf(" (%g, %g)", i.ref->x, i.ref->y);
printf("\n lst:");
for (c_each(i, list_int, lst))
printf(" %d", *i.ref);
printf("\n map:");
for (c_each(i, smap_int, map))
printf(" [%d: %d]", i.ref->first, i.ref->second);
}
}
STC is a fast and memory efficient library, and code compiles fast:
Benchmark notes:
uint64_t
and pairs of uint64_t
for the maps.i_type
ori_key
(+ i_val
for maps). In this case, STC assumes that the elements are of basic types.const char*
cstr
in ordervec_cstr_emplace(&vec, "Hello")
vec_cstr_push(&vec, cstr_from("Hello"))
.#define i_type MyInts, int
#include "stc/list.h"
...
MyInts ints = c_make(MyInts, {3, 5, 9, 7, 2});
for (c_each(it, MyInts, ints)) *it.ref += 42;
Naming conventions
c
, e.g. cstr
, cbits
, cregex
.c_
, e.g. c_each
, c_make
.i_
, e.g. i_key
, i_type
.{0}
(also cstr
), i.e. no heap allocation initially.Common types for any container type Cont:
Functions available for most all containers:
The container template parameters are specified with a #define i_xxxx
statement. Only i_key
is
strictly required. Each templated type instantiation requires an #include
statement, even if the
same container base type was included earlier. Possible template parameters are:
i_type
ContType, KeyType[, ValType] is a shorthand for defining i_type, i_key (and i_val)i_type
ContType - Custom container type name.i_key
KeyType - Element type. [required].i_val
MappedType - Element type. [required for] hmap and smap containers.i_cmp
Func - Three-way comparison of two i_keyraw*i_less
Func - Comparison of two i_keyraw* - an alternative to specifying i_cmp.i_eq
Func - Equality comparison of two i_keyraw* - defaults to !i_cmp. Companion with i_hash.i_hash
Func - Hash function taking i_keyraw* - defaults to c_default_hash. [required for]i_keydrop
Func - Destroy key - defaults to empty destructor.i_keyclone
Func - [required if] i_keydrop is defined (exception for arc, as it shares).i_keyraw
Type - Lookup and emplace “raw” type, defaults to i_key.i_keyfrom
Func - Convertion func from i_keyraw to i_key.i_keytoraw
Func - Convertion func to i_keyraw from i_key. [required if] i_keyraw is definedi_valdrop
, i_valclone
, i_valraw
, etc.The following meta-template parameters can be specified instead of i_key, i_val, and i_keyraw.
These parameters make types into “classes” in the sense that they bind associated function names to the basic
template parameters described above. This reduces boiler-plate code and simplifies the management
of non-trivial container elements. Note that many basic template parameters will be defined when defining the
following parameters, but the user may override those when needed. E.g. by defining the template parameters
directly as macro functions or with macros that refer to the C function names.
i_rawclass
RawType - Defines i_keyraw and binds RawType_cmp(), RawType_eq(), RawType_hash()i_keyclass
KeyType - Defines i_key and binds standard named functions KeyType_clone() andi_keyraw
is also specified, KeyType_from()i_keypro
KeyType - Use with “pro”-element types, i.e. library types like cstr, box and arc.i_key
, i_keyclone
, i_keydrop
, i_keyraw
, i_keyfrom
, i_keytoraw
, i_cmp
, i_eq
, i_hash
will all be defined/bound.i_valclass
MappedType - Analogous to the i_keyclass parameter.i_valpro
MappedType - Comparison functions are not relevant for the mapped type, so this defines
i_val
, i_valclone
, i_valdrop
, i_valraw
, i_valfrom
, i_valtoraw
will all be defined/bound.Option flags:
i_opt
Flags - Boolean properties: may combine c_no_clone, c_no_atomic, c_declared, c_static,|
separator.Notes:
i_no_clone
or i_opt c_no_clone | c_... | ...
to disable clone functionality.i_has_cmp
instead of the comparison parameters to enable searching / sorting for integralThe table below shows the template parameters which must be defined to support element search/lookup and sort for various container type instantiations.
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” key-types, specified with i_keyclass
(so not only for true integral types like int
or float
).
Container | search (integral type) | sort (integral type) | | | search (struct elem) | sort (struct elem) | optional |
---|---|---|---|---|---|---|
vec, deque, list | i_use_cmp |
i_use_cmp |
i_eq |
i_cmp / i_less |
yes | |
stack | n/a | i_use_cmp |
n/a | 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 | |||
pqueue | n/a | n/a | i_cmp / i_less |
no | ||
queue | n/a | n/a | n/a | n/a | n/a |
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, pqueue, stack |
push_back(), push() | emplace_back() | deque, list, vec |
push_front() | emplace_front() | deque, 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:
#include "stc/cstr.h"
#define i_keypro cstr // use i_keypro for "pro" types like cstr, arc, box
#include "stc/vec.h" // vector of string (cstr)
...
vec_cstr vec = {0};
cstr s = cstr_lit("a string literal");
const char* hello = "Hello";
vec_cstr_push(&vec, cstr_from(hello); // make a cstr from const char* and move it onto vec
vec_cstr_push(&vec, cstr_clone(s)); // make a cstr clone and move it onto vec
vec_cstr_emplace(&vec, "Yay, literal"); // internally make a cstr from const char*
vec_cstr_emplace(&vec, cstr_clone(s)); // <-- COMPILE ERROR: expects const char*
vec_cstr_emplace(&vec, cstr_str(&s)); // Ok: const char* input type.
cstr_drop(&s)
vec_cstr_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_cstr_emplace(&map, "Hello", "world");
// Two cstr-objects were constructed by emplace
hmap_cstr_emplace(&map, "Hello", "again");
// No cstr was constructed because "Hello" was already in the map.
hmap_cstr_emplace_or_assign(&map, "Hello", "there");
// Only cstr_lit("there") constructed. "world" was destructed and replaced.
hmap_cstr_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_cstr_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, deque, list |
erase_range() | iterator based | smap, sset, vec, deque, list |
erase_n() | index based | vec, deque, cstr |
remove() | remove all matching values | list |
Define i_type
and/or i_key
:
// #define i_type MyVec, int // shorthand
#define i_type MyVec
#define i_key int
#include "stc/vec.h"
MyVec vec = {0};
MyVec_push(&vec, 42);
...
Pre-declare templated container in header file. The container can then e.g. be a “private”
member of a struct defined in a header file.
// Dataset.h
#ifndef Dataset_H_
#define Dataset_H_
#include "stc/types.h" // include various container data structure templates
// declare PointVec as a vec. Also struct Point may be incomplete/undeclared.
declare_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" // struct Point must be defined here.
#define i_type PointVec, struct Point
#define i_declared // must flag that the container was pre-declared.
#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. Because most containers are templated,
an extra template parameter, i_aux
may be defined to extend the container with
typesafe custom attributes.
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_aux
template parameter.
Note that pgs_realloc
and pgs_free
is also passed the
allocated size of the given pointer, unlike standard realloc
and free
.
self->aux
is accessible from the following template parameters / container combinations:
i_malloc
, i_calloc
, i_realloc
, i_free
: all containersi_eq
: all containersi_cmp
, i_less
: all containers except hmap and hseti_hash
: hmap and hset// stcpgs.h
#define pgs_malloc(sz) MemoryContextAlloc(self->aux.memctx, sz)
#define pgs_calloc(n, sz) MemoryContextAllocZero(self->aux.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_aux { MemoryContext memctx; } // NB: enclose in curly braces!
#define i_allocator pgs
#define i_no_clone
Usage is straight forward:
#define i_type IMap, int, int
#include "stcpgs.h"
#include "stc/smap.h"
void maptest()
{
IMap map = {.aux={CurrentMemoryContext}};
for (c_range(i, 1, 16))
IMap_insert(&map, i*i, i); // uses pgs_malloc
for (c_each(i, IMap, map))
printf("%d:%d ", i.ref->first, i.ref->second);
IMap_drop(&map);
}
Another example is to sort struct elements by the active field and reverse flag:
[ Run this code ]
#include <stdio.h>
#include <time.h>
#include <stc/cstr.h>
#include <c11/fmt.h>
typedef struct {
cstr fileName;
cstr directory;
isize size;
time_t lastWriteTime;
} FileMetaData;
enum FMDActive {FMD_fileName, FMD_directory, FMD_size, FMD_lastWriteTime};
struct FMDVector_aux; // defined when specifying i_aux
int FileMetaData_cmp(const struct FMDVector_aux*, const FileMetaData*, const FileMetaData*);
void FileMetaData_drop(FileMetaData*);
#define i_type FMDVector, FileMetaData
#define i_aux { enum FMDActive activeField; bool reverse; }
#define i_cmp(x, y) FileMetaData_cmp(&self->aux, x, y)
#define i_keydrop FileMetaData_drop
#define i_no_clone
#include <stc/stack.h>
// --------------
int FileMetaData_cmp(const struct FMDVector_aux* aux, const FileMetaData* a, const FileMetaData* b) {
int dir = aux->reverse ? -1 : 1;
switch (aux->activeField) {
case FMD_fileName: return dir*cstr_cmp(&a->fileName, &b->fileName);
case FMD_directory: return dir*cstr_cmp(&a->directory, &b->directory);
case FMD_size: return dir*c_default_cmp(&a->size, &b->size);
case FMD_lastWriteTime: return dir*c_default_cmp(&a->lastWriteTime, &b->lastWriteTime);
}
return 0;
}
void FileMetaData_drop(FileMetaData* fmd) {
cstr_drop(&fmd->fileName);
cstr_drop(&fmd->directory);
}
int main(void) {
FMDVector vec = c_make(FMDVector, {
{cstr_from("WScript.cpp"), cstr_from("code/unix"), 3624, 123567},
{cstr_from("CanvasBackground.cpp"), cstr_from("code/unix/canvas"), 38273, 12398},
{cstr_from("Brush_test.cpp"), cstr_from("code/tests"), 67236, 7823},
});
vec.aux.reverse = true;
for (c_range(activeField, FMD_lastWriteTime + 1)) {
vec.aux.activeField = (enum FMDActive)activeField;
FMDVector_sort(&vec);
for (c_each(i, FMDVector, vec)) {
fmt_println("{:30}{:30}{:10}{:10}",
cstr_str(&i.ref->fileName), cstr_str(&i.ref->directory),
i.ref->size, i.ref->lastWriteTime);
}
puts("");
}
FMDVector_drop(&vec);
}
STC is generally very memory efficient. Memory usage for the different containers:
level
, but has no parent node.cregex
.cstr
and csview
.zsview
, some API changes.Container_is_empty()
.random
numbers.c_make
c_foritems
c_each_kv
(changed API).c_<xxxx>()
in common.h.c_svfmt(sv)
c_svarg(sv)
cco_yield
.c_fortoken()
to make it consistent with all other c_for*()
, i.e, input object is third last.vec.h
renamed from cvec.hdeque.h
renamed from cdeq.hlist.h
renamed from clist.hstack.h
renamed from cstack.hqueue.h
renamed from cqueue.hpqueue.h
renamed from cpque.hhmap.h
renamed from cmap.hhset.h
renamed from cset.hsmap.h
renamed from csmap.hsset.h
renamed from csset.hzsview.h
renamed from czview.hrandom.h
renamed from crand.htypes.h
renamed from forward.hi_implement
or i_static
before including."stc/algorithm.h"
"stc/coroutine.h"
"stc/random.h"
with the new API.
_uniform
and _normal
.i_use_cmp
to enable comparison for built-in i_key types (<, ==).i_keyclass
still expects comparison functions to be 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_svarg()
: 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_foritems
: macro replacing c_forarray and c_apply. Iterate a compound literal list.*_range*
function names.