1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|

STC - Standard Template Containers
==================================
Introduction
------------
A modern, templated, user-friendly, fast, fully typesafe, and customizable container library for C99,
with a cross-containers uniform API, similar to c++ STL.
This is a compact, header-only library with the all the major "standard" data containers, except for the multi-map/set variants:
- [***carray*** - Templated **multi-dimensional array** type](docs/carray_api.md)
- [***cbits*** - A **std::bitset** / **boost::dynamic_bitset** alike type](docs/cbits_api.md)
- [***cdeq*** - Templated **std::deque** alike type](docs/cdeq_api.md)
- [***clist*** - Templated **std::forward_list** alike type](docs/clist_api.md)
- [***cmap*** - Templated **std::unordered_map** alike type](docs/cmap_api.md)
- [***cpque*** - Templated **std::priority_queue** alike adapter type](docs/cpque_api.md)
- [***cptr*** - Container pointers and **std::shared_ptr** alike support](docs/cptr_api.md)
- [***cqueue*** - Templated **std::queue** alike adapter type](docs/cqueue_api.md)
- [***cset*** - Templated **std::unordered_set** alike type](docs/cset_api.md)
- [***csmap*** - Templated sorted map **std::map** alike type](docs/csmap_api.md)
- [***csset*** - Templated sorted set **std::set** alike type](docs/csset_api.md)
- [***cstack*** - Templated **std::stack** alike adapter type](docs/cstack_api.md)
- [***cstr*** - A **std::string** alike type](docs/cstr_api.md)
- [***cvec*** - Templated **std::vector** alike type](docs/cvec_api.md)
Others:
- [***ccommon*** - General definitions and handy, safe-to-use macros](docs/ccommon_api.md)
- [***coption*** - Implements ***coption_get()***, similar to posix **getopt_long()**](docs/coption_api.md)
- [***crandom*** - A novel, extremely fast *PRNG* named **stc64**](docs/crandom_api.md)
Performance
-----------

STC containers performs either about equal or better than the c++ std counterparts. **cmap** *insert* is almost ***4x*** times faster
than *std::unordered_map* in this benchmark, and ***2x*** times faster than *erase*! Iteration and destruction is an order of magnitude
faster. **csmap** has noticable faster lookup than *std::map*'s typical red-black tree implementation. It uses an AA-tree
(Arne Andersson, 1993), which tends to create a flatter structure (more balanced) than red-black trees. **cvec** is only slighly slower than *std::vector*.
Notes:
- The barchart shows average test times from three platforms: Win-Clang++ v11, Mingw64 g++ 9.20, VC19. CPU: Ryzen 7 2700X CPU @4Ghz.
- Containers uses value types `uint64_t` and pairs of `uint64_t`for the maps.
- Black bars indicates performance variation between various platforms/compilers.
- Iterations are repeated 4 times over n elements.
- *find* is not executed for *forward_list*, *deque*, and *vector* because these c++ containers have no native *find*.
- **deque** - *insert*: n/3 push_front(), n/3 push_back()+push_front(), n/3 push_back().
- **map and unordered map** - *insert*: n/2 random numbers, n/2 sequential numbers. *erase*: n/2 keys are in the map, n/2 keys are random.
Highlights
----------
- **User friendly** - Super easy usage, just include the header and you are good to go. The API and functionality is very close to c++ STL, and is fully listed in the docs. The ***using_***-declaration instantiates the container type to use. You may pass *optional* arguments to it for customization of value- *comparison*, *destruction*, *cloning*, *convertion types*, and more.
- **Unparalleled performance** - All the containers are either equal or faster than c++ STL containers.
- **Fully memory managed** - All containers will destruct keys, values via destructor passed as macro parameters to the ***using_***-declaration. Also smart-pointers are supported and can be stored in containers, see ***csptr***.
- **Fully type safe** - Avoids error-prone casting of container types and elements back and forth from the containers.
- **Uniform API** - Methods to ***construct***, ***initialize***, ***iterate*** and ***destruct*** have a uniform and intuitive usage across the various containers.
- **Small footprint** - Small source code and generated executables. The executable from the example below using six different containers is *27 kb in size* compiled with TinyC.
- **Dual mode compilation** - By default it is a simple header-only library with inline and static methods only, but you can easily switch to create a traditional library with shared symbols, without changing existing source files. See next how-to.
- **No callback functions** - All passed template argument functions/macros are directly called from the implementation, no slow callbacks which requires storage.
Usage
-----
The usage of the containers is similar to the c++ standard containers in STL, so it should be easy if you are familiar with them.
All containers are generic/templated, except for **cstr** and **cbits**. No casting is used, so containers are typesafe like
templates in c++. A basic usage example:
```c
#include <stc/cvec.h>
using_cvec(i, int);
int main(void) {
cvec_i vec = cvec_i_init();
cvec_i_push_back(&vec, 10);
cvec_i_push_back(&vec, 20);
cvec_i_push_back(&vec, 30);
c_foreach (i, cvec_i, vec)
printf(" %d", *i.ref);
cvec_i_del(&vec);
}
```
And with multiple containers...
```c
#include <stc/cset.h>
#include <stc/cvec.h>
#include <stc/cdeq.h>
#include <stc/clist.h>
#include <stc/cqueue.h>
#include <stc/csmap.h>
#include <stdio.h>
struct Point { float x, y; };
int Point_compare(const struct Point* a, const struct Point* b) {
if (a->x != b->x) return 1 - 2*(a->x < b->x);
return (a->y > b->y) - (a->y < b->y);
}
// declare container types
using_cset(i, int); // unordered set
using_cvec(p, struct Point, Point_compare); // vector, struct as elements
using_cdeq(i, int); // deque
using_clist(i, int); // singly linked list
using_cqueue(i, cdeq_i); // queue, using deque as adapter
using_csmap(i, int, int); // sorted map
int main(void) {
// define and initialize
c_init (cset_i, set, {10, 20, 30});
c_init (cvec_p, vec, { {10, 1}, {20, 2}, {30, 3} });
c_init (cdeq_i, deq, {10, 20, 30});
c_init (clist_i, lst, {10, 20, 30});
c_init (cqueue_i, que, {10, 20, 30});
c_init (csmap_i, map, { {20, 2}, {10, 1}, {30, 3} });
// add one more element to each container
cset_i_insert(&set, 40);
cvec_p_push_back(&vec, (struct Point) {40, 4});
cdeq_i_push_front(&deq, 5);
clist_i_push_front(&lst, 5);
cqueue_i_push(&que, 40);
csmap_i_emplace(&map, 40, 4);
// find an element in each container
cset_i_iter_t i1 = cset_i_find(&set, 20);
cvec_p_iter_t i2 = cvec_p_find(&vec, (struct Point) {20, 2});
cdeq_i_iter_t i3 = cdeq_i_find(&deq, 20);
clist_i_iter_t i4 = clist_i_find_before(&lst, 20);
csmap_i_iter_t i5 = csmap_i_find(&map, 20);
printf("\nFound: %d, (%g, %g), %d, %d, [%d: %d]\n", *i1.ref, i2.ref->x, i2.ref->y, *i3.ref,
*clist_i_fwd(i4, 1).ref, i5.ref->first, i5.ref->second);
// erase the elements found
cset_i_erase_at(&set, i1);
cvec_p_erase_at(&vec, i2);
cdeq_i_erase_at(&deq, i3);
clist_i_erase_after(&lst, i4);
csmap_i_erase_at(&map, i5);
printf("After erasing elements found:");
printf("\n set:"); c_foreach (i, cset_i, set) printf(" %d", *i.ref);
printf("\n vec:"); c_foreach (i, cvec_p, vec) printf(" (%g, %g)", i.ref->x, i.ref->y);
printf("\n deq:"); c_foreach (i, cdeq_i, deq) printf(" %d", *i.ref);
printf("\n lst:"); c_foreach (i, clist_i, lst) printf(" %d", *i.ref);
printf("\n que:"); c_foreach (i, cqueue_i, que) printf(" %d", *i.ref);
printf("\n map:"); c_foreach (i, csmap_i, map) printf(" [%d: %d]", i.ref->first, i.ref->second);
// cleanup
cset_i_del(&set);
cvec_p_del(&vec);
cdeq_i_del(&deq);
clist_i_del(&lst);
cqueue_i_del(&que);
csmap_i_del(&map);
}
```
Output
```
Found: 20, (20, 2), 20, 20, [20: 2]
After erasing elements found:
set: 10 30 40
vec: (10, 1) (30, 3) (40, 4)
deq: 5 10 30
lst: 5 10 30
que: 10 20 30 40
map: [10: 1] [30: 3] [40: 4]
```
Installation
------------
Because it is headers-only, headers can simply be included in your program. The methods are static by default (some inlined).
You may add the project folder to **CPATH** environment variable, to let GCC, Clang, and TinyC locate the headers.
If containers are used accross several translation units with common instantiated container types, it is recommended to
build as a "library" to minimize the executable size. To enable this mode, specify **-DSTC_HEADER** as compiler option
in your build environment, and place all the instantiations of containers used in a single C source file, e.g.:
```c
// stc_libs.c
#define STC_IMPLEMENTATION
#include <stc/cstr.h>
#include <stc/cmap.h>
#include <stc/cvec.h>
#include <stc/clist.h>
#include "Point.h"
using_cmap(ii, int, int);
using_cset(ix, int64_t);
using_cvec(i, int);
using_clist(pt, struct Point);
```
Non POD-type container elements
-------------------------------
For STC containers with dynamic allocated elements e.g. **cstr**, the elements are ***moved*** into the containers, not *copied*! Methods like
**push_back()**, **push_front()**, **insert()**, **insert_or_assign()** takes element values. Typical usage is therefore:
```c
cvec_str_push_back(&vec, cstr_from("Hello"));
cvec_str_push_back(&vec, cstr_clone(mycstr));
cmap_str_insert_or_assign(&vec, cstr_from("Hello"), cstr_clone(mycstr));
```
However, most templated STC containers can simulate automatic type convertion. You may specify an optional
convertion/"rawvalue"-type as a template parameter in the **using_**-declaration, along with back and forth convertion methods
to the container value type. By default, *rawvalue has the same type as value*. Methods like **emplace_back()**,
**emplace_front()**, **emplace()**, **put()** takes the rawvalue-type instead of value. Adding literal strings to
containers with **cstr**-elements becomes:
```c
using_cvec_str(); // predefined using-statement for cvec of cstr, with 'const char*' as rawvalue type.
...
cvec_str_emplace_back(&vec, "Hello");
clist_str_emplace_front(&list, "Hello");
```
Rawvalues are also beneficial for **find()** and map insertions, because key and mapped values are only conditionally inserted.
The **emplace()** and **put()** methods constructs cstr-objects from the rawvalues only when they are needed:
```c
cmap_str_emplace(&map, "Hello", "world"); // no cstr constructed if "Hello" is already in the map.
cmap_str_put(&map, "Hello", "world"); // similar, but a cstr_from("world") call is always made in put.
it = cmap_str_find(&map, "Hello"); // no cstr constructed for lookup, although keys are cstr-type.
```
Memory efficiency
-----------------
STC containers are very memory efficent.
- **cstr**, **cvec**: Type size: one pointer. The size and capacity is stored as part of the heap allocation that also holds the vector elements.
- **clist**: Type size: one pointer. Each node allocates block storing value and next pointer.
- **cdeq**: Type size: two pointers. Otherwise like *cvec*.
- **cmap**: Type size: 4 pointers. *cmap* uses one table of keys+value, and one table of precomputed hash-value/used bucket, which occupies only one byte per bucket. The closed hashing has a default max load factor of 85%, and hash table scales by 1.5x when reaching that.
- **csmap**: Type size: 1 pointer. *csmap* manages its own array of tree-nodes for allocation efficiency. Each node uses two 32-bit words by default for left/right childs, and one byte for `level`. *csmap* can be configured to allow more than 2^32 elements, ie. 2^64, but it will double the overhead per node.
- **carray**: carray1, carray2 and carray3. Type size: One pointer plus one, two, or three size_t variables to store dimensions. Arrays are allocated as one contiguous block of heap memory.
- **csptr**: a shared-pointer uses two pointers, one for the data and one for the reference counter.
|