update ucx default tip

Thu, 18 Dec 2025 17:50:15 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 18 Dec 2025 17:50:15 +0100
changeset 1016
ccde46662db7
parent 1015
b459361d98ad

update ucx

application/main.c file | annotate | diff | comparison | revisions
ucx/array_list.c file | annotate | diff | comparison | revisions
ucx/buffer.c file | annotate | diff | comparison | revisions
ucx/compare.c file | annotate | diff | comparison | revisions
ucx/cx/allocator.h file | annotate | diff | comparison | revisions
ucx/cx/array_list.h file | annotate | diff | comparison | revisions
ucx/cx/buffer.h file | annotate | diff | comparison | revisions
ucx/cx/collection.h file | annotate | diff | comparison | revisions
ucx/cx/compare.h file | annotate | diff | comparison | revisions
ucx/cx/hash_map.h file | annotate | diff | comparison | revisions
ucx/cx/iterator.h file | annotate | diff | comparison | revisions
ucx/cx/json.h file | annotate | diff | comparison | revisions
ucx/cx/kv_list.h file | annotate | diff | comparison | revisions
ucx/cx/linked_list.h file | annotate | diff | comparison | revisions
ucx/cx/list.h file | annotate | diff | comparison | revisions
ucx/cx/map.h file | annotate | diff | comparison | revisions
ucx/cx/properties.h file | annotate | diff | comparison | revisions
ucx/cx/streams.h file | annotate | diff | comparison | revisions
ucx/cx/string.h file | annotate | diff | comparison | revisions
ucx/hash_map.c file | annotate | diff | comparison | revisions
ucx/iterator.c file | annotate | diff | comparison | revisions
ucx/json.c file | annotate | diff | comparison | revisions
ucx/kv_list.c file | annotate | diff | comparison | revisions
ucx/linked_list.c file | annotate | diff | comparison | revisions
ucx/list.c file | annotate | diff | comparison | revisions
ucx/map.c file | annotate | diff | comparison | revisions
ucx/properties.c file | annotate | diff | comparison | revisions
ucx/streams.c file | annotate | diff | comparison | revisions
ucx/string.c file | annotate | diff | comparison | revisions
ucx/tree.c file | annotate | diff | comparison | revisions
ui/cocoa/button.m file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/menu.c file | annotate | diff | comparison | revisions
ui/common/message.c file | annotate | diff | comparison | revisions
ui/common/object.c file | annotate | diff | comparison | revisions
ui/common/properties.c file | annotate | diff | comparison | revisions
ui/common/toolbar.c file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/common/utils.c file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/container.c file | annotate | diff | comparison | revisions
ui/gtk/dnd.c file | annotate | diff | comparison | revisions
ui/gtk/icon.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/motif/button.c file | annotate | diff | comparison | revisions
ui/motif/container.c file | annotate | diff | comparison | revisions
ui/motif/menu.c file | annotate | diff | comparison | revisions
ui/motif/pathbar.c file | annotate | diff | comparison | revisions
ui/server/button.c file | annotate | diff | comparison | revisions
ui/winui/text.cpp file | annotate | diff | comparison | revisions
--- a/application/main.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/application/main.c	Thu Dec 18 17:50:15 2025 +0100
@@ -783,7 +783,7 @@
         ui_tab(obj, "Tab 9") {
 #ifdef UI_WEBVIEW
           ui_webview(obj, .fill = TRUE, .value = doc->web);
-          cxstring html = CX_STR("<html><body><h1>Hello Toolkit</h1><p>Toolkit WebView</p></body></html>");
+          cxstring html = cx_str("<html><body><h1>Hello Toolkit</h1><p>Toolkit WebView</p></body></html>");
           ui_webview_load_content(doc->web, "mypage", html.ptr, html.length, NULL, NULL);
 #else
           ui_label(obj, .label = "Webview is not supported");
--- a/ucx/array_list.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/array_list.c	Thu Dec 18 17:50:15 2025 +0100
@@ -32,76 +32,6 @@
 #include <string.h>
 #include <errno.h>
 
-// Default array reallocator
-
-static void *cx_array_default_realloc(
-        void *array,
-        cx_attr_unused size_t old_capacity,
-        size_t new_capacity,
-        size_t elem_size,
-        cx_attr_unused CxArrayReallocator *alloc
-) {
-    size_t n;
-    // LCOV_EXCL_START
-    if (cx_szmul(new_capacity, elem_size, &n)) {
-        errno = EOVERFLOW;
-        return NULL;
-    } // LCOV_EXCL_STOP
-    return cxReallocDefault(array, n);
-}
-
-CxArrayReallocator cx_array_default_reallocator_impl = {
-        cx_array_default_realloc, NULL, NULL
-};
-
-CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
-
-// Stack-aware array reallocator
-
-static void *cx_array_advanced_realloc(
-        void *array,
-        size_t old_capacity,
-        size_t new_capacity,
-        size_t elem_size,
-        cx_attr_unused CxArrayReallocator *alloc
-) {
-    // check for overflow
-    size_t n;
-    // LCOV_EXCL_START
-    if (cx_szmul(new_capacity, elem_size, &n)) {
-        errno = EOVERFLOW;
-        return NULL;
-    } // LCOV_EXCL_STOP
-
-    // retrieve the pointer to the actual allocator
-    const CxAllocator *al = alloc->allocator;
-
-    // check if the array is still located on the stack
-    void *newmem;
-    if (array == alloc->stack_ptr) {
-        newmem = cxMalloc(al, n);
-        if (newmem != NULL && array != NULL) {
-            memcpy(newmem, array, old_capacity*elem_size);
-        }
-    } else {
-        newmem = cxRealloc(al, array, n);
-    }
-    return newmem;
-}
-
-struct cx_array_reallocator_s cx_array_reallocator(
-        const struct cx_allocator_s *allocator,
-        const void *stack_ptr
-) {
-    if (allocator == NULL) {
-        allocator = cxDefaultAllocator;
-    }
-    return (struct cx_array_reallocator_s) {
-            cx_array_advanced_realloc,
-            allocator, stack_ptr,
-    };
-}
-
 // LOW LEVEL ARRAY LIST FUNCTIONS
 
 /**
@@ -128,295 +58,139 @@
     return cap - (cap % alignment) + alignment;
 }
 
-int cx_array_reserve(
-        void **array,
-        void *size,
-        void *capacity,
-        unsigned width,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-) {
-    // assert pointers
-    assert(array != NULL);
-    assert(size != NULL);
-    assert(capacity != NULL);
+int cx_array_init_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) {
+    memset(array, 0, sizeof(CxArray));
+    return cx_array_reserve_(allocator, array, elem_size, capacity);
+}
+
+void cx_array_init_fixed_(CxArray *array, const void *data, size_t capacity, size_t size) {
+    array->data = (void*) data;
+    array->capacity = capacity;
+    array->size = size;
+}
+
+int cx_array_reserve_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) {
+    if (cxReallocateArray(allocator, &array->data, capacity, elem_size)) {
+        return -1; // LCOV_EXCL_LINE
+    }
+    array->capacity = capacity;
+    if (array->size > capacity) {
+        array->size = capacity;
+    }
+    return 0;
+}
 
-    // default reallocator
-    if (reallocator == NULL) {
-        reallocator = cx_array_default_reallocator;
+int cx_array_copy_to_new_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) {
+    CxArray heap_array;
+    if (cx_array_init_(allocator, &heap_array, elem_size, capacity)) {
+        return -1; // LCOV_EXCL_LINE
+    }
+    heap_array.size = array->size;
+    memcpy(heap_array.data, array->data, elem_size * array->size);
+    *array = heap_array;
+    return 0;
+}
+
+int cx_array_insert_(const CxAllocator *allocator, CxArray *array,
+        size_t elem_size, size_t index, const void *other, size_t n) {
+    // out of bounds and special case check
+    if (index > array->size) return -1;
+    if (n == 0) return 0;
+
+    // guarantee enough capacity
+    if (array->capacity < array->size + n) {
+        const size_t new_capacity = cx_array_grow_capacity(array->capacity,array->size + n);
+        if (cxReallocateArray(allocator, &array->data, new_capacity, elem_size)) {
+            return -1; // LCOV_EXCL_LINE
+        }
+        array->capacity = new_capacity;
     }
 
-    // determine size and capacity
-    size_t oldcap;
-    size_t oldsize;
-    size_t max_size;
-    if (width == 0 || width == sizeof(size_t)) {
-        oldcap = *(size_t*) capacity;
-        oldsize = *(size_t*) size;
-        max_size = SIZE_MAX;
-    } else if (width == sizeof(uint16_t)) {
-        oldcap = *(uint16_t*) capacity;
-        oldsize = *(uint16_t*) size;
-        max_size = UINT16_MAX;
-    } else if (width == sizeof(uint8_t)) {
-        oldcap = *(uint8_t*) capacity;
-        oldsize = *(uint8_t*) size;
-        max_size = UINT8_MAX;
-    }
-#if CX_WORDSIZE == 64
-    else if (width == sizeof(uint32_t)) {
-        oldcap = *(uint32_t*) capacity;
-        oldsize = *(uint32_t*) size;
-        max_size = UINT32_MAX;
-    }
-#endif
-    else {
-        errno = EINVAL;
-        return 1;
+    // determine insert position
+    char *dst = array->data;
+    dst += index * elem_size;
+
+    // do we need to move some elements?
+    if (index < array->size) {
+        size_t elems_to_move = array->size - index;
+        char *target = dst + n * elem_size;
+        memmove(target, dst, elems_to_move * elem_size);
     }
 
-    // assert that the array is allocated when it has capacity
-    assert(*array != NULL || oldcap == 0);
-
-    // check for overflow
-    if (elem_count > max_size - oldsize) {
-        errno = EOVERFLOW;
-        return 1;
+    // place the new elements, if any
+    // otherwise, this function just reserved the memory (a.k.a emplace)
+    if (other != NULL) {
+        memcpy(dst, other, n * elem_size);
     }
-
-    // determine new capacity
-    size_t newcap = oldsize + elem_count;
-
-    // reallocate if possible
-    if (newcap > oldcap) {
-        void *newmem = reallocator->realloc(
-                *array, oldcap, newcap, elem_size, reallocator
-        );
-        if (newmem == NULL) {
-            return 1; // LCOV_EXCL_LINE
-        }
-
-        // store new pointer
-        *array = newmem;
-
-        // store new capacity
-        if (width == 0 || width == sizeof(size_t)) {
-            *(size_t*) capacity = newcap;
-        } else if (width == sizeof(uint16_t)) {
-            *(uint16_t*) capacity = (uint16_t) newcap;
-        } else if (width == sizeof(uint8_t)) {
-            *(uint8_t*) capacity = (uint8_t) newcap;
-        }
-#if CX_WORDSIZE == 64
-        else if (width == sizeof(uint32_t)) {
-            *(uint32_t*) capacity = (uint32_t) newcap;
-        }
-#endif
-    }
+    array->size += n;
 
     return 0;
 }
 
-int cx_array_copy(
-        void **target,
-        void *size,
-        void *capacity,
-        unsigned width,
-        size_t index,
-        const void *src,
+int cx_array_insert_sorted_s_(
+        const CxAllocator *allocator,
+        CxArray *array,
         size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
+        const void *sorted_data,
+        size_t n,
+        bool allow_duplicates,
+        cx_compare_func2 cmp_func,
+        void *context
 ) {
     // assert pointers
-    assert(target != NULL);
-    assert(size != NULL);
-    assert(capacity != NULL);
-    assert(src != NULL);
-
-    // default reallocator
-    if (reallocator == NULL) {
-        reallocator = cx_array_default_reallocator;
-    }
-
-    // determine size and capacity
-    size_t oldcap;
-    size_t oldsize;
-    size_t max_size;
-    if (width == 0 || width == sizeof(size_t)) {
-        oldcap = *(size_t*) capacity;
-        oldsize = *(size_t*) size;
-        max_size = SIZE_MAX;
-    } else if (width == sizeof(uint16_t)) {
-        oldcap = *(uint16_t*) capacity;
-        oldsize = *(uint16_t*) size;
-        max_size = UINT16_MAX;
-    } else if (width == sizeof(uint8_t)) {
-        oldcap = *(uint8_t*) capacity;
-        oldsize = *(uint8_t*) size;
-        max_size = UINT8_MAX;
-    }
-#if CX_WORDSIZE == 64
-    else if (width == sizeof(uint32_t)) {
-        oldcap = *(uint32_t*) capacity;
-        oldsize = *(uint32_t*) size;
-        max_size = UINT32_MAX;
-    }
-#endif
-    else {
-        errno = EINVAL;
-        return 1;
-    }
-
-    // assert that the array is allocated when it has capacity
-    assert(*target != NULL || oldcap == 0);
-
-    // check for overflow
-    if (index > max_size || elem_count > max_size - index) {
-        errno = EOVERFLOW;
-        return 1;
-    }
-
-    // check if resize is required
-    const size_t minsize = index + elem_count;
-    const size_t newsize = oldsize < minsize ? minsize : oldsize;
-
-    // reallocate if necessary
-    const size_t newcap = cx_array_grow_capacity(oldcap, newsize);
-    if (newcap > oldcap) {
-        // check if we need to repair the src pointer
-        uintptr_t targetaddr = (uintptr_t) *target;
-        uintptr_t srcaddr = (uintptr_t) src;
-        bool repairsrc = targetaddr <= srcaddr
-                         && srcaddr < targetaddr + oldcap * elem_size;
-
-        // perform reallocation
-        void *newmem = reallocator->realloc(
-                *target, oldcap, newcap, elem_size, reallocator
-        );
-        if (newmem == NULL) {
-            return 1; // LCOV_EXCL_LINE
-        }
-
-        // repair src pointer, if necessary
-        if (repairsrc) {
-            src = ((char *) newmem) + (srcaddr - targetaddr);
-        }
-
-        // store new pointer
-        *target = newmem;
-    }
-
-    // determine target pointer
-    char *start = *target;
-    start += index * elem_size;
-
-    // copy elements and set new size
-    // note: no overflow check here, b/c we cannot get here w/o allocation
-    memmove(start, src, elem_count * elem_size);
-
-    // if any of size or capacity changed, store them back
-    if (newsize != oldsize || newcap != oldcap) {
-        if (width == 0 || width == sizeof(size_t)) {
-            *(size_t*) capacity = newcap;
-            *(size_t*) size = newsize;
-        } else if (width == sizeof(uint16_t)) {
-            *(uint16_t*) capacity = (uint16_t) newcap;
-            *(uint16_t*) size = (uint16_t) newsize;
-        } else if (width == sizeof(uint8_t)) {
-            *(uint8_t*) capacity = (uint8_t) newcap;
-            *(uint8_t*) size = (uint8_t) newsize;
-        }
-#if CX_WORDSIZE == 64
-        else if (width == sizeof(uint32_t)) {
-            *(uint32_t*) capacity = (uint32_t) newcap;
-            *(uint32_t*) size = (uint32_t) newsize;
-        }
-#endif
-    }
-
-    // return successfully
-    return 0;
-}
-
-static int cx_array_insert_sorted_impl(
-        void **target,
-        size_t *size,
-        size_t *capacity,
-        cx_compare_func cmp_func,
-        const void *sorted_data,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator,
-        bool allow_duplicates
-) {
-    // assert pointers
-    assert(target != NULL);
-    assert(size != NULL);
-    assert(capacity != NULL);
+    assert(allocator != NULL);
+    assert(array != NULL);
     assert(cmp_func != NULL);
     assert(sorted_data != NULL);
 
-    // default reallocator
-    if (reallocator == NULL) {
-        reallocator = cx_array_default_reallocator;
-    }
-
     // corner case
-    if (elem_count == 0) return 0;
+    if (n == 0) return 0;
 
     // overflow check
     // LCOV_EXCL_START
-    if (elem_count > SIZE_MAX - *size) {
+    if (n > SIZE_MAX - array->size) {
         errno = EOVERFLOW;
         return 1;
     }
     // LCOV_EXCL_STOP
 
     // store some counts
-    const size_t old_size = *size;
-    const size_t old_capacity = *capacity;
+    const size_t old_size = array->size;
+    const size_t old_capacity = array->capacity;
     // the necessary capacity is the worst case assumption, including duplicates
-    const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + elem_count);
+    const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + n);
 
     // if we need more than we have, try a reallocation
     if (needed_capacity > old_capacity) {
-        void *new_mem = reallocator->realloc(
-                *target, old_capacity, needed_capacity, elem_size, reallocator
-        );
-        if (new_mem == NULL) {
-            // give it up right away, there is no contract
-            // that requires us to insert as much as we can
-            return 1;  // LCOV_EXCL_LINE
+        if (cxReallocateArray(allocator, &array->data, needed_capacity, elem_size)) {
+            return -1; // LCOV_EXCL_LINE
         }
-        *target = new_mem;
-        *capacity = needed_capacity;
+        array->capacity = needed_capacity;
     }
 
     // now we have guaranteed that we can insert everything
-    size_t new_size = old_size + elem_count;
-    *size = new_size;
+    size_t new_size = old_size + n;
+    array->size = new_size;
 
     // declare the source and destination indices/pointers
     size_t si = 0, di = 0;
     const char *src = sorted_data;
-    char *dest = *target;
+    char *dest = array->data;
 
     // find the first insertion point
-    di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func);
+    di = cx_array_binary_search_sup_c(dest, old_size, elem_size, src, cmp_func, context);
     dest += di * elem_size;
 
     // move the remaining elements in the array completely to the right
     // we will call it the "buffer" for parked elements
     size_t buf_size = old_size - di;
     size_t bi = new_size - buf_size;
-    char *bptr = ((char *) *target) + bi * elem_size;
+    char *bptr = ((char *) array->data) + bi * elem_size;
     memmove(bptr, dest, buf_size * elem_size);
 
     // while there are both source and buffered elements left,
     // copy them interleaving
-    while (si < elem_count && bi < new_size) {
+    while (si < n && bi < new_size) {
         // determine how many source elements can be inserted.
         // the first element that shall not be inserted is the smallest element
         // that is strictly larger than the first buffered element
@@ -430,8 +204,8 @@
         // Therefore, the buffer can never contain an element that is smaller
         // than any element in the source and the infimum exists.
         size_t copy_len, bytes_copied;
-        copy_len = cx_array_binary_search_inf(
-            src, elem_count - si, elem_size, bptr, cmp_func
+        copy_len = cx_array_binary_search_inf_c(
+            src, n - si, elem_size, bptr, cmp_func, context
         );
         copy_len++;
 
@@ -450,17 +224,17 @@
                 // for being a duplicate of the bptr
                 const char *end_of_src = src + (copy_len - 1) * elem_size;
                 size_t skip_len = 0;
-                while (copy_len > 0 && cmp_func(bptr, end_of_src) == 0) {
+                while (copy_len > 0 && cmp_func(bptr, end_of_src, context) == 0) {
                     end_of_src -= elem_size;
                     skip_len++;
                     copy_len--;
                 }
-                char *last = dest == *target ? NULL : dest - elem_size;
+                char *last = dest == array->data ? NULL : dest - elem_size;
                 // then iterate through the source chunk
                 // and skip all duplicates with the last element in the array
                 size_t more_skipped = 0;
                 for (unsigned j = 0; j < copy_len; j++) {
-                    if (last != NULL && cmp_func(last, src) == 0) {
+                    if (last != NULL && cmp_func(last, src, context) == 0) {
                         // duplicate - skip
                         src += elem_size;
                         si++;
@@ -479,20 +253,21 @@
                 si += skip_len;
                 skip_len += more_skipped;
                 // reduce the actual size by the number of skipped elements
-                *size -= skip_len;
+                array->size -= skip_len;
             }
         }
 
         // when all source elements are in place, we are done
-        if (si >= elem_count) break;
+        if (si >= n) break;
 
         // determine how many buffered elements need to be restored
-        copy_len = cx_array_binary_search_sup(
+        copy_len = cx_array_binary_search_sup_c(
                 bptr,
                 new_size - bi,
                 elem_size,
                 src,
-                cmp_func
+                cmp_func,
+                context
         );
 
         // restore the buffered elements
@@ -505,24 +280,24 @@
     }
 
     // still source elements left?
-    if (si < elem_count) {
+    if (si < n) {
         if (allow_duplicates) {
             // duplicates allowed or nothing inserted yet: simply copy everything
-            memcpy(dest, src, elem_size * (elem_count - si));
+            memcpy(dest, src, elem_size * (n - si));
         } else {
             // we must check the remaining source elements one by one
             // to skip the duplicates.
             // Note that no source element can equal the last element in the
             // destination, because that would have created an insertion point
             // and a buffer, s.t. the above loop already handled the duplicates
-            while (si < elem_count) {
+            while (si < n) {
                 // find a chain of elements that can be copied
                 size_t copy_len = 1, skip_len = 0;
                 {
                     const char *left_src = src;
-                    while (si + copy_len + skip_len < elem_count) {
+                    while (si + copy_len + skip_len < n) {
                         const char *right_src = left_src + elem_size;
-                        int d = cmp_func(left_src,  right_src);
+                        int d = cmp_func(left_src,  right_src, context);
                         if (d < 0) {
                             if (skip_len > 0) {
                                 // new larger element found;
@@ -545,13 +320,13 @@
                 src += bytes_copied + skip_len * elem_size;
                 si += copy_len + skip_len;
                 di += copy_len;
-                *size -= skip_len;
+                array->size -= skip_len;
             }
         }
     }
 
     // buffered elements need to be moved when we skipped duplicates
-    size_t total_skipped = new_size - *size;
+    size_t total_skipped = new_size - array->size;
     if (bi < new_size && total_skipped > 0) {
         // move the remaining buffer to the end of the array
         memmove(dest, bptr, elem_size * (new_size - bi));
@@ -560,41 +335,43 @@
     return 0;
 }
 
-int cx_array_insert_sorted(
-        void **target,
-        size_t *size,
-        size_t *capacity,
+int cx_array_insert_sorted_(
+        const CxAllocator *allocator,
+        CxArray *array,
+        size_t elem_size,
         cx_compare_func cmp_func,
         const void *sorted_data,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
+        size_t n,
+        bool allow_duplicates
 ) {
-    return cx_array_insert_sorted_impl(target, size, capacity,
-        cmp_func, sorted_data, elem_size, elem_count, reallocator, true);
+    cx_compare_func_wrapper wrapper = {cmp_func};
+    return cx_array_insert_sorted_s_(allocator, array, elem_size, sorted_data,
+        n, allow_duplicates, cx_acmp_wrap, &wrapper);
 }
 
-int cx_array_insert_unique(
-        void **target,
-        size_t *size,
-        size_t *capacity,
-        cx_compare_func cmp_func,
-        const void *sorted_data,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-) {
-    return cx_array_insert_sorted_impl(target, size, capacity,
-        cmp_func, sorted_data, elem_size, elem_count, reallocator, false);
+CxIterator cx_array_iterator_(CxArray *array, size_t elem_size) {
+    return cxIterator(array->data, elem_size, array->size);
+}
+
+CxIterator cx_array_iterator_ptr_(CxArray *array) {
+    return cxIteratorPtr(array->data, array->size);
 }
 
+void cx_array_free_(const CxAllocator *allocator, CxArray *array) {
+    cxFree(allocator, array->data);
+    array->data = NULL;
+    array->size = array->capacity = 0;
+}
+
+
 // implementation that finds ANY index
 static size_t cx_array_binary_search_inf_impl(
         const void *arr,
         size_t size,
         size_t elem_size,
         const void *elem,
-        cx_compare_func cmp_func
+        cx_compare_func2 cmp_func,
+        void *context
 ) {
     // special case: empty array
     if (size == 0) return 0;
@@ -606,7 +383,7 @@
     const char *array = arr;
 
     // check the first array element
-    result = cmp_func(elem, array);
+    result = cmp_func(elem, array, context);
     if (result < 0) {
         return size;
     } else if (result == 0) {
@@ -617,7 +394,7 @@
     if (size == 1) return 0;
 
     // check the last array element
-    result = cmp_func(elem, array + elem_size * (size - 1));
+    result = cmp_func(elem, array + elem_size * (size - 1), context);
     if (result >= 0) {
         return size - 1;
     }
@@ -626,12 +403,12 @@
     // so start the binary search
     size_t left_index = 1;
     size_t right_index = size - 1;
-    size_t pivot_index;
+    size_t pivot_index = 0;
 
     while (left_index <= right_index) {
         pivot_index = left_index + (right_index - left_index) / 2;
         const char *arr_elem = array + pivot_index * elem_size;
-        result = cmp_func(elem, arr_elem);
+        result = cmp_func(elem, arr_elem, context);
         if (result == 0) {
             // found it!
             return pivot_index;
@@ -648,6 +425,74 @@
     return result < 0 ? (pivot_index - 1) : pivot_index;
 }
 
+size_t cx_array_binary_search_inf_c(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func2 cmp_func,
+        void *context
+) {
+    size_t index = cx_array_binary_search_inf_impl(
+        arr, size, elem_size, elem, cmp_func, context);
+    // in case of equality, report the largest index
+    const char *e = ((const char *) arr) + (index + 1) * elem_size;
+    while (index + 1 < size && cmp_func(e, elem, context) == 0) {
+        e += elem_size;
+        index++;
+    }
+    return index;
+}
+
+size_t cx_array_binary_search_c(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func2 cmp_func,
+        void *context
+) {
+    size_t index = cx_array_binary_search_inf_c(
+            arr, size, elem_size, elem, cmp_func, context
+    );
+    if (index < size && cmp_func(((const char *) arr) + index * elem_size,
+        elem, context) == 0) {
+        return index;
+    } else {
+        return size;
+    }
+}
+
+size_t cx_array_binary_search_sup_c(
+        const void *arr,
+        size_t size,
+        size_t elem_size,
+        const void *elem,
+        cx_compare_func2 cmp_func,
+        void *context
+) {
+    size_t index = cx_array_binary_search_inf_impl(
+            arr, size, elem_size, elem, cmp_func, context
+    );
+    const char *e = ((const char *) arr) + index * elem_size;
+    if (index == size) {
+        // no infimum means the first element is supremum
+        return 0;
+    } else if (cmp_func(e, elem, context) == 0) {
+        // found an equal element, search the smallest index
+        e -= elem_size; // e now contains the element at index-1
+        while (index > 0 && cmp_func(e, elem, context) == 0) {
+            e -= elem_size;
+            index--;
+        }
+        return index;
+    } else {
+        // we already have the largest index of the infimum (by design)
+        // the next element is the supremum (or there is no supremum)
+        return index + 1;
+    }
+}
+
 size_t cx_array_binary_search_inf(
         const void *arr,
         size_t size,
@@ -655,15 +500,8 @@
         const void *elem,
         cx_compare_func cmp_func
 ) {
-    size_t index = cx_array_binary_search_inf_impl(
-        arr, size, elem_size, elem, cmp_func);
-    // in case of equality, report the largest index
-    const char *e = ((const char *) arr) + (index + 1) * elem_size;
-    while (index + 1 < size && cmp_func(e, elem) == 0) {
-        e += elem_size;
-        index++;
-    }
-    return index;
+    cx_compare_func_wrapper wrapper = {cmp_func};
+    return cx_array_binary_search_inf_c(arr, size, elem_size, elem, cx_acmp_wrap, &wrapper);
 }
 
 size_t cx_array_binary_search(
@@ -673,15 +511,8 @@
         const void *elem,
         cx_compare_func cmp_func
 ) {
-    size_t index = cx_array_binary_search_inf(
-            arr, size, elem_size, elem, cmp_func
-    );
-    if (index < size &&
-            cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
-        return index;
-    } else {
-        return size;
-    }
+    cx_compare_func_wrapper wrapper = {cmp_func};
+    return cx_array_binary_search_c(arr, size, elem_size, elem, cx_acmp_wrap, &wrapper);
 }
 
 size_t cx_array_binary_search_sup(
@@ -691,26 +522,8 @@
         const void *elem,
         cx_compare_func cmp_func
 ) {
-    size_t index = cx_array_binary_search_inf_impl(
-            arr, size, elem_size, elem, cmp_func
-    );
-    const char *e = ((const char *) arr) + index * elem_size;
-    if (index == size) {
-        // no infimum means the first element is supremum
-        return 0;
-    } else if (cmp_func(e, elem) == 0) {
-        // found an equal element, search the smallest index
-        e -= elem_size; // e now contains the element at index-1
-        while (index > 0 && cmp_func(e, elem) == 0) {
-            e -= elem_size;
-            index--;
-        }
-        return index;
-    } else {
-        // we already have the largest index of the infimum (by design)
-        // the next element is the supremum (or there is no supremum)
-        return index + 1;
-    }
+    cx_compare_func_wrapper wrapper = {cmp_func};
+    return cx_array_binary_search_sup_c(arr, size, elem_size, elem, cx_acmp_wrap, &wrapper);
 }
 
 #ifndef CX_ARRAY_SWAP_SBO_SIZE
@@ -763,7 +576,6 @@
     struct cx_list_s base;
     void *data;
     size_t capacity;
-    CxArrayReallocator reallocator;
 } cx_array_list;
 
 static void cx_arl_destructor(struct cx_list_s *list) {
@@ -794,42 +606,47 @@
         const void *array,
         size_t n
 ) {
-    // out of bounds and special case check
-    if (index > list->collection.size || n == 0) return 0;
-
-    // get a correctly typed pointer to the list
     cx_array_list *arl = (cx_array_list *) list;
-
-    // guarantee enough capacity
-    if (arl->capacity < list->collection.size + n) {
-        const size_t new_capacity = cx_array_grow_capacity(arl->capacity,list->collection.size + n);
-        if (cxReallocateArray(
-                list->collection.allocator,
-                &arl->data, new_capacity,
-                list->collection.elem_size)
-        ) {
-            return 0; // LCOV_EXCL_LINE
-        }
-        arl->capacity = new_capacity;
+    CxArray wrap = {
+        arl->data, list->collection.size, arl->capacity
+    };
+    if (cx_array_insert_(list->collection.allocator, &wrap,
+            list->collection.elem_size, index, array, n)) {
+        return 0;
     }
+    arl->data = wrap.data;
+    arl->capacity = wrap.capacity;
+    list->collection.size = wrap.size;
+    return n;
+}
 
-    // determine insert position
-    char *arl_data = arl->data;
-    char *insert_pos = arl_data + index * list->collection.elem_size;
+static size_t cx_arl_insert_sorted_impl(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n,
+        bool allow_duplicates
+) {
+    cx_array_list *arl = (cx_array_list *) list;
+    CxArray wrap = {
+        arl->data, list->collection.size, arl->capacity
+    };
 
-    // do we need to move some elements?
-    if (index < list->collection.size) {
-        size_t elems_to_move = list->collection.size - index;
-        char *target = insert_pos + n * list->collection.elem_size;
-        memmove(target, insert_pos, elems_to_move * list->collection.elem_size);
+    if (cx_array_insert_sorted_s_(
+            list->collection.allocator,
+            &wrap,
+            list->collection.elem_size,
+            sorted_data,
+            n,
+            allow_duplicates,
+            cx_list_compare_wrapper,
+            list
+    )) {
+        // array list implementation is "all or nothing"
+        return 0;  // LCOV_EXCL_LINE
     }
-
-    // place the new elements, if any
-    if (array != NULL) {
-        memcpy(insert_pos, array, n * list->collection.elem_size);
-    }
-    list->collection.size += n;
-
+    arl->data = wrap.data;
+    arl->capacity = wrap.capacity;
+    list->collection.size = wrap.size;
     return n;
 }
 
@@ -838,24 +655,7 @@
         const void *sorted_data,
         size_t n
 ) {
-    // get a correctly typed pointer to the list
-    cx_array_list *arl = (cx_array_list *) list;
-
-    if (cx_array_insert_sorted(
-            &arl->data,
-            &list->collection.size,
-            &arl->capacity,
-            list->collection.cmpfunc,
-            sorted_data,
-            list->collection.elem_size,
-            n,
-            &arl->reallocator
-    )) {
-        // array list implementation is "all or nothing"
-        return 0;  // LCOV_EXCL_LINE
-    } else {
-        return n;
-    }
+    return cx_arl_insert_sorted_impl(list, sorted_data, n, true);
 }
 
 static size_t cx_arl_insert_unique(
@@ -863,24 +663,7 @@
         const void *sorted_data,
         size_t n
 ) {
-    // get a correctly typed pointer to the list
-    cx_array_list *arl = (cx_array_list *) list;
-
-    if (cx_array_insert_unique(
-            &arl->data,
-            &list->collection.size,
-            &arl->capacity,
-            list->collection.cmpfunc,
-            sorted_data,
-            list->collection.elem_size,
-            n,
-            &arl->reallocator
-    )) {
-        // array list implementation is "all or nothing"
-        return 0;  // LCOV_EXCL_LINE
-    } else {
-        return n;
-    }
+    return cx_arl_insert_sorted_impl(list, sorted_data, n, false);
 }
 
 static void *cx_arl_insert_element(
@@ -959,24 +742,21 @@
         );
     }
 
+    // calculate how many elements would need to be moved
+    size_t remaining = list->collection.size - index - remove;
+
     // short-circuit removal of last elements
-    if (index + remove == list->collection.size) {
+    if (remaining == 0) {
         list->collection.size -= remove;
         return remove;
     }
 
     // just move the elements to the left
-    cx_array_copy(
-            &arl->data,
-            &list->collection.size,
-            &arl->capacity,
-            0,
-            index,
-            ((char *) arl->data) + (index + remove) * list->collection.elem_size,
-            list->collection.elem_size,
-            list->collection.size - index - remove,
-            &arl->reallocator
-    );
+    char *first_remaining = arl->data;
+    first_remaining += (index + remove) * list->collection.elem_size;
+    char *dst_move = arl->data;
+    dst_move += index * list->collection.elem_size;
+    memmove(dst_move, first_remaining, remaining * list->collection.elem_size);
 
     // decrease the size
     list->collection.size -= remove;
@@ -1037,18 +817,18 @@
         bool remove
 ) {
     assert(list != NULL);
-    assert(list->collection.cmpfunc != NULL);
     if (list->collection.size == 0) return 0;
     char *cur = ((const cx_array_list *) list)->data;
 
     // optimize with binary search, when sorted
     if (list->collection.sorted) {
-        size_t i = cx_array_binary_search(
+        size_t i = cx_array_binary_search_c(
             cur,
             list->collection.size,
             list->collection.elem_size,
             elem,
-            list->collection.cmpfunc
+            cx_list_compare_wrapper,
+            list
         );
         if (remove && i < list->collection.size) {
             cx_arl_remove(list, i, 1, NULL);
@@ -1058,7 +838,7 @@
 
     // fallback: linear search
     for (size_t i = 0; i < list->collection.size; i++) {
-        if (0 == list->collection.cmpfunc(elem, cur)) {
+        if (0 == cx_list_compare_wrapper(elem, cur, list)) {
             if (remove) {
                 cx_arl_remove(list, i, 1, NULL);
             }
@@ -1069,12 +849,19 @@
     return list->collection.size;
 }
 
+// TODO: remove this hack once we have a solution for qsort() / qsort_s()
+static _Thread_local CxList *cx_hack_for_qsort_list;
+static int cx_hack_cmp_for_qsort(const void *l, const void *r) {
+    return cx_list_compare_wrapper(l, r, cx_hack_for_qsort_list);
+}
+
 static void cx_arl_sort(struct cx_list_s *list) {
-    assert(list->collection.cmpfunc != NULL);
+    // TODO: think about if we can somehow use qsort()_s
+    cx_hack_for_qsort_list = list;
     qsort(((cx_array_list *) list)->data,
           list->collection.size,
           list->collection.elem_size,
-          list->collection.cmpfunc
+          cx_hack_cmp_for_qsort
     );
 }
 
@@ -1082,12 +869,11 @@
         const struct cx_list_s *list,
         const struct cx_list_s *other
 ) {
-    assert(list->collection.cmpfunc != NULL);
     if (list->collection.size == other->collection.size) {
         const char *left = ((const cx_array_list *) list)->data;
         const char *right = ((const cx_array_list *) other)->data;
         for (size_t i = 0; i < list->collection.size; i++) {
-            int d = list->collection.cmpfunc(left, right);
+            int d = cx_list_compare_wrapper(left, right, (void*)list);
             if (d != 0) {
                 return d;
             }
@@ -1200,7 +986,6 @@
 
 CxList *cxArrayListCreate(
         const CxAllocator *allocator,
-        cx_compare_func comparator,
         size_t elem_size,
         size_t initial_capacity
 ) {
@@ -1211,7 +996,7 @@
     cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
     if (list == NULL) return NULL;
     cx_list_init((CxList*)list, &cx_array_list_class,
-        allocator, comparator, elem_size);
+        allocator, elem_size);
     list->capacity = initial_capacity;
 
     // allocate the array after the real elem_size is known
@@ -1222,8 +1007,5 @@
         return NULL;
     } // LCOV_EXCL_STOP
 
-    // configure the reallocator
-    list->reallocator = cx_array_reallocator(allocator, NULL);
-
     return (CxList *) list;
 }
--- a/ucx/buffer.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/buffer.c	Thu Dec 18 17:50:15 2025 +0100
@@ -45,11 +45,11 @@
 
 int cxBufferInit(
         CxBuffer *buffer,
+        const CxAllocator *allocator,
         void *space,
         size_t capacity,
-        const CxAllocator *allocator,
         int flags
-) {
+        ) {
     if (allocator == NULL) {
         allocator = cxDefaultAllocator;
     }
@@ -82,17 +82,17 @@
 }
 
 CxBuffer *cxBufferCreate(
+        const CxAllocator *allocator,
         void *space,
         size_t capacity,
-        const CxAllocator *allocator,
         int flags
-) {
+        ) {
     if (allocator == NULL) {
         allocator = cxDefaultAllocator;
     }
     CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
     if (buf == NULL) return NULL; // LCOV_EXCL_LINE
-    if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
+    if (0 == cxBufferInit(buf, allocator, space, capacity, flags)) {
         return buf;
     } else {
         // LCOV_EXCL_START
--- a/ucx/compare.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/compare.c	Thu Dec 18 17:50:15 2025 +0100
@@ -29,6 +29,7 @@
 #include "cx/compare.h"
 
 #include <math.h>
+#include <string.h>
 
 int cx_vcmp_int(int a, int b) {
     if (a == b) {
@@ -289,3 +290,21 @@
         return p1 < p2 ? -1 : 1;
     }
 }
+
+int cx_acmp_memcmp(
+        const void *ptr1,
+        const void *ptr2,
+        void *size
+) {
+    size_t n = *(size_t*)size;
+    return memcmp(ptr1, ptr2, n);
+}
+
+int cx_acmp_wrap(
+        const void *ptr1,
+        const void *ptr2,
+        void *w
+) {
+    cx_compare_func_wrapper *wrapper = w;
+    return wrapper->cmp(ptr1, ptr2);
+}
--- a/ucx/cx/allocator.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/allocator.h	Thu Dec 18 17:50:15 2025 +0100
@@ -142,7 +142,7 @@
  * @return either the specified @p target, a pointer to the allocated memory,
  * or @c NULL, if any error occurred
  */
-typedef void*(cx_clone_func)(void *target, const void *source,
+typedef void*(*cx_clone_func)(void *target, const void *source,
                              const CxAllocator *allocator, void *data);
 
 /**
@@ -157,13 +157,9 @@
 CX_EXPORT unsigned long cx_system_page_size(void);
 
 /**
- * Reallocate a previously allocated block and changes the pointer in-place,
- * if necessary.
+ * Reallocate a previously allocated block.
  *
- * @note This will use stdlib reallocate and @em not the cxDefaultAllocator.
- *
- * @par Error handling
- * @c errno will be set by realloc() on failure.
+ * Internal function - do not use.
  *
  * @param mem pointer to the pointer to allocated block
  * @param n the new size in bytes
@@ -175,16 +171,9 @@
 CX_EXPORT int cx_reallocate_(void **mem, size_t n);
 
 /**
- * Reallocate a previously allocated block and changes the pointer in-place,
- * if necessary.
- *
- * The size is calculated by multiplying @p nemb and @p size.
+ * Reallocate a previously allocated block.
  *
- * @note This will use stdlib reallocate and @em not the cxDefaultAllocator.
- *
- * @par Error handling
- * @c errno will be set by realloc() on failure or when the multiplication of
- * @p nmemb and @p size overflows.
+ * Internal function - do not use.
  *
  * @param mem pointer to the pointer to allocated block
  * @param nmemb the number of elements
@@ -272,6 +261,8 @@
  *
  * @note Re-allocating a block allocated by a different allocator is undefined.
  *
+ * @attention This function is bug-prone. Consider using cxReallocate().
+ *
  * @param allocator the allocator
  * @param mem pointer to the previously allocated block
  * @param n the new size in bytes
@@ -282,8 +273,8 @@
 CX_EXPORT void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n);
 
 /**
- * Reallocate the previously allocated block in @p mem, making the new block
- * @p n bytes long.
+ * Reallocate the previously allocated block in @p mem.
+ *
  * This function may return the same pointer passed to it if moving
  * the memory was not necessary.
  *
@@ -293,6 +284,8 @@
  *
  * @note Re-allocating a block allocated by a different allocator is undefined.
  *
+ * @attention This function is bug-prone. Consider using cxReallocateArray().
+ *
  * @param allocator the allocator
  * @param mem pointer to the previously allocated block
  * @param nmemb the number of elements
@@ -305,14 +298,9 @@
         void *mem, size_t nmemb, size_t size);
 
 /**
- * Reallocate a previously allocated block and changes the pointer in-place,
- * if necessary.
- * This function acts like cxRealloc() using the pointer pointed to by @p mem.
+ * Reallocate a previously allocated block.
  *
- * @note Re-allocating a block allocated by a different allocator is undefined.
- *
- * @par Error handling
- * @c errno will be set if the underlying realloc function does so.
+ * Internal function - do not use.
  *
  * @param allocator the allocator
  * @param mem pointer to the pointer to allocated block
@@ -343,16 +331,9 @@
     cxReallocate_(allocator, (void**)(mem), n)
 
 /**
- * Reallocate a previously allocated block and changes the pointer in-place,
- * if necessary.
- * This function acts like cxReallocArray() using the pointer pointed to
- * by @p mem.
+ * Reallocate a previously allocated block.
  *
- * @note Re-allocating a block allocated by a different allocator is undefined.
- *
- * @par Error handling
- * @c errno will be set, if the underlying realloc function does so or the
- * multiplication of @p nmemb and @p size overflows.
+ * Internal function - do not use.
  *
  * @param allocator the allocator
  * @param mem pointer to the pointer to allocated block
@@ -388,7 +369,7 @@
         cxReallocateArray_(allocator, (void**) (mem), nmemb, size)
 
 /**
- * Allocate @p nmemb elements of @p n bytes each, all initialized to zero.
+ * Allocate @p nmemb elements of @p size bytes each, all initialized to zero.
  *
  * @param allocator the allocator
  * @param nmemb the number of elements
@@ -411,35 +392,123 @@
 CX_EXPORT void *cxZalloc(const CxAllocator *allocator, size_t n);
 
 /**
+ * Allocate @p n bytes of memory.
+ *
  * Convenience macro that invokes cxMalloc() with the cxDefaultAllocator.
+ *
+ * @param n (@c size_t) the number of bytes
+ * @return (@c void*) a pointer to the allocated memory
  */
-#define cxMallocDefault(...) cxMalloc(cxDefaultAllocator, __VA_ARGS__)
+#define cxMallocDefault(n) cxMalloc(cxDefaultAllocator, n)
+
 /**
+ * Allocate @p n bytes of memory and sets every byte to zero.
+ *
  * Convenience macro that invokes cxZalloc() with the cxDefaultAllocator.
+ *
+ * @param n (@c size_t) the number of bytes
+ * @return (@c void*) a pointer to the allocated memory
  */
-#define cxZallocDefault(...) cxZalloc(cxDefaultAllocator, __VA_ARGS__)
+#define cxZallocDefault(n) cxZalloc(cxDefaultAllocator, n)
+
 /**
+ * Allocate @p nmemb elements of @p size bytes each, all initialized to zero.
+ *
  * Convenience macro that invokes cxCalloc() with the cxDefaultAllocator.
+ *
+ * @param nmemb (@c size_t) the number of elements
+ * @param size (@c size_t) the size of each element in bytes
+ * @return (@c void*) a pointer to the allocated memory
  */
-#define cxCallocDefault(...) cxCalloc(cxDefaultAllocator, __VA_ARGS__)
+#define cxCallocDefault(nmemb, size) cxCalloc(cxDefaultAllocator, nmemb, size)
+
 /**
+ * Reallocate the previously allocated block in @p mem.
+ *
+ * This function may return the same pointer passed to it if moving
+ * the memory was not necessary.
+ *
  * Convenience macro that invokes cxRealloc() with the cxDefaultAllocator.
+ *
+ * @attention This function is bug-prone. Consider using cxReallocateDefault().
+ *
+ * @param mem (@c void*) pointer to the previously allocated block
+ * @param n (@c size_t) the new size in bytes
+ * @return (@c void*) a pointer to the reallocated memory
  */
-#define cxReallocDefault(...) cxRealloc(cxDefaultAllocator, __VA_ARGS__)
+#define cxReallocDefault(mem, n) cxRealloc(cxDefaultAllocator, mem, n)
+
 /**
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by @p mem.
+ *
  * Convenience macro that invokes cxReallocate() with the cxDefaultAllocator.
- */
-#define cxReallocateDefault(...) cxReallocate(cxDefaultAllocator, __VA_ARGS__)
-/**
- * Convenience macro that invokes cxReallocateArray() with the cxDefaultAllocator.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set if the underlying realloc function does so.
+ *
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param n (@c size_t) the new size in bytes
+ * @retval zero success
+ * @retval non-zero failure
  */
-#define cxReallocateArrayDefault(...) cxReallocateArray(cxDefaultAllocator, __VA_ARGS__)
+#define cxReallocateDefault(mem, n) cxReallocate(cxDefaultAllocator, mem, n)
+
 /**
- * Convenience macro that invokes cxReallocArray() with the cxDefaultAllocator.
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxReallocArray() using the pointer pointed to
+ * by @p mem.
+ *
+ * Convenience macro that invokes cxReallocateArray() with the cxDefaultAllocator.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so or the
+ * multiplication of @p nmemb and @p size overflows.
+ *
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param nmemb (@c size_t) the number of elements
+ * @param size (@c size_t) the size of each element
+ * @retval zero success
+ * @retval non-zero failure
  */
-#define cxReallocArrayDefault(...) cxReallocArray(cxDefaultAllocator, __VA_ARGS__)
+#define cxReallocateArrayDefault(mem, nmemb, size) \
+        cxReallocateArray(cxDefaultAllocator, mem, nmemb, size)
+
 /**
+ * Reallocate the previously allocated block in @p mem.
+ *
+ * Convenience macro that invokes cxReallocArray() with the cxDefaultAllocator.
+ *
+ * This function may return the same pointer passed to it if moving
+ * the memory was not necessary.
+ *
+ * The size is calculated by multiplying @p nemb and @p size.
+ * If that multiplication overflows, this function returns @c NULL, and @c errno
+ * will be set.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @attention This function is bug-prone. Consider using cxReallocateArrayDefault().
+ *
+ * @param mem (@c void*) pointer to the previously allocated block
+ * @param nmemb (@c size_t) the number of elements
+ * @param size (@c size_t) the size of each element
+ * @return (@c void*) a pointer to the reallocated memory
+ */
+#define cxReallocArrayDefault(mem, nmemb, size) cxReallocArray(cxDefaultAllocator, mem, nmemb, size)
+
+/**
+ * Free a block of memory.
+ *
  * Convenience function that invokes cxFree() with the cxDefaultAllocator.
+ *
+ * @param mem the memory to deallocate
  */
 CX_EXPORT void cxFreeDefault(void *mem);
 
--- a/ucx/cx/array_list.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/array_list.h	Thu Dec 18 17:50:15 2025 +0100
@@ -50,624 +50,581 @@
 CX_EXPORT extern const unsigned cx_array_swap_sbo_size;
 
 /**
- * Declares variables for an array that can be used with the convenience macros.
- *
- * @par Examples
- * @code
- * // integer array with at most 255 elements
- * CX_ARRAY_DECLARE_SIZED(int, myarray, uint8_t)
- *
- * // array of MyObject* pointers where size and capacity are stored as unsigned int
- * CX_ARRAY_DECLARE_SIZED(MyObject*, objects, unsigned int)
- *
- * // initializing code
- * cx_array_initialize(myarray, 16); // reserve space for 16
- * cx_array_initialize(objects, 100); // reserve space for 100
- * @endcode
+ * Declares a typed array with size and capacity.
  *
- * @param type the type of the data
+ * @param type the type of the elements
  * @param name the name of the array
- * @param size_type the type of the size (should be uint8_t, uint16_t, uint32_t, or size_t)
+ */
+#define CX_ARRAY(type, name) \
+    struct { \
+        type *data; \
+        size_t size; \
+        size_t capacity; \
+    } name
+
+/**
+ * Internal structure for arrays.
  *
- * @see cx_array_initialize()
- * @see cx_array_simple_add()
- * @see cx_array_simple_copy()
- * @see cx_array_simple_add_sorted()
- * @see cx_array_simple_insert_sorted()
+ * A generalization of array structures declared with CX_ARRAY().
  */
-#define CX_ARRAY_DECLARE_SIZED(type, name, size_type) \
-    type * name; \
-    /** Array size. */ size_type name##_size; \
-    /** Array capacity. */ size_type name##_capacity
+typedef struct cx_array_s {
+    /** The array data. */
+    void *data;
+    /** The number of elements. */
+    size_t size;
+    /** The maximum number of elements. */
+    size_t capacity;
+} CxArray;
 
 /**
- * Declares variables for an array that can be used with the convenience macros.
- *
- * The size and capacity variables will have type @c size_t.
- * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type.
+ * Initializes an array by allocating memory.
  *
- * @par Examples
- * @code
- * // int array
- * CX_ARRAY_DECLARE(int, myarray)
+ * Internal function - do not use manually.
  *
- * // initializing code
- * cx_array_initialize(myarray, 32); // reserve space for 32
- * @endcode
- *
- * @param type the type of the data
- * @param name the name of the array
- *
- * @see cx_array_initialize()
- * @see cx_array_simple_add()
- * @see cx_array_simple_copy()
- * @see cx_array_simple_add_sorted()
- * @see cx_array_simple_insert_sorted()
+ * @param allocator the allocator for the array
+ * @param array a pointer to the array structure
+ * @param elem_size size of one element
+ * @param capacity the initial maximum number of elements
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
  */
-#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t)
+cx_attr_nonnull
+CX_EXPORT int cx_array_init_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity);
 
 /**
- * Initializes an array with the given capacity.
+ * Initializes an array by allocating memory.
  *
- * The type of the capacity depends on the type used during declaration.
+ * The size is set to zero.
+ *
+ * @attention If the array was already initialized, this will leak memory.
+ * Use cx_array_reserve() to change the capacity of an initialized array.
  *
- * @par Examples
- * @code
- * CX_ARRAY_DECLARE_SIZED(int, arr1, uint8_t)
- * CX_ARRAY_DECLARE(int, arr2) // size and capacity are implicitly size_t
+ * @param allocator (@c CxAllocator*) the allocator for the array
+ * @param array the name of the array
+ * @param capacity (@c size_t) the initial maximum number of elements
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
+ */
+#define cx_array_init_a(allocator, array, capacity) cx_array_init_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
+
+/**
+ * Initializes an array by allocating memory.
  *
- * // initializing code
- * cx_array_initialize(arr1, 500); // error: maximum for uint8_t is 255
- * cx_array_initialize(arr2, 500); // OK
- * @endcode
+ * The size is set to zero.
  *
- *
- * The memory for the array is allocated with the cxDefaultAllocator.
+ * @attention If the array was already initialized, this will leak memory.
  *
  * @param array the name of the array
- * @param capacity the initial capacity
- * @see cx_array_initialize_a()
- * @see CX_ARRAY_DECLARE_SIZED()
- * @see CX_ARRAY_DECLARE()
+ * @param capacity (@c size_t) the initial maximum number of elements
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
  */
-#define cx_array_initialize(array, capacity) \
-        array##_capacity = capacity; \
-        array##_size = 0; \
-        array = cxMallocDefault(sizeof(array[0]) * capacity)
+#define cx_array_init(array, capacity) cx_array_init_a(cxDefaultAllocator, array, capacity)
 
 /**
- * Initializes an array with the given capacity using the specified allocator.
+ * Initializes an array with fixed size memory.
+ *
+ * Internal function - do not use manually.
  *
- * @par Example
- * @code
- * CX_ARRAY_DECLARE(int, myarray)
+ * @param array a pointer to the array structure
+ * @param data the fixed size array
+ * @param capacity the capacity of the fixed size array
+ * @param size the number of initialized elements in the fixed size array
+ */
+cx_attr_nonnull
+CX_EXPORT void cx_array_init_fixed_(CxArray *array, const void *data, size_t capacity, size_t size);
+
+/**
+ * Initializes an array with fixed size memory.
  *
+ * This is useful, for example, when you want to work with memory on the stack
+ * and only want to move to the heap when the stack memory is not enough.
  *
- * const CxAllocator *al = // ...
- * cx_array_initialize_a(al, myarray, 128);
- * // ...
- * cxFree(al, myarray); // remember to free with the same allocator
- * @endcode
+ * With the @p num_initialized argument you can specify how many elements in the
+ * fixed size array are already correctly initialized, which determines the
+ * initial size of the array.
+ *
+ * The capacity is determined automatically by the compiler.
+ *
+ * @attention When you add elements to an array that was initialized with fixed
+ * size memory, you MUST check the capacity before adding the element and invoke
+ * cx_array_copy_to_new() when you intend to exceed the capacity.
  *
- * @param allocator (@c CxAllocator*) the allocator
- * @param array the name of the array
- * @param capacity the initial capacity
- * @see cx_array_initialize()
- * @see CX_ARRAY_DECLARE_SIZED()
- * @see CX_ARRAY_DECLARE()
+ * @attention When you pass a pointer to an array that does not have a fixed
+ * size, the behavior is unspecified.
+ *
+ * @param array the name of the array to initialize
+ * @param fixed_size_array (@c void*) the fixed size array
+ * @param num_initialized (@c size_t) the number of already initialized elements in the fixed size array
+ * @see cx_array_copy_to_new()
  */
-#define cx_array_initialize_a(allocator, array, capacity) \
-        array##_capacity = capacity; \
-        array##_size = 0; \
-        array = cxMalloc(allocator, sizeof(array[0]) * capacity)
+#define cx_array_init_fixed(array, fixed_size_array, num_initialized) \
+        cx_array_init_fixed_((CxArray*)&(array), fixed_size_array, cx_nmemb(fixed_size_array), num_initialized)
 
 /**
- * Defines a reallocation mechanism for arrays.
- * You can create your own, use cx_array_reallocator(), or
- * use the #cx_array_default_reallocator.
+ * Changes the capacity of an array.
+ *
+ * Internal function - do not use.
+ *
+ * @param allocator the allocator
+ * @param array a pointer to the array structure
+ * @param elem_size the size of one element
+ * @param capacity the new capacity
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
  */
-struct cx_array_reallocator_s {
-    /**
-     * Reallocates space for the given array.
-     *
-     * Implementations are not required to free the original array.
-     * This allows reallocation of static or stack memory by allocating heap memory
-     * and copying the array contents; namely when @c stack_ptr in this struct
-     * is not @c NULL and @p array equals @c stack_ptr.
-     *
-     * @param array the array to reallocate
-     * @param old_capacity the old number of elements
-     * @param new_capacity the new number of elements
-     * @param elem_size the size of each element
-     * @param alloc a reference to this allocator
-     * @return a pointer to the reallocated memory or @c NULL on failure
-     */
-    void *(*realloc)( void *array, size_t old_capacity, size_t new_capacity,
-            size_t elem_size, struct cx_array_reallocator_s *alloc);
-
-    /**
-     * The allocator that shall be used for the reallocations.
-     */
-    const CxAllocator *allocator;
-    /**
-     * Optional pointer to stack memory
-     * if the array is originally located on the stack.
-     */
-    const void *stack_ptr;
-};
+cx_attr_nonnull
+CX_EXPORT int cx_array_reserve_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity);
 
 /**
- * Typedef for the array reallocator struct.
+ * Changes the capacity of an array.
+ *
+ * If required, the size is reduced to fit into the new capacity.
+ *
+ * @param allocator (@c CxAllocator*) the allocator for the array
+ * @param array the name of the array
+ * @param capacity (@c size_t) the new maximum number of elements
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
  */
-typedef struct cx_array_reallocator_s CxArrayReallocator;
-
-/**
- * A default array reallocator that is based on the cxDefaultAllocator.
- */
-CX_EXPORT extern CxArrayReallocator *cx_array_default_reallocator;
+#define cx_array_reserve_a(allocator, array, capacity) \
+        cx_array_reserve_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
 
 /**
- * Creates a new array reallocator.
- *
- * When @p allocator is @c NULL, the cxDefaultAllocator will be used.
+ * Changes the capacity of an array.
  *
- * When @p stack_ptr is not @c NULL, the reallocator is supposed to be used
- * @em only for the specific array initially located at @p stack_ptr.
- * When reallocation is needed, the reallocator checks if the array is
- * still located at @p stack_ptr and copies the contents to the heap.
+ * If required, the size is reduced to fit into the new capacity.
  *
- * @note Invoking this function with both arguments being @c NULL will return a
- * reallocator that behaves like #cx_array_default_reallocator.
+ * @param array the name of the array
+ * @param capacity (@c size_t) the new maximum number of elements
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
+ */
+#define cx_array_reserve(array, capacity) \
+        cx_array_reserve_a(cxDefaultAllocator, array, capacity)
+
+/**
+ * Copies the array to a new memory region.
  *
- * @param allocator the allocator this reallocator shall be based on
- * @param stack_ptr the address of the array when the array is initially located
- * on the stack or shall not reallocate in place
- * @return an array reallocator
+ * Internal function - do not use.
+ *
+ * @param allocator the allocator for new new memory
+ * @param array a pointer to the array structure
+ * @param elem_size the size of one element
+ * @param capacity the new capacity
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
  */
-CX_EXPORT CxArrayReallocator cx_array_reallocator(
-        const struct cx_allocator_s *allocator, const void *stack_ptr);
+cx_attr_nonnull
+CX_EXPORT int cx_array_copy_to_new_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity);
 
 /**
- * Reserves memory for additional elements.
- *
- * This function checks if the @p capacity of the array is sufficient to hold
- * at least @p size plus @p elem_count elements. If not, a reallocation is
- * performed with the specified @p reallocator.
- * You can create your own reallocator by hand, use #cx_array_default_reallocator,
- * or use the convenience function cx_array_reallocator() to create a custom reallocator.
+ * Copies the array to a new memory region.
  *
- * This function can be useful to replace subsequent calls to cx_array_copy()
- * with one single cx_array_reserve() and then - after guaranteeing a
- * sufficient capacity - use simple memmove() or memcpy().
+ * This is useful when you have initialized the array with a fixed size memory
+ * using cx_array_init_fixed(), and now you want to increase the capacity.
  *
- * The @p width in bytes refers to the size and capacity.
- * Both must have the same width.
- * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit
- * architecture. If set to zero, the native word width is used.
+ * @attention When the original memory does not belong to stack memory, and
+ * you do not have another reference to this memory, it will leak.
  *
- * @note This function will reserve the minimum required capacity to hold
- * the additional elements and does not perform an overallocation.
+ * @param allocator (@c CxAllocator*) the allocator for the new memory
+ * @param array the name of the array
+ * @param capacity (@c size_t) the new maximum number of elements
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
+ * @see cx_array_init_fixed()
+ */
+#define cx_array_copy_to_new_a(allocator, array, capacity) \
+        cx_array_copy_to_new_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
+
+/**
+ * Copies the array to a new memory region.
  *
- * @param array a pointer to the target array
- * @param size a pointer to the size of the array
- * @param capacity a pointer to the capacity of the array
- * @param width the width in bytes for the @p size and @p capacity or zero for default
- * @param elem_size the size of one element
- * @param elem_count the number of expected additional elements
- * @param reallocator the array reallocator to use
- * (@c NULL defaults to #cx_array_default_reallocator)
- * @retval zero success
- * @retval non-zero failure
- * @see cx_array_reallocator()
+ * This is useful when you have initialized the array with a fixed size memory
+ * using cx_array_init_fixed(), and now you want to increase the capacity.
+ *
+ * @attention When the original memory does not belong to stack memory, and
+ * you do not have another reference to this memory, it will leak.
+ *
+ * @param array the name of the array
+ * @param capacity (@c size_t) the new maximum number of elements
+ * @retval zero allocation was successful
+ * @retval non-zero allocation failed
+ * @see cx_array_init_fixed()
  */
-cx_attr_nonnull_arg(1, 2, 3)
-CX_EXPORT int cx_array_reserve(void **array, void *size, void *capacity,
-        unsigned width, size_t elem_size, size_t elem_count,
-        CxArrayReallocator *reallocator);
+#define cx_array_copy_to_new(array, capacity) \
+        cx_array_copy_to_new_a(cxDefaultAllocator, array, capacity)
 
 /**
- * Copies elements from one array to another.
- *
- * The elements are copied to the @p target array at the specified @p index,
- * overwriting possible elements. The @p index does not need to be in range of
- * the current array @p size. If the new index plus the number of elements added
- * extends the array's size, the remaining @p capacity is used.
+ * Inserts data into an array.
  *
- * If the @p capacity is also insufficient to hold the new data, a reallocation
- * attempt is made with the specified @p reallocator.
- * You can create your own reallocator by hand, use #cx_array_default_reallocator,
- * or use the convenience function cx_array_reallocator() to create a custom reallocator.
- *
- * The @p width in bytes refers to the size and capacity.
- * Both must have the same width.
- * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit
- * architecture. If set to zero, the native word width is used.
- *
- * @note When this function does reallocate the array, it may allocate more
- * space than required to avoid further allocations in the near future.
+ * Internal function - do not use.
  *
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the capacity of the target array
- * @param width the width in bytes for the @p size and @p capacity or zero for default
- * @param index the index where the copied elements shall be placed
- * @param src the source array
+ * @param allocator the allocator to use for a possible reallocation
+ * @param array a pointer to the array structure
  * @param elem_size the size of one element
- * @param elem_count the number of elements to copy
- * @param reallocator the array reallocator to use
- * (@c NULL defaults to #cx_array_default_reallocator)
+ * @param index the index where to insert the @p other data
+ * @param other a pointer to an array of data that shall be inserted
+ * @param n the number of elements that shall be inserted
  * @retval zero success
- * @retval non-zero failure
- * @see cx_array_reallocator()
- * @see cx_array_reserve()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-cx_attr_nonnull_arg(1, 2, 3, 6)
-CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width,
-        size_t index, const void *src, size_t elem_size, size_t elem_count,
-        CxArrayReallocator *reallocator);
+cx_attr_nonnull_arg(1, 2)
+CX_EXPORT int cx_array_insert_(const CxAllocator *allocator, CxArray *array,
+        size_t elem_size, size_t index, const void *other, size_t n);
 
 /**
- * Convenience macro that uses cx_array_copy() with a default layout and
- * the specified reallocator.
+ * Appends an element to an array.
+ *
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
  *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param index (@c size_t) the index where the copied elements shall be placed
- * @param src (@c void*) the source array
- * @param count (@c size_t) the number of elements to copy
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the element shall be added
+ * @param element (@c void*) a pointer to the element that shall be added
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_copy()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_copy_a(reallocator, array, index, src, count) \
-    cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \
-        sizeof(array##_size), index, src, sizeof((array)[0]), count, \
-        reallocator)
+#define cx_array_add_a(allocator, array, element) \
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, element, 1)
 
 /**
- * Convenience macro that uses cx_array_copy() with a default layout and
- * the default reallocator.
+ * Appends an element to an array.
+ *
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
  *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param index (@c size_t) the index where the copied elements shall be placed
- * @param src (@c void*) the source array
- * @param count (@c size_t) the number of elements to copy
+ * @param array the name of the array where the element shall be added
+ * @param element (@c void*) a pointer to the element that shall be added
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_copy_a()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_copy(array, index, src, count) \
-    cx_array_simple_copy_a(NULL, array, index, src, count)
+#define cx_array_add(array, element) \
+        cx_array_add_a(cxDefaultAllocator, array, element)
 
 /**
- * Convenience macro that uses cx_array_reserve() with a default layout and
- * the specified reallocator.
+ * Inserts an element into an array.
+ *
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
  *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param count (@c size_t) the number of expected @em additional elements
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the element shall be inserted
+ * @param index (@c size_t) the index where to insert the @p element
+ * @param element (@c void*) a pointer to the element that shall be inserted
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_reserve()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_reserve_a(reallocator, array, count) \
-    cx_array_reserve((void**)&(array), &(array##_size), &(array##_capacity), \
-        sizeof(array##_size), sizeof((array)[0]), count, \
-        reallocator)
+#define cx_array_insert_a(allocator, array, index, element) \
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), index, element, 1)
 
 /**
- * Convenience macro that uses cx_array_reserve() with a default layout and
- * the default reallocator.
+ * Inserts an element into an array.
+ *
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
  *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param count (@c size_t) the number of expected additional elements
+ * @param array the name of the array where the element shall be inserted
+ * @param index (@c size_t) the index where to insert the @p element
+ * @param element (@c void*) a pointer to the element that shall be inserted
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_reserve_a()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_reserve(array, count) \
-    cx_array_simple_reserve_a(NULL, array, count)
+#define cx_array_insert(array, index, element) \
+        cx_array_insert_a(cxDefaultAllocator, array, index, element)
+
+/**
+ * Inserts data into an array.
+ *
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
+ *
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the elements shall be inserted
+ * @param index (@c size_t) the index where to insert the @p other data
+ * @param other (@c void*) a pointer to an array of data that shall be inserted
+ * @param n (@c size_t) the number of elements that shall be inserted
+ * @retval zero success
+ * @retval non-zero a re-allocation was necessary but failed
+ */
+#define cx_array_insert_array_a(allocator, array, index, other, n) \
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), index, other, n)
 
 /**
- * Adds an element to an array with the possibility of allocating more space.
- *
- * The element @p elem is added to the end of the @p target array which contains
- * @p size elements, already. The @p capacity must point to a variable denoting
- * the current maximum number of elements the array can hold.
+ * Inserts data into an array.
  *
- * If the capacity is insufficient to hold the new element, an attempt to
- * increase the @p capacity is made and the new capacity is written back.
- *
- * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
- * It is important, however, that @p size and @p capacity are pointers to
- * variables of the same type.
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
  *
- * @param target (@c void**) a pointer to the target array
- * @param size (@c SIZE_TYPE*) a pointer to the size of the target array
- * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array
- * @param elem_size (@c size_t) the size of one element
- * @param elem (@c void*) a pointer to the element to add
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @param array the name of the array where the elements shall be inserted
+ * @param index (@c size_t) the index where to insert the @p other data
+ * @param other (@c void*) a pointer to an array of data that shall be inserted
+ * @param n (@c size_t) the number of elements that shall be inserted
  * @retval zero success
- * @retval non-zero failure
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \
-    cx_array_copy((void**)(target), size, capacity, sizeof(*(size)), \
-    *(size), elem, elem_size, 1, reallocator)
+#define cx_array_insert_array(array, index, other, n) \
+        cx_array_insert_array_a(cxDefaultAllocator, array, index, other, n)
+
+/**
+ * Appends data to an array.
+ *
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
+ *
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the elements shall be added
+ * @param other (@c void*) a pointer to an array of data that shall be added
+ * @param n (@c size_t) the number of elements that shall be added
+ * @retval zero success
+ * @retval non-zero a re-allocation was necessary but failed
+ */
+#define cx_array_add_array_a(allocator, array, other, n) \
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, other, n)
 
 /**
- * Convenience macro that uses cx_array_add() with a default layout and
- * the specified reallocator.
+ * Appends data to an array.
+ *
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
  *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
+ * @param array the name of the array where the elements shall be added
+ * @param other (@c void*) a pointer to an array of data that shall be added
+ * @param n (@c size_t) the number of elements that shall be added
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_add_a(reallocator, array, elem) \
-    cx_array_simple_copy_a(reallocator, array, array##_size, &(elem), 1)
+#define cx_array_add_array(array, other, n) \
+        cx_array_add_array_a(cxDefaultAllocator, array, other, n)
 
 /**
- * Convenience macro that uses cx_array_add() with a default layout and
- * the default reallocator.
+ * Inserts sorted data into a sorted array.
  *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_a()
- */
-#define cx_array_simple_add(array, elem) \
-    cx_array_simple_add_a(cx_array_default_reallocator, array, elem)
-
-/**
- * Inserts a sorted array into another sorted array.
- *
- * If either the target or the source array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
+ * Internal function - do not use.
  *
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made.
- * You can create your own reallocator by hand, use #cx_array_default_reallocator,
- * or use the convenience function cx_array_reallocator() to create a custom reallocator.
- *
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the capacity of the target array
- * @param cmp_func the compare function for the elements
- * @param src the source array
+ * @param allocator the allocator to use for a possible reallocation
+ * @param array a pointer to the array structure
  * @param elem_size the size of one element
- * @param elem_count the number of elements to insert
- * @param reallocator the array reallocator to use
- * (@c NULL defaults to #cx_array_default_reallocator)
+ * @param cmp_func
+ * @param sorted_data a pointer to an array of data that shall be inserted
+ * @param n the number of elements that shall be inserted
+ * @param allow_duplicates @c false if duplicates shall be skipped during insertion
  * @retval zero success
- * @retval non-zero failure
+ * @retval non-zero a re-allocation was necessary but failed
  */
-cx_attr_nonnull_arg(1, 2, 3, 5)
-CX_EXPORT int cx_array_insert_sorted(void **target, size_t *size, size_t *capacity,
-        cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count,
-        CxArrayReallocator *reallocator);
+cx_attr_nonnull
+CX_EXPORT int cx_array_insert_sorted_(const CxAllocator *allocator, CxArray *array,
+        size_t elem_size, cx_compare_func cmp_func, const void *sorted_data, size_t n,
+        bool allow_duplicates);
 
 /**
  * Inserts an element into a sorted array.
  *
- * If the target array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
  *
- * If the capacity is not enough to hold the new data, a reallocation
- * attempt is made.
- *
- * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
- * It is important, however, that @p size and @p capacity are pointers to
- * variables of the same type.
+ * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined.
  *
- * @param target (@c void**) a pointer to the target array
- * @param size (@c SIZE_TYPE*) a pointer to the size of the target array
- * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array
- * @param elem_size (@c size_t) the size of one element
- * @param elem (@c void*) a pointer to the element to add
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param element (@c void*) a pointer to element that shall be inserted
  * @retval zero success
- * @retval non-zero failure
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
-    cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
+#define cx_array_insert_sorted_a(allocator, array, cmp_func, element) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, element, 1, true)
 
 /**
- * Convenience macro for cx_array_add_sorted() with a default
- * layout and the specified reallocator.
+ * Inserts an element into a sorted array.
+ *
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
  *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined.
+ *
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param element (@c void*) a pointer to element that shall be inserted
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_sorted()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_add_sorted_a(reallocator, array, elem, cmp_func) \
-    cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \
-        sizeof((array)[0]), &(elem), cmp_func, reallocator)
+#define cx_array_insert_sorted(array, cmp_func, element) \
+        cx_array_insert_sorted_a(cxDefaultAllocator, array, cmp_func, element)
+
+/**
+ * Inserts sorted data into a sorted array.
+ *
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
+ *
+ * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined.
+ *
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted
+ * @param n (@c size_t) the number of elements that shall be inserted
+ * @retval zero success
+ * @retval non-zero a re-allocation was necessary but failed
+ */
+#define cx_array_insert_sorted_array_a(allocator, array, cmp_func, sorted_data, n) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, sorted_data, n, true)
 
 /**
- * Convenience macro for cx_array_add_sorted() with a default
- * layout and the default reallocator.
+ * Inserts sorted data into a sorted array.
+ *
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
  *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined.
+ *
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted
+ * @param n (@c size_t) the number of elements that shall be inserted
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_sorted_a()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_add_sorted(array, elem, cmp_func) \
-    cx_array_simple_add_sorted_a(NULL, array, elem, cmp_func)
+#define cx_array_insert_sorted_array(array, cmp_func, sorted_data, n) \
+        cx_array_insert_sorted_array_a(cxDefaultAllocator, array, cmp_func, sorted_data, n)
 
 /**
- * Convenience macro for cx_array_insert_sorted() with a default
- * layout and the specified reallocator.
+ * Inserts an element into a sorted array if it is not already contained.
+ *
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
+ *
+ * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined.
  *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param element (@c void*) a pointer to element that shall be inserted
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_sorted()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_insert_sorted_a(reallocator, array, src, n, cmp_func) \
-    cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
-        cmp_func, src, sizeof((array)[0]), n, reallocator)
+#define cx_array_insert_unique_a(allocator, array, cmp_func, element) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, element, 1, false)
 
 /**
- * Convenience macro for cx_array_insert_sorted() with a default
- * layout and the default reallocator.
+ * Inserts an element into a sorted array if it is not already contained.
+ *
+ * When the capacity is not enough to hold the new element, a re-allocation is attempted.
  *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
+ * @attention if the array is not sorted according to the specified @p cmp_func, the behavior is undefined.
+ *
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param element (@c void*) a pointer to element that shall be inserted
  * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_sorted_a()
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \
-    cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func)
-
+#define cx_array_insert_unique(array, cmp_func, element) \
+        cx_array_insert_unique_a(cxDefaultAllocator, array, cmp_func, element)
 
 /**
- * Inserts a sorted array into another sorted array, avoiding duplicates.
- *
- * If either the target or the source array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
+ * Inserts sorted data into a sorted array, skipping duplicates.
  *
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made.
- * You can create your own reallocator by hand, use #cx_array_default_reallocator,
- * or use the convenience function cx_array_reallocator() to create a custom reallocator.
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
+ *
+ * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined.
  *
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the capacity of the target array
- * @param cmp_func the compare function for the elements
- * @param src the source array
- * @param elem_size the size of one element
- * @param elem_count the number of elements to insert
- * @param reallocator the array reallocator to use
- * (@c NULL defaults to #cx_array_default_reallocator)
+ * @param allocator (@c CxAllocator*) the allocator to use for a possible reallocation
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted
+ * @param n (@c size_t) the number of elements that shall be inserted
  * @retval zero success
- * @retval non-zero failure
+ * @retval non-zero a re-allocation was necessary but failed
  */
-cx_attr_nonnull_arg(1, 2, 3, 5)
-CX_EXPORT int cx_array_insert_unique(void **target, size_t *size, size_t *capacity,
-        cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count,
-        CxArrayReallocator *reallocator);
+#define cx_array_insert_unique_array_a(allocator, array, cmp_func, sorted_data, n) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, sorted_data, n, false)
 
 /**
- * Inserts an element into a sorted array if it does not exist.
- *
- * If the target array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
+ * Inserts sorted data into a sorted array, skipping duplicates.
  *
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made.
+ * When the capacity is not enough to hold the new elements, a re-allocation is attempted.
  *
- * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
- * It is important, however, that @p size and @p capacity are pointers to
- * variables of the same type.
+ * @attention if either array is not sorted according to the specified @p cmp_func, the behavior is undefined.
  *
- * @param target (@c void**) a pointer to the target array
- * @param size (@c SIZE_TYPE*) a pointer to the size of the target array
- * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array
- * @param elem_size (@c size_t) the size of one element
- * @param elem (@c void*) a pointer to the element to add
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @retval zero success (also when the element was already present)
- * @retval non-zero failure
+ * @param array the name of the array where the elements shall be inserted
+ * @param cmp_func (@c cx_compare_func) the compare function that establishes the order
+ * @param sorted_data (@c void*) a pointer to an array of sorted data that shall be inserted
+ * @param n (@c size_t) the number of elements that shall be inserted
+ * @retval zero success
+ * @retval non-zero a re-allocation was necessary but failed
  */
-#define cx_array_add_unique(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
-    cx_array_insert_unique((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
+#define cx_array_insert_unique_array(array, cmp_func, sorted_data, n) \
+        cx_array_insert_unique_array_a(cxDefaultAllocator, array, cmp_func, sorted_data, n)
+
+/**
+ * Creates an iterator over the elements of an array.
+ *
+ * Internal function - do not use.
+ *
+ * @param array a pointer to the array structure
+ * @param elem_size the size of one element
+ * @return an iterator over the elements
+ */
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT CxIterator cx_array_iterator_(CxArray *array, size_t elem_size);
 
 /**
- * Convenience macro for cx_array_add_unique() with a default
- * layout and the specified reallocator.
+ * Creates an iterator over the elements of an array.
+ *
+ * The iterator will yield pointers to the elements.
  *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_unique()
+ * @param array the name of the array
+ * @return an iterator over the elements
+ * @see cx_array_iterator_ptr()
  */
-#define cx_array_simple_add_unique_a(reallocator, array, elem, cmp_func) \
-    cx_array_add_unique(&array, &(array##_size), &(array##_capacity), \
-        sizeof((array)[0]), &(elem), cmp_func, reallocator)
+#define cx_array_iterator(array) \
+        cx_array_iterator_((CxArray*)&(array), sizeof((array).data[0]))
 
 /**
- * Convenience macro for cx_array_add_unique() with a default
- * layout and the default reallocator.
+ * Creates an iterator over the elements of an array containing pointers.
+ *
+ * Internal function - do not use.
  *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_unique_a()
+ * @param array the name of the array
+ * @return an iterator over the elements
  */
-#define cx_array_simple_add_unique(array, elem, cmp_func) \
-    cx_array_simple_add_unique_a(NULL, array, elem, cmp_func)
+cx_attr_nodiscard cx_attr_nonnull
+CX_EXPORT CxIterator cx_array_iterator_ptr_(CxArray *array);
 
 /**
- * Convenience macro for cx_array_insert_unique() with a default
- * layout and the specified reallocator.
+ * Creates an iterator over the elements of an array containing pointers.
+ *
+ * The iterator will yield the elements themselves, which are supposed to
+ * be pointers.
  *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_unique()
+ * @param array the name of the array
+ * @return an iterator over the elements
+ * @see cx_array_iterator()
  */
-#define cx_array_simple_insert_unique_a(reallocator, array, src, n, cmp_func) \
-    cx_array_insert_unique((void**)(&array), &(array##_size), &(array##_capacity), \
-        cmp_func, src, sizeof((array)[0]), n, reallocator)
+#define cx_array_iterator_ptr(array) \
+        cx_array_iterator_ptr_((CxArray*)&(array))
 
 /**
- * Convenience macro for cx_array_insert_unique() with a default
- * layout and the default reallocator.
+ * Deallocates an array.
+ *
+ * Internal function - do not use.
+ *
+ * @param allocator (@c CxAllocator*) the allocator which was used to allocate the array
+ * @param array a pointer to the array structure
+ */
+cx_attr_nonnull
+CX_EXPORT void cx_array_free_(const CxAllocator *allocator, CxArray *array);
+
+/**
+ * Deallocates an array.
+ *
+ * The structure is reset to zero and can be re-initialized with
+ * cx_array_inita().
  *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_unique_a()
+ * @param array the name of the array
  */
-#define cx_array_simple_insert_unique(array, src, n, cmp_func) \
-    cx_array_simple_insert_unique_a(NULL, array, src, n, cmp_func)
+#define cx_array_free(array) cx_array_free_(cxDefaultAllocator, (CxArray*)&(array))
+
+/**
+ * Deallocates an array.
+ *
+ * The structure is reset to zero and can be re-initialized with
+ * cx_array_init_a().
+ *
+ * @param allocator (@c CxAllocator*) the allocator which was used to allocate the array
+ * @param array the name of the array
+ */
+#define cx_array_free_a(allocator, array) cx_array_free_(allocator, (CxArray*)&(array))
+
 
 /**
  * Searches the largest lower bound in a sorted array.
@@ -750,6 +707,91 @@
 CX_EXPORT size_t cx_array_binary_search_sup(const void *arr, size_t size,
         size_t elem_size, const void *elem, cx_compare_func cmp_func);
 
+
+/**
+ * Searches the largest lower bound in a sorted array.
+ *
+ * In other words, this function returns the index of the largest element
+ * in @p arr that is less or equal to @p elem with respect to @p cmp_func.
+ * When no such element exists, @p size is returned.
+ *
+ * When such an element exists more than once, the largest index of all those
+ * elements is returned.
+ *
+ * If @p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @param context the context for the compare function
+ * @return the index of the largest lower bound, or @p size
+ * @see cx_array_binary_search_sup()
+ * @see cx_array_binary_search()
+ */
+cx_attr_nonnull
+CX_EXPORT size_t cx_array_binary_search_inf_c(const void *arr, size_t size,
+        size_t elem_size, const void *elem, cx_compare_func2 cmp_func, void *context);
+
+/**
+ * Searches an item in a sorted array.
+ *
+ * When such an element exists more than once, the largest index of all those
+ * elements is returned.
+ *
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @param context the context for the compare function
+ * @return the index of the element in the array, or @p size if the element
+ * cannot be found
+ * @see cx_array_binary_search_inf()
+ * @see cx_array_binary_search_sup()
+ */
+cx_attr_nonnull
+CX_EXPORT size_t cx_array_binary_search_c(const void *arr, size_t size,
+        size_t elem_size, const void *elem, cx_compare_func2 cmp_func, void *context);
+
+/**
+ * Searches the smallest upper bound in a sorted array.
+ *
+ * In other words, this function returns the index of the smallest element
+ * in @p arr that is greater or equal to @p elem with respect to @p cmp_func.
+ * When no such element exists, @p size is returned.
+ *
+ * When such an element exists more than once, the smallest index of all those
+ * elements is returned.
+ *
+ * If @p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @param context the context for the compare function
+ * @return the index of the smallest upper bound, or @p size
+ * @see cx_array_binary_search_inf()
+ * @see cx_array_binary_search()
+ */
+cx_attr_nonnull
+CX_EXPORT size_t cx_array_binary_search_sup_c(const void *arr, size_t size,
+        size_t elem_size, const void *elem, cx_compare_func2 cmp_func, void *context);
+
 /**
  * Swaps two array elements.
  *
@@ -766,13 +808,10 @@
  *
  * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
  * copies of the added elements, and the compare function will be automatically set
- * to cx_cmp_ptr(), if none is given.
+ * to cx_cmp_ptr().
  *
  * @param allocator the allocator for allocating the list memory
  * (if @c NULL, the cxDefaultAllocator will be used)
- * @param comparator the comparator for the elements
- * (if @c NULL, and the list is not storing pointers, sort and find
- * functions will not work)
  * @param elem_size the size of each element in bytes
  * @param initial_capacity the initial number of elements the array can store
  * @return the created list
@@ -781,25 +820,7 @@
 cx_attr_malloc
 cx_attr_dealloc(cxListFree, 1)
 CX_EXPORT CxList *cxArrayListCreate(const CxAllocator *allocator,
-        cx_compare_func comparator, size_t elem_size, size_t initial_capacity);
-
-/**
- * Allocates an array list for storing elements with @p elem_size bytes each.
- *
- * The list will use the cxDefaultAllocator and @em NO compare function.
- * If you want to call functions that need a compare function, you have to
- * set it immediately after creation or use cxArrayListCreate().
- *
- * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
- * copies of the added elements and the compare function will be automatically set
- * to cx_cmp_ptr().
- *
- * @param elem_size (@c size_t) the size of each element in bytes
- * @param initial_capacity (@c size_t) the initial number of elements the array can store
- * @return the created list
- */
-#define cxArrayListCreateSimple(elem_size, initial_capacity) \
-    cxArrayListCreate(NULL, NULL, elem_size, initial_capacity)
+        size_t elem_size, size_t initial_capacity);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/buffer.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/buffer.h	Thu Dec 18 17:50:15 2025 +0100
@@ -168,18 +168,18 @@
  * space will be leaking after the copy-on-write operation.
  *
  * @param buffer the buffer to initialize
+ * @param allocator the allocator this buffer shall use for automatic
+ * memory management
+ * (if @c NULL, the cxDefaultAllocator will be used)
  * @param space pointer to the memory area, or @c NULL to allocate
  * new memory
  * @param capacity the capacity of the buffer
- * @param allocator the allocator this buffer shall use for automatic
- * memory management
- * (if @c NULL, the cxDefaultAllocator will be used)
  * @param flags buffer features (see cx_buffer_s.flags)
  * @return zero on success, non-zero if a required allocation failed
  */
 cx_attr_nonnull_arg(1)
-CX_EXPORT int cxBufferInit(CxBuffer *buffer, void *space, size_t capacity,
-        const CxAllocator *allocator, int flags);
+CX_EXPORT int cxBufferInit(CxBuffer *buffer, const CxAllocator *allocator,
+        void *space, size_t capacity, int flags);
 
 /**
  * Destroys the buffer contents.
@@ -219,18 +219,18 @@
  * Then this function will allocate the space and enforce
  * the #CX_BUFFER_FREE_CONTENTS flag.
  *
+ * @param allocator the allocator to use for allocating the structure and the automatic
+ * memory management within the buffer
+ * (if @c NULL, the cxDefaultAllocator will be used)
  * @param space pointer to the memory area, or @c NULL to allocate
  * new memory
  * @param capacity the capacity of the buffer
- * @param allocator the allocator to use for allocating the structure and the automatic
- * memory management within the buffer
- * (if @c NULL, the cxDefaultAllocator will be used)
  * @param flags buffer features (see cx_buffer_s.flags)
  * @return a pointer to the buffer on success, @c NULL if a required allocation failed
  */
 cx_attr_malloc cx_attr_dealloc(cxBufferFree, 1) cx_attr_nodiscard
-CX_EXPORT CxBuffer *cxBufferCreate(void *space, size_t capacity,
-        const CxAllocator *allocator, int flags);
+CX_EXPORT CxBuffer *cxBufferCreate(const CxAllocator *allocator, void *space,
+                                   size_t capacity, int flags);
 
 /**
  * Shifts the contents of the buffer by the given offset.
--- a/ucx/cx/collection.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/collection.h	Thu Dec 18 17:50:15 2025 +0100
@@ -58,10 +58,6 @@
      */
     const CxAllocator *allocator;
     /**
-     * The comparator function for the elements.
-     */
-    cx_compare_func cmpfunc;
-    /**
      * The size of each element.
      */
     size_t elem_size;
@@ -70,6 +66,19 @@
      */
     size_t size;
     /**
+     * A two-argument comparator function for the elements.
+     */
+    cx_compare_func simple_cmp;
+    /**
+     * A three-argument comparator function for the elements.
+     * If specified, this function has precedence over the @c simple_cmp function.
+     */
+    cx_compare_func2 advanced_cmp;
+    /**
+     * A pointer to custom data for the @c advanced_cmp function
+     */
+    void *cmp_data;
+    /**
      * An optional simple destructor for the collection's elements.
      *
      * @attention Read the documentation of the particular collection implementation
@@ -139,6 +148,25 @@
  */
 #define cxCollectionStoresPointers(c) ((c)->collection.store_pointer)
 
+
+/**
+ * Convenience macro for adding indirection to an element if the collection is storing pointers.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param elem the pointer that shall be taken the address from, if the collection is storing pointers
+ * @return if the collection is storing pointers, takes the address of @p elem, otherwise returns @p elem
+ */
+#define cx_ref(c, elem) (cxCollectionStoresPointers(c) ? ((void*)&(elem)) : (elem))
+
+/**
+ * Convenience macro for dereferencing an element if the collection is storing pointers.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param elem a pointer to the collection element
+ * @return if the collection is storing pointers, dereferences @p elem, otherwise returns @p elem
+ */
+#define cx_deref(c, elem) (cxCollectionStoresPointers(c) ? *((void**)(elem)) : (elem))
+
 /**
  * Indicates whether the collection can guarantee that the stored elements are currently sorted.
  *
@@ -154,29 +182,89 @@
 #define cxCollectionSorted(c) ((c)->collection.sorted || (c)->collection.size == 0)
 
 /**
- * Sets the compare function for a collection.
+ * Sets a simple compare function for a collection.
+ *
+ * Erases a possible advanced compare function.
+ * If you want to set both, because you want to access the simple function
+ * in your advanced function, you must set the simple function first.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param func (@c cx_compare_func) the compare function
+ */
+#define cxSetCompareFunc(c, func) \
+    (c)->collection.simple_cmp = (cx_compare_func)(func); \
+    (c)->collection.advanced_cmp = NULL
+
+/**
+ * Sets an advanced compare function that supports custom data for a collection.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param func (@c cx_compare_func2) the compare function
+ * @param data (@c void*) the pointer to custom data that is passed to the compare function
+ */
+#define cxSetAdvancedCompareFunc(c, func, data) \
+    (c)->collection.advanced_cmp = (cx_compare_func2) func; \
+    (c)->collection.destructor_data = data
+
+/**
+ * Invokes the simple comparator function for two elements.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
  *
  * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
- * @param func (@c cx_compare_func) the compare function that shall be used by @c c
+ * @param left (@c void*) pointer to data
+ * @param right (@c void*) pointer to data
+ */
+#define cx_invoke_simple_compare_func(c, left, right) \
+    (c)->collection.simple_cmp(left, right)
+
+/**
+ * Invokes the advanced comparator function for two elements.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param left (@c void*) pointer to data
+ * @param right (@c void*) pointer to data
  */
-#define cxCollectionCompareFunc(c, func) (c)->collection.cmpfunc = (func)
+#define cx_invoke_advanced_compare_func(c, left, right) \
+    (c)->collection.advanced_cmp(left, right, (c)->collection.cmp_data)
+
+
+/**
+ * Invokes the configured comparator function for two elements.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param left (@c void*) pointer to data
+ * @param right (@c void*) pointer to data
+ */
+#define cx_invoke_compare_func(c, left, right) \
+    (((c)->collection.advanced_cmp) ? \
+    cx_invoke_advanced_compare_func(c,left,right) : \
+    cx_invoke_simple_compare_func(c,left,right))
 
 /**
  * Sets a simple destructor function for this collection.
  *
  * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
- * @param destr the destructor function
+ * @param destr (@c cx_destructor_func) the destructor function
  */
-#define cxDefineDestructor(c, destr) \
+#define cxSetDestructor(c, destr) \
     (c)->collection.simple_destructor = (cx_destructor_func) destr
 
 /**
- * Sets a simple destructor function for this collection.
+ * Sets an advanced destructor function for this collection.
  *
  * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
- * @param destr the destructor function
+ * @param destr (@c cx_destructor_func2) the destructor function
+ * @param data (@c void*) the additional data the advanced destructor is invoked with
  */
-#define cxDefineAdvancedDestructor(c, destr, data) \
+#define cxSetAdvancedDestructor(c, destr, data) \
     (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \
     (c)->collection.destructor_data = data
 
--- a/ucx/cx/compare.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/compare.h	Thu Dec 18 17:50:15 2025 +0100
@@ -57,6 +57,13 @@
 typedef int (*cx_compare_func)(const void *left, const void *right);
 
 /**
+ * A comparator function comparing two arbitrary values.
+ *
+ * Functions with this signature allow specifying a pointer to custom data.
+ */
+typedef int (*cx_compare_func2)(const void *left, const void *right, void *data);
+
+/**
  * Compares two integers of type int.
  *
  * @note the parameters deliberately have type @c void* to be
@@ -527,6 +534,41 @@
 cx_attr_nonnull cx_attr_nodiscard
 CX_EXPORT int cx_cmp_ptr(const void *ptr1, const void *ptr2);
 
+/**
+ * A @c cx_compare_func2 compatible wrapper for @c memcmp().
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param n (@c size_t*) a pointer to the length
+ * @return the result of @c memcmp()
+ */
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_acmp_memcmp(const void *ptr1, const void *ptr2, void *n);
+
+/** Wraps a compare function for cx_acmp_wrap. */
+typedef struct {
+    /** The wrapped compare function */
+    cx_compare_func cmp;
+} cx_compare_func_wrapper;
+
+/**
+ * A @c cx_compare_func2 wrapper for a @c cx_compare_func().
+ *
+ * This is not strictly compatible with a @c cx_compare_func2 because
+ * ISO C does not define conversions between function and object pointers.
+ *
+ * But it works on all tested platforms to cast a pointer to this function to
+ * a @c cx_compare_func2.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param cmp_wrapper a pointer to a @c cx_compare_func_wrapper
+ * @return the result of the invoked compare function
+ * @see cx_compare_func_wrapper_s
+ */
+cx_attr_nonnull cx_attr_nodiscard
+CX_EXPORT int cx_acmp_wrap(const void *ptr1, const void *ptr2, void* cmp_wrapper);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
--- a/ucx/cx/hash_map.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/hash_map.h	Thu Dec 18 17:50:15 2025 +0100
@@ -88,22 +88,6 @@
         size_t itemsize, size_t buckets);
 
 /**
- * Creates a new hash map with a default number of buckets.
- *
- * If @p elem_size is #CX_STORE_POINTERS, the created map stores pointers instead of
- * copies of the added elements.
- *
- * @note Iterators provided by this hash map implementation provide the remove operation.
- * The index value of an iterator is incremented when the iterator advanced without
- * removing an entry.
- * In other words, when the iterator is finished, @c index==size .
- *
- * @param itemsize (@c size_t) the size of one element
- * @return (@c CxMap*) a pointer to the new hash map
- */
-#define cxHashMapCreateSimple(itemsize) cxHashMapCreate(NULL, itemsize, 0)
-
-/**
  * Increases the number of buckets, if necessary.
  *
  * The load threshold is @c 0.75*buckets. If the element count exceeds the load
--- a/ucx/cx/iterator.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/iterator.h	Thu Dec 18 17:50:15 2025 +0100
@@ -214,26 +214,15 @@
  * use cxIteratorPtr() to create an iterator which directly
  * yields the stored pointers.
  *
- * While the iterator is in use, the array may only be altered by removing
- * elements through #cxIteratorFlagRemoval(). Every other change to the array
- * will bring this iterator to an undefined state.
- *
- * When @p remove_keeps_order is set to @c false, removing an element will only
- * move the last element to the position of the removed element, instead of
- * moving all subsequent elements by one. Usually, when the order of elements is
- * not important, this parameter should be set to @c false.
- *
  * @param array a pointer to the array (can be @c NULL)
  * @param elem_size the size of one array element
  * @param elem_count the number of elements in the array
- * @param remove_keeps_order @c true if the order of elements must be preserved
- * when removing an element
  * @return an iterator for the specified array
  * @see cxIteratorPtr()
  */
 cx_attr_nodiscard
 CX_EXPORT CxIterator cxIterator(const void *array,
-        size_t elem_size, size_t elem_count, bool remove_keeps_order);
+        size_t elem_size, size_t elem_count);
 
 /**
  * Creates an iterator for the specified plain pointer array.
@@ -243,25 +232,13 @@
  * hand, an iterator created with cxIterator() would return the
  * addresses of those pointers within the array).
  *
- * While the iterator is in use, the array may only be altered by removing
- * elements through #cxIteratorFlagRemoval(). Every other change to the array
- * will bring this iterator to an undefined state.
- *
- * When @p remove_keeps_order is set to @c false, removing an element will only
- * move the last element to the position of the removed element, instead of
- * moving all subsequent elements by one. Usually, when the order of elements is
- * not important, this parameter should be set to @c false.
- *
  * @param array a pointer to the array (can be @c NULL)
  * @param elem_count the number of elements in the array
- * @param remove_keeps_order @c true if the order of elements must be preserved
- * when removing an element
  * @return an iterator for the specified array
  * @see cxIterator()
  */
 cx_attr_nodiscard
-CX_EXPORT CxIterator cxIteratorPtr(const void *array, size_t elem_count,
-        bool remove_keeps_order);
+CX_EXPORT CxIterator cxIteratorPtr(const void *array, size_t elem_count);
 
 #ifdef __cplusplus
 } // extern "C"
--- a/ucx/cx/json.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/json.h	Thu Dec 18 17:50:15 2025 +0100
@@ -43,8 +43,6 @@
 #include "array_list.h"
 #include "map.h"
 
-#include <string.h>
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -185,10 +183,6 @@
 typedef struct cx_json_value_s CxJsonValue;
 
 /**
- * Type alias for the JSON array struct.
- */
-typedef struct cx_json_array_s CxJsonArray;
-/**
  * Type alias for the map representing a JSON object.
  * The map contains pointers of type @c CxJsonValue.
  */
@@ -211,16 +205,6 @@
 typedef enum cx_json_literal CxJsonLiteral;
 
 /**
- * JSON array structure.
- */
-struct cx_json_array_s {
-    /**
-     * The array data.
-     */
-    CX_ARRAY_DECLARE(CxJsonValue*, data);
-};
-
-/**
  * Structure for a JSON value.
  */
 struct cx_json_value_s {
@@ -243,7 +227,7 @@
         /**
          * The array data if the type is #CX_JSON_ARRAY.
          */
-        CxJsonArray array;
+        CX_ARRAY(CxJsonValue*, array);
         /**
          * The object data if the type is #CX_JSON_OBJECT.
          */
@@ -298,6 +282,7 @@
      * The allocator used for produced JSON values.
      */
     const CxAllocator *allocator;
+
     /**
      * The input buffer.
      */
@@ -327,12 +312,12 @@
     /**
      * State stack.
      */
-    CX_ARRAY_DECLARE_SIZED(int, states, unsigned);
+    CX_ARRAY(int, states);
 
     /**
      * Value buffer stack.
      */
-    CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned);
+    CX_ARRAY(CxJsonValue*, vbuf);
 
     /**
      * Internally reserved memory for the state stack.
@@ -477,28 +462,28 @@
 /**
  * Produces a compact string representation of the specified JSON value.
  *
+ * @param allocator the allocator for the string
  * @param value the JSON value
- * @param allocator the allocator for the string
  * @return the produced string
  * @see cxJsonWrite()
  * @see cxJsonWriterCompact()
  * @see cxJsonToPrettyString()
  */
-cx_attr_nonnull_arg(1)
-CX_EXPORT cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator);
+cx_attr_nonnull_arg(2)
+CX_EXPORT cxmutstr cxJsonToString(const CxAllocator *allocator, CxJsonValue *value);
 
 /**
  * Produces a pretty string representation of the specified JSON value.
  *
+ * @param allocator the allocator for the string
  * @param value the JSON value
- * @param allocator the allocator for the string
  * @return the produced string
  * @see cxJsonWrite()
  * @see cxJsonWriterPretty()
  * @see cxJsonToString()
  */
-cx_attr_nonnull_arg(1)
-CX_EXPORT cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator);
+cx_attr_nonnull_arg(2)
+CX_EXPORT cxmutstr cxJsonToPrettyString(const CxAllocator *allocator, CxJsonValue *value);
 
 /**
  * Initializes the JSON interface.
@@ -628,13 +613,16 @@
 /**
  * Creates a new (empty) JSON array.
  *
+ * Optionally, this function already allocates memory with the given capacity.
+ *
  * @param allocator the allocator to use
+ * @param capacity optional capacity or zero if it's unknown how many elements the array will have
  * @return the new JSON array or @c NULL if allocation fails
  * @see cxJsonObjPutArr()
  * @see cxJsonArrAddValues()
  */
 cx_attr_nodiscard
-CX_EXPORT CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator);
+CX_EXPORT CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator, size_t capacity);
 
 /**
  * Creates a new JSON number value.
@@ -840,23 +828,25 @@
  *
  * @param obj the target JSON object
  * @param name the name of the new value
+ * @param capacity optional initial capacity
  * @return the new value or @c NULL if allocation fails
  * @see cxJsonObjPut()
  * @see cxJsonCreateArr()
  */
 cx_attr_nonnull
-CX_EXPORT CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name);
+CX_EXPORT CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name, size_t capacity);
 
 /**
  * Creates a new JSON array and adds it to an object.
  *
  * @param obj (@c CxJsonValue*) the target JSON object
  * @param name (any string) the name of the new value
+ * @param capacity (@c size_t) optional initial capacity
  * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails
  * @see cxJsonObjPut()
  * @see cxJsonCreateArr()
  */
-#define cxJsonObjPutArr(obj, name) cx_json_obj_put_arr(obj, cx_strcast(name))
+#define cxJsonObjPutArr(obj, name, capacity) cx_json_obj_put_arr(obj, cx_strcast(name), capacity)
 
 /**
  * Creates a new JSON number and adds it to an object.
@@ -1238,7 +1228,7 @@
  */
 cx_attr_nonnull
 CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) {
-    return value->array.data_size;
+    return value->array.size;
 }
 
 /**
@@ -1366,6 +1356,66 @@
  */
 #define cxJsonObjRemove(value, name) cx_json_obj_remove(value, cx_strcast(name))
 
+/**
+ * Performs a deep comparison of two JSON values.
+ *
+ * The order of object members is ignored during comparison.
+ *
+ * @param json the JSON value
+ * @param other the other JSON value that the JSON value is compared to
+ * @retval zero the values are equal (except for ordering of object members)
+ * @retval non-zero the values differ
+ */
+CX_EXPORT int cxJsonCompare(const CxJsonValue *json, const CxJsonValue *other);
+
+
+/**
+ * Creates a deep copy of the specified JSON value.
+ *
+ * If you need a @c cx_clone_func compatible version, see cxJsonCloneFunc().
+ *
+ * @note when you are cloning @c NULL, you will get a pointer to a statically
+ * allocated value which represents nothing.
+ *
+ * @param value the value to be cloned
+ * @param allocator the allocator for the new value
+ * @return the new value or @c NULL if any allocation was unsuccessful
+ * @see cxJsonCloneFunc()
+ */
+cx_attr_nodiscard
+CX_EXPORT CxJsonValue* cxJsonClone(const CxJsonValue* value,
+        const CxAllocator* allocator);
+
+
+/**
+ * A @c cx_clone_func compatible version of cxJsonClone().
+ *
+ * Internal function - use cxJsonCloneFunc() to get a properly casted function pointer.
+ *
+ * @param target the target memory or @c NULL
+ * @param source the value to be cloned
+ * @param allocator the allocator for the new value
+ * @param data unused
+ * @return the new value or @c NULL if any allocation was unsuccessful
+ * @see cxJsonClone()
+ */
+cx_attr_nodiscard
+CX_EXPORT CxJsonValue* cx_json_clone_func(
+        CxJsonValue* target, const CxJsonValue* source,
+        const CxAllocator* allocator, void *data);
+
+/**
+ * A @c cx_clone_func compatible version of cxJsonClone().
+ *
+ * @param target (@c CxJsonValue*) the target memory or @c NULL
+ * @param source (@c CxJsonValue*) the value to be cloned
+ * @param allocator (@c CxAllocator*) the allocator for the new value
+ * @param data unused
+ * @return the new value or @c NULL if any allocation was unsuccessful
+ * @see cxJsonClone()
+ */
+#define cxJsonCloneFunc  ((cx_clone_func) cx_json_clone_func)
+
 #ifdef __cplusplus
 }
 #endif
--- a/ucx/cx/kv_list.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/kv_list.h	Thu Dec 18 17:50:15 2025 +0100
@@ -49,7 +49,7 @@
  *
  * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
  * copies of the added elements, and the compare function will be automatically set
- * to cx_cmp_ptr() if none is given.
+ * to cx_cmp_ptr().
  *
  * After creating the list, it can also be used as a map after converting the pointer
  * to a CxMap pointer with cxKvListAsMap().
@@ -58,9 +58,6 @@
  *
  * @param allocator the allocator for allocating the list nodes
  * (if @c NULL, the cxDefaultAllocator will be used)
- * @param comparator the comparator for the elements
- * (if @c NULL, and the list is not storing pointers, sort and find
- * functions will not work)
  * @param elem_size the size of each element in bytes
  * @return the created list
  * @see cxKvListAsMap()
@@ -68,14 +65,14 @@
  */
 cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1)
 CX_EXPORT CxList *cxKvListCreate(const CxAllocator *allocator,
-        cx_compare_func comparator, size_t elem_size);
+        size_t elem_size);
 
 /**
  * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each.
  *
  * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
  * copies of the added elements, and the compare function will be automatically set
- * to cx_cmp_ptr() if none is given.
+ * to cx_cmp_ptr().
  *
  * This function creates the list with cxKvListCreate() and immediately applies
  * cxKvListAsMap(). If you want to use the returned object as a list, you can call
@@ -83,9 +80,6 @@
  *
  * @param allocator the allocator for allocating the list nodes
  * (if @c NULL, the cxDefaultAllocator will be used)
- * @param comparator the comparator for the elements
- * (if @c NULL, and the list is not storing pointers, sort and find
- * functions will not work)
  * @param elem_size the size of each element in bytes
  * @return the created list wrapped into the CxMap interface
  * @see cxKvListAsMap()
@@ -93,52 +87,7 @@
  */
 cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1)
 CX_EXPORT CxMap *cxKvListCreateAsMap(const CxAllocator *allocator,
-        cx_compare_func comparator, size_t elem_size);
-
-/**
- * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each.
- *
- * The list will use cxDefaultAllocator and no comparator function. If you want
- * to call functions that need a comparator, you must either set one immediately
- * after list creation or use cxKvListCreate().
- *
- * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
- * copies of the added elements, and the compare function will be automatically set
- * to cx_cmp_ptr().
- *
- * After creating the list, it can also be used as a map after converting the pointer
- * to a CxMap pointer with cxKvListAsMap().
- * When you want to use the list interface again, you can also convert the map pointer back
- * with cxKvListAsList().
- *
- * @param elem_size (@c size_t) the size of each element in bytes
- * @return (@c CxList*) the created list
- * @see cxKvListAsMap()
- * @see cxKvListAsList()
- */
-#define cxKvListCreateSimple(elem_size) cxKvListCreate(NULL, NULL, elem_size)
-
-/**
- * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each.
- *
- * The list will use cxDefaultAllocator and no comparator function. If you want
- * to call functions that need a comparator, you must either set one immediately
- * after list creation or use cxKvListCreate().
- *
- * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
- * copies of the added elements, and the compare function will be automatically set
- * to cx_cmp_ptr().
- *
- * This macro behaves as if the list was created with cxKvListCreateSimple() and
- * immediately followed up by cxKvListAsMap().
- * If you want to use the returned object as a list, you can call cxKvListAsList() later.
- *
- * @param elem_size (@c size_t) the size of each element in bytes
- * @return (@c CxMap*) the created list wrapped into the CxMap interface
- * @see cxKvListAsMap()
- * @see cxKvListAsList()
- */
-#define cxKvListCreateAsMapSimple(elem_size) cxKvListCreateAsMap(NULL, NULL, elem_size)
+        size_t elem_size);
 
 /**
  * Converts a map pointer belonging to a key-value-List back to the original list pointer.
@@ -152,7 +101,7 @@
 /**
  * Converts a map pointer belonging to a key-value-List back to the original list pointer.
  *
- * @param list a list created by cxKvListCreate() or cxKvListCreateSimple()
+ * @param list a list created by cxKvListCreate()
  * @return a map pointer that lets you use the list as if it was a map
  */
 cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull
--- a/ucx/cx/linked_list.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/linked_list.h	Thu Dec 18 17:50:15 2025 +0100
@@ -87,36 +87,16 @@
  *
  * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
  * copies of the added elements, and the compare function will be automatically set
- * to cx_cmp_ptr() if none is given.
+ * to cx_cmp_ptr().
  *
  * @param allocator the allocator for allocating the list nodes
  * (if @c NULL, the cxDefaultAllocator will be used)
- * @param comparator the comparator for the elements
- * (if @c NULL, and the list is not storing pointers, sort and find
- * functions will not work)
  * @param elem_size the size of each element in bytes
  * @return the created list
  */
 cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1)
 CX_EXPORT CxList *cxLinkedListCreate(const CxAllocator *allocator,
-        cx_compare_func comparator, size_t elem_size);
-
-/**
- * Allocates a linked list for storing elements with @p elem_size bytes each.
- *
- * The list will use cxDefaultAllocator and no comparator function. If you want
- * to call functions that need a comparator, you must either set one immediately
- * after list creation or use cxLinkedListCreate().
- *
- * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
- * copies of the added elements, and the compare function will be automatically set
- * to cx_cmp_ptr().
- *
- * @param elem_size (@c size_t) the size of each element in bytes
- * @return (@c CxList*) the created list
- */
-#define cxLinkedListCreateSimple(elem_size) \
-        cxLinkedListCreate(NULL, NULL, elem_size)
+        size_t elem_size);
 
 /**
  * Instructs the linked list to reserve extra data in each node.
@@ -162,16 +142,34 @@
  * @param start a pointer to the start node
  * @param loc_advance the location of the pointer to advance
  * @param loc_data the location of the @c data pointer within your node struct
- * @param cmp_func a compare function to compare @p elem against the node data
  * @param elem a pointer to the element to find
  * @param found_index an optional pointer where the index of the found node
  * (given that @p start has index 0) is stored
+ * @param cmp_func a compare function to compare @p elem against the node data
  * @return a pointer to the found node or @c NULL if no matching node was found
  */
-cx_attr_nonnull_arg(1, 4, 5)
+cx_attr_nonnull_arg(1, 4, 6)
 CX_EXPORT void *cx_linked_list_find(const void *start, ptrdiff_t loc_advance,
-        ptrdiff_t loc_data, cx_compare_func cmp_func, const void *elem,
-        size_t *found_index);
+        ptrdiff_t loc_data, const void *elem, size_t *found_index,
+        cx_compare_func cmp_func);
+
+/**
+ * Finds the node containing an element within a linked list.
+ *
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the @c data pointer within your node struct
+ * @param elem a pointer to the element to find
+ * @param found_index an optional pointer where the index of the found node
+ * (given that @p start has index 0) is stored
+ * @param cmp_func a compare function to compare @p elem against the node data
+ * @param context additional context for the compare function
+ * @return a pointer to the found node or @c NULL if no matching node was found
+ */
+cx_attr_nonnull_arg(1, 4, 6)
+CX_EXPORT void *cx_linked_list_find_c(const void *start, ptrdiff_t loc_advance,
+        ptrdiff_t loc_data, const void *elem, size_t *found_index,
+        cx_compare_func2 cmp_func, void *context);
 
 /**
  * Finds the first node in a linked list.
@@ -454,16 +452,6 @@
 /**
  * Sorts a linked list based on a comparison function.
  *
- * This function can work with linked lists of the following structure:
- * @code
- * typedef struct node node;
- * struct node {
- *   node* prev;
- *   node* next;
- *   my_payload data;
- * }
- * @endcode
- *
  * @note This is a recursive function with at most logarithmic recursion depth.
  *
  * @param begin a pointer to the beginning node pointer (required)
@@ -477,6 +465,23 @@
 CX_EXPORT void cx_linked_list_sort(void **begin, void **end,
         ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func cmp_func);
 
+/**
+ * Sorts a linked list based on a comparison function.
+ *
+ * @note This is a recursive function with at most logarithmic recursion depth.
+ *
+ * @param begin a pointer to the beginning node pointer (required)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param loc_data the location of the @c data pointer within your node struct
+ * @param cmp_func the compare function defining the sort order
+ * @param context additional context for the compare function
+ */
+cx_attr_nonnull_arg(1, 6)
+CX_EXPORT void cx_linked_list_sort_c(void **begin, void **end,
+        ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_data, cx_compare_func2 cmp_func, void *context);
+
 
 /**
  * Compares two lists element wise.
@@ -496,6 +501,23 @@
         ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func);
 
 /**
+ * Compares two lists element wise.
+ *
+ * @attention Both lists must have the same structure.
+ *
+ * @param begin_left the beginning of the left list (@c NULL denotes an empty list)
+ * @param begin_right the beginning of the right list  (@c NULL denotes an empty list)
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the @c data pointer within your node struct
+ * @param cmp_func the function to compare the elements
+ * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the
+ * right list, positive if the left list is larger than the right list, zero if both lists are equal.
+ */
+cx_attr_nonnull_arg(5)
+CX_EXPORT int cx_linked_list_compare_c(const void *begin_left, const void *begin_right,
+        ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func2 cmp_func, void *context);
+
+/**
  * Reverses the order of the nodes in a linked list.
  *
  * @param begin a pointer to the beginning node pointer (required)
--- a/ucx/cx/list.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/list.h	Thu Dec 18 17:50:15 2025 +0100
@@ -60,10 +60,6 @@
      * The list class definition.
      */
     const cx_list_class *cl;
-    /**
-     * The actual implementation in case the list class is delegating.
-     */
-    const cx_list_class *climpl;
 };
 
 /**
@@ -302,19 +298,18 @@
  * @code
  * CxList *myCustomListCreate(
  *         const CxAllocator *allocator,
- *         cx_compare_func comparator,
  *         size_t elem_size
  * ) {
  *     if (allocator == NULL) {
  *         allocator = cxDefaultAllocator;
  *     }
  *
- *     MyCustomList *list = cxCalloc(allocator, 1, sizeof(MyCustomList));
+ *     MyCustomList *list = cxZalloc(allocator, sizeof(MyCustomList));
  *     if (list == NULL) return NULL;
  *
  *     // initialize
  *     cx_list_init((CxList*)list, &my_custom_list_class,
- *             allocator, comparator, elem_size);
+ *             allocator, elem_size);
  *
  *     // ... some more custom stuff ...
  *
@@ -325,13 +320,24 @@
  * @param list the list to initialize
  * @param cl the list class
  * @param allocator the allocator for the elements
- * @param comparator a compare function for the elements
  * @param elem_size the size of one element
  */
 cx_attr_nonnull_arg(1, 2, 3)
 CX_EXPORT void cx_list_init(struct cx_list_s *list,
     struct cx_list_class_s *cl, const struct cx_allocator_s *allocator,
-    cx_compare_func comparator, size_t elem_size);
+    size_t elem_size);
+
+/**
+ * A @c cx_compare_func2 compatible wrapper for the compare functions of a list.
+ *
+ * @param left first element
+ * @param right second element
+ * @param list the list which is comparing the elements
+ * @return the comparison result
+ */
+cx_attr_nonnull
+CX_EXPORT int cx_list_compare_wrapper(
+    const void *left, const void *right, void *list);
 
 /**
  * Returns the number of elements currently stored in the list.
@@ -984,7 +990,7 @@
  * @param data optional additional data that is passed to the clone function
  * @retval zero when all elements were successfully cloned
  * @retval non-zero when an allocation error occurred
- * @see cxListCloneSimple()
+ * @see cxListCloneShallow()
  */
 cx_attr_nonnull_arg(1, 2, 3)
 CX_EXPORT int cxListClone(CxList *dst, const CxList *src,
@@ -1007,7 +1013,7 @@
  * @param data optional additional data that is passed to the clone function
  * @retval zero when the elements were successfully cloned
  * @retval non-zero when an allocation error occurred
- * @see cxListDifferenceSimple()
+ * @see cxListDifferenceShallow()
  */
 cx_attr_nonnull_arg(1, 2, 3, 4)
 CX_EXPORT int cxListDifference(CxList *dst,
@@ -1031,7 +1037,7 @@
  * @param data optional additional data that is passed to the clone function
  * @retval zero when the elements were successfully cloned
  * @retval non-zero when an allocation error occurred
- * @see cxListIntersectionSimple()
+ * @see cxListIntersectionShallow()
  */
 cx_attr_nonnull_arg(1, 2, 3, 4)
 CX_EXPORT int cxListIntersection(CxList *dst, const CxList *src, const CxList *other,
@@ -1056,7 +1062,7 @@
  * @param data optional additional data that is passed to the clone function
  * @retval zero when the elements were successfully cloned
  * @retval non-zero when an allocation error occurred
- * @see cxListUnionSimple()
+ * @see cxListUnionShallow()
  */
 cx_attr_nonnull_arg(1, 2, 3, 4)
 CX_EXPORT int cxListUnion(CxList *dst, const CxList *src, const CxList *other,
@@ -1082,7 +1088,7 @@
  * @see cxListClone()
  */
 cx_attr_nonnull
-CX_EXPORT int cxListCloneSimple(CxList *dst, const CxList *src);
+CX_EXPORT int cxListCloneShallow(CxList *dst, const CxList *src);
 
 /**
  * Clones elements from a list only if they are not present in another list.
@@ -1104,7 +1110,7 @@
  * @see cxListDifference()
  */
 cx_attr_nonnull
-CX_EXPORT int cxListDifferenceSimple(CxList *dst,
+CX_EXPORT int cxListDifferenceShallow(CxList *dst,
         const CxList *minuend, const CxList *subtrahend);
 
 /**
@@ -1127,7 +1133,7 @@
  * @see cxListIntersection()
  */
 cx_attr_nonnull
-CX_EXPORT int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other);
+CX_EXPORT int cxListIntersectionShallow(CxList *dst, const CxList *src, const CxList *other);
 
 /**
  * Performs a deep clone of one list into another, skipping duplicates.
@@ -1151,7 +1157,7 @@
  * @see cxListUnion()
  */
 cx_attr_nonnull
-CX_EXPORT int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other);
+CX_EXPORT int cxListUnionShallow(CxList *dst, const CxList *src, const CxList *other);
 
 /**
  * Asks the list to reserve enough memory for a given total number of elements.
--- a/ucx/cx/map.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/map.h	Thu Dec 18 17:50:15 2025 +0100
@@ -183,9 +183,9 @@
      * Add or overwrite an element.
      * If the @p value is @c NULL, the implementation
      * shall only allocate memory instead of adding an existing value to the map.
-     * Returns a pointer to the allocated memory or @c NULL if allocation fails.
+     * Returns a map entry where the pointer to the key is @c NULL if allocation fails.
      */
-    void *(*put)(CxMap *map, CxHashKey key, void *value);
+    CxMapEntry (*put)(CxMap *map, CxHashKey key, void *value);
 
     /**
      * Returns an element.
@@ -357,8 +357,6 @@
  * @param map the map
  * @param key the key
  * @return the pointer to the allocated memory or @c NULL if allocation fails
- * @retval zero success
- * @retval non-zero value on memory allocation failure
  * @see cxMapEmplace()
  */
 cx_attr_nonnull
@@ -379,8 +377,6 @@
  * @param map (@c CxMap*) the map
  * @param key (any supported key type) the key
  * @return the pointer to the allocated memory or @c NULL if allocation fails
- * @retval zero success
- * @retval non-zero value on memory allocation failure
  * @see CX_HASH_KEY()
  */
 #define cxMapEmplace(map, key) cx_map_emplace(map, CX_HASH_KEY(key))
@@ -627,7 +623,7 @@
  * @see cxMapClone()
  */
 cx_attr_nonnull
-CX_EXPORT int cxMapCloneSimple(CxMap *dst, const CxMap *src);
+CX_EXPORT int cxMapCloneShallow(CxMap *dst, const CxMap *src);
 
 /**
  * Clones entries of a map if their key is not present in another map.
@@ -642,7 +638,7 @@
  * @retval non-zero when an allocation error occurred
  */
 cx_attr_nonnull
-CX_EXPORT int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend);
+CX_EXPORT int cxMapDifferenceShallow(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend);
 
 /**
  * Clones entries of a map if their key is not present in a list.
@@ -663,7 +659,7 @@
  * @see cxMapListDifference()
  */
 cx_attr_nonnull
-CX_EXPORT int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys);
+CX_EXPORT int cxMapListDifferenceShallow(CxMap *dst, const CxMap *src, const CxList *keys);
 
 
 /**
@@ -679,7 +675,7 @@
  * @retval non-zero when an allocation error occurred
  */
 cx_attr_nonnull
-CX_EXPORT int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other);
+CX_EXPORT int cxMapIntersectionShallow(CxMap *dst, const CxMap *src, const CxMap *other);
 
 /**
  * Clones entries of a map only if their key is present in a list.
@@ -699,7 +695,7 @@
  * @retval non-zero when an allocation error occurred
  */
 cx_attr_nonnull
-CX_EXPORT int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys);
+CX_EXPORT int cxMapListIntersectionShallow(CxMap *dst, const CxMap *src, const CxList *keys);
 
 /**
  * Clones entries into a map if their key does not exist yet.
@@ -718,7 +714,23 @@
  * @retval non-zero when an allocation error occurred
  */
 cx_attr_nonnull
-CX_EXPORT int cxMapUnionSimple(CxMap *dst, const CxMap *src);
+CX_EXPORT int cxMapUnionShallow(CxMap *dst, const CxMap *src);
+
+
+/**
+ * Compares the entries of two maps.
+ *
+ * @param map the map
+ * @param other the other map that the first map is compared to
+ * @retval zero when both maps have the same key sets
+ * and the values are pairwise equivalent
+ * @retval negative when the first @p map has fewer keys than the @p other map
+ * @retval positive when the first @p map has more keys than the @p other map
+ * @retval non-zero (unspecified whether positive or negative) when the size
+ * of both maps is equal but a key or a value is different
+ */
+cx_attr_nonnull
+CX_EXPORT int cxMapCompare(const CxMap *map, const CxMap *other);
 
 #ifdef    __cplusplus
 } // extern "C"
--- a/ucx/cx/properties.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/properties.h	Thu Dec 18 17:50:15 2025 +0100
@@ -73,7 +73,7 @@
      */
     char comment3;
 
-    /*
+    /**
      * The character, when appearing at the end of a line, continues that line.
      * This is '\' by default.
      */
@@ -336,15 +336,15 @@
 /**
  * Internal function - use cxPropertiesLoad() instead.
  *
- * @param config the parser config
  * @param allocator the allocator for the values
  * @param filename the file name
  * @param target the target map
+ * @param config the parser config
  * @return status code
  */
-cx_attr_nonnull_arg(4)
-CX_EXPORT CxPropertiesStatus cx_properties_load(CxPropertiesConfig config,
-        const CxAllocator *allocator, cxstring filename, CxMap *target);
+cx_attr_nonnull_arg(3)
+CX_EXPORT CxPropertiesStatus cx_properties_load(const CxAllocator *allocator,
+        cxstring filename, CxMap *target, CxPropertiesConfig config);
 
 /**
  * Loads properties from a file and inserts them into a map.
@@ -357,10 +357,10 @@
  * @note When the parser finds an error, all successfully parsed keys before the error
  * are added to the map nonetheless.
  *
- * @param config the parser config
  * @param allocator the allocator for the values that will be stored in the map
  * @param filename (any string) the absolute or relative path to the file
  * @param target (@c CxMap*) the map where the properties shall be added
+ * @param config the parser config
  * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found
  * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties
  * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file
@@ -371,8 +371,8 @@
  * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed
  * @see cxPropertiesLoadDefault()
  */
-#define cxPropertiesLoad(config, allocator, filename, target) \
-    cx_properties_load(config, allocator, cx_strcast(filename), target)
+#define cxPropertiesLoad(allocator, filename, target, config) \
+    cx_properties_load(allocator, cx_strcast(filename), target, config)
 
 /**
  * Loads properties from a file and inserts them into a map with a default config.
@@ -399,7 +399,7 @@
  * @see cxPropertiesLoad()
  */
 #define cxPropertiesLoadDefault(allocator, filename, target) \
-    cx_properties_load(cx_properties_config_default, allocator, cx_strcast(filename), target)
+    cx_properties_load(allocator, cx_strcast(filename), target, cx_properties_config_default)
 
 
 #ifdef __cplusplus
--- a/ucx/cx/streams.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/streams.h	Thu Dec 18 17:50:15 2025 +0100
@@ -36,8 +36,8 @@
  * @copyright 2-Clause BSD License
  */
 
-#ifndef UCX_STREAMS_H
-#define UCX_STREAMS_H
+#ifndef Ucx_strEAMS_H
+#define Ucx_strEAMS_H
 
 #include "common.h"
 
@@ -117,4 +117,4 @@
 }
 #endif
 
-#endif // UCX_STREAMS_H
+#endif // Ucx_strEAMS_H
--- a/ucx/cx/string.h	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/cx/string.h	Thu Dec 18 17:50:15 2025 +0100
@@ -33,12 +33,14 @@
  * @copyright 2-Clause BSD License
  */
 
-#ifndef UCX_STRING_H
-#define UCX_STRING_H
+#ifndef Ucx_strING_H
+#define Ucx_strING_H
 
 #include "common.h"
 #include "allocator.h"
 
+#include <string.h>
+
 /** Expands a UCX string as printf arguments. */
 #define CX_SFMT(s) (int) (s).length, (s).ptr
 
@@ -137,30 +139,6 @@
  */
 typedef struct cx_strtok_ctx_s CxStrtokCtx;
 
-#ifdef __cplusplus
-extern "C" {
-
-/**
- * A literal initializer for an UCX string structure.
- *
- * @param literal the string literal
- */
-#define CX_STR(literal) cxstring{literal, sizeof(literal) - 1}
-
-#else // __cplusplus
-
-/**
- * A literal initializer for an UCX string structure.
- *
- * The argument MUST be a string (const char*) @em literal.
- *
- * @param literal the string literal
- */
-#define CX_STR(literal) ((cxstring){literal, sizeof(literal) - 1})
-
-#endif
-
-
 /**
  * Wraps a mutable string that must be zero-terminated.
  *
@@ -179,7 +157,12 @@
  * @see cx_mutstrn()
  */
 cx_attr_nodiscard cx_attr_cstr_arg(1)
-CX_EXPORT cxmutstr cx_mutstr(char *cstring);
+CX_INLINE cxmutstr cx_mutstr(char *cstring) {
+    cxmutstr str;
+    str.ptr = cstring;
+    str.length = cstring == NULL ? 0 : strlen(cstring);
+    return str;
+}
 
 /**
  * Wraps a string that does not need to be zero-terminated.
@@ -198,7 +181,12 @@
  * @see cx_mutstr()
  */
 cx_attr_nodiscard cx_attr_access_rw(1, 2)
-CX_EXPORT cxmutstr cx_mutstrn(char *cstring, size_t length);
+CX_INLINE cxmutstr cx_mutstrn(char *cstring, size_t length) {
+    cxmutstr str;
+    str.ptr = cstring;
+    str.length = length;
+    return str;
+}
 
 /**
  * Wraps a string that must be zero-terminated.
@@ -218,7 +206,12 @@
  * @see cx_strn()
  */
 cx_attr_nodiscard cx_attr_cstr_arg(1)
-CX_EXPORT cxstring cx_str(const char *cstring);
+CX_INLINE cxstring cx_str(const char *cstring) {
+    cxstring str;
+    str.ptr = cstring;
+    str.length = cstring == NULL ? 0 : strlen(cstring);
+    return str;
+}
 
 
 /**
@@ -238,10 +231,14 @@
  * @see cx_str()
  */
 cx_attr_nodiscard cx_attr_access_r(1, 2)
-CX_EXPORT cxstring cx_strn(const char *cstring, size_t length);
+CX_INLINE cxstring cx_strn(const char *cstring, size_t length) {
+    cxstring str;
+    str.ptr = cstring;
+    str.length = length;
+    return str;
+}
 
 #ifdef __cplusplus
-} // extern "C"
 cx_attr_nodiscard
 CX_CPPDECL cxstring cx_strcast(cxmutstr str) {
     return cx_strn(str.ptr, str.length);
@@ -2087,4 +2084,4 @@
 } // extern "C"
 #endif
 
-#endif //UCX_STRING_H
+#endif //Ucx_strING_H
--- a/ucx/hash_map.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/hash_map.c	Thu Dec 18 17:50:15 2025 +0100
@@ -78,7 +78,7 @@
     cxFree(map->collection.allocator, map);
 }
 
-static void *cx_hash_map_put(
+static CxMapEntry cx_hash_map_put(
         CxMap *map,
         CxHashKey key,
         void *value
@@ -117,7 +117,7 @@
                 allocator,
                 sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
         );
-        if (e == NULL) return NULL; // LCOV_EXCL_LINE
+        if (e == NULL) return (CxMapEntry){NULL, NULL}; // LCOV_EXCL_LINE
 
         // write the value
         if (value == NULL) {
@@ -132,7 +132,7 @@
         void *kd = cxMalloc(allocator, key.len);
         if (kd == NULL) { // LCOV_EXCL_START
             cxFree(allocator, e);
-            return NULL;
+            return (CxMapEntry){NULL, NULL};
         } // LCOV_EXCL_STOP
         memcpy(kd, key.data, key.len);
         e->key.data = kd;
@@ -152,8 +152,8 @@
         map->collection.size++;
     }
 
-    // return pointer to the element
-    return elm->data;
+    // return the entry
+    return (CxMapEntry){&elm->key, elm->data};
 }
 
 static void cx_hash_map_unlink(
@@ -414,8 +414,7 @@
         buckets = 16;
     }
 
-    struct cx_hash_map_s *map = cxCalloc(allocator, 1,
-                                         sizeof(struct cx_hash_map_s));
+    struct cx_hash_map_s *map = cxZalloc(allocator, sizeof(struct cx_hash_map_s));
     if (map == NULL) return NULL;
 
     // initialize hash map members
@@ -433,9 +432,12 @@
 
     if (itemsize > 0) {
         map->base.collection.elem_size = itemsize;
+        map->base.collection.advanced_cmp = cx_acmp_memcmp;
+        map->base.collection.cmp_data = &map->base.collection.elem_size;
     } else {
         map->base.collection.elem_size = sizeof(void *);
         map->base.collection.store_pointer = true;
+        map->base.collection.simple_cmp = cx_cmp_ptr;
     }
 
     return (CxMap *) map;
--- a/ucx/iterator.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/iterator.c	Thu Dec 18 17:50:15 2025 +0100
@@ -29,6 +29,7 @@
 #include "cx/iterator.h"
 
 #include <string.h>
+#include <assert.h>
 
 static bool cx_iter_valid(const void *it) {
     const struct cx_iterator_s *iter = it;
@@ -45,51 +46,14 @@
     return *(void**)iter->elem_handle;
 }
 
-static void cx_iter_next_fast(void *it) {
+static void cx_iter_next(void *it) {
     struct cx_iterator_s *iter = it;
-    if (iter->base.remove) {
-        iter->base.remove = false;
-        iter->elem_count--;
-        // only move the last element when we are not currently aiming
-        // at the last element already
-        if (iter->index < iter->elem_count) {
-            void *last = ((char *) iter->src_handle)
-                         + iter->elem_count * iter->elem_size;
-            memcpy(iter->elem_handle, last, iter->elem_size);
-        }
-    } else {
-        iter->index++;
-        iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
-    }
+    assert(!iter->base.remove);
+    iter->index++;
+    iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
 }
 
-static void cx_iter_next_slow(void *it) {
-    struct cx_iterator_s *iter = it;
-    if (iter->base.remove) {
-        iter->base.remove = false;
-        iter->elem_count--;
-
-        // number of elements to move
-        size_t remaining = iter->elem_count - iter->index;
-        if (remaining > 0) {
-            memmove(
-                    iter->elem_handle,
-                    ((char *) iter->elem_handle) + iter->elem_size,
-                    remaining * iter->elem_size
-            );
-        }
-    } else {
-        iter->index++;
-        iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
-    }
-}
-
-CxIterator cxIterator(
-        const void *array,
-        size_t elem_size,
-        size_t elem_count,
-        bool remove_keeps_order
-) {
+CxIterator cxIterator(const void *array, size_t elem_size, size_t elem_count) {
     CxIterator iter;
 
     iter.index = 0;
@@ -99,19 +63,18 @@
     iter.elem_count = array == NULL ? 0 : elem_count;
     iter.base.valid = cx_iter_valid;
     iter.base.current = cx_iter_current;
-    iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast;
+    iter.base.next = cx_iter_next;
+    iter.base.valid_impl = NULL;
+    iter.base.current_impl = NULL;
+    iter.base.next_impl = NULL;
     iter.base.remove = false;
     iter.base.allow_remove = true;
 
     return iter;
 }
 
-CxIterator cxIteratorPtr(
-        const void *array,
-        size_t elem_count,
-        bool remove_keeps_order
-) {
-    CxIterator iter = cxIterator(array, sizeof(void*), elem_count, remove_keeps_order);
+CxIterator cxIteratorPtr(const void *array, size_t elem_count) {
+    CxIterator iter = cxIterator(array, sizeof(void*), elem_count);
     iter.base.current = cx_iter_current_ptr;
     return iter;
 }
--- a/ucx/json.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/json.c	Thu Dec 18 17:50:15 2025 +0100
@@ -111,8 +111,8 @@
         ttype = CX_JSON_TOKEN_STRING;
     } else {
         cxstring s = cx_strcast(str);
-        if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false"))
-            || !cx_strcmp(s, CX_STR("null"))) {
+        if (!cx_strcmp(s, "true") || !cx_strcmp(s, "false")
+            || !cx_strcmp(s, "null")) {
             ttype = CX_JSON_TOKEN_LITERAL;
         } else {
             ttype = token_numbertype(str.ptr, str.length);
@@ -410,7 +410,7 @@
             size_t capa = str.length + 32;
             char *space = cxMallocDefault(capa);
             if (space == NULL) return cx_mutstrn(NULL, 0);
-            cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND);
+            cxBufferInit(&buf, NULL, space, capa, CX_BUFFER_AUTO_EXTEND);
             cxBufferWrite(str.ptr, 1, i, &buf);
             all_printable = false;
         }
@@ -453,10 +453,10 @@
 }
 
 static CxJsonObject json_create_object_map(const CxAllocator *allocator) {
-    // TODO: we might want to add a comparator that is sorting the elements by their key
-    CxMap *map = cxKvListCreateAsMap(allocator, NULL, CX_STORE_POINTERS);
+    CxMap *map = cxKvListCreateAsMap(allocator, CX_STORE_POINTERS);
     if (map == NULL) return NULL; // LCOV_EXCL_LINE
-    cxDefineDestructor(map, cxJsonValueFree);
+    cxSetCompareFunc(map, cxJsonCompare);
+    cxSetDestructor(map, cxJsonValueFree);
     return map;
 }
 
@@ -472,20 +472,20 @@
     v->type = type;
     v->allocator = json->allocator;
     if (type == CX_JSON_ARRAY) {
-        cx_array_initialize_a(json->allocator, v->array.data, 16);
-        if (v->array.data == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE
+        if (cx_array_init_a(json->allocator, v->array, 16)) {
+            goto create_json_value_exit_error; // LCOV_EXCL_LINE
+        }
     } else if (type == CX_JSON_OBJECT) {
         v->object = json_create_object_map(json->allocator);
         if (v->object == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE
     }
 
     // add the new value to a possible parent
-    if (json->vbuf_size > 0) {
-        CxJsonValue *parent = json->vbuf[json->vbuf_size - 1];
+    if (json->vbuf.size > 0) {
+        CxJsonValue *parent = json->vbuf.data[json->vbuf.size - 1];
         assert(parent != NULL);
         if (parent->type == CX_JSON_ARRAY) {
-            CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL);
-            if (cx_array_simple_add_a(&value_realloc, parent->array.data, v)) {
+            if (cx_array_add_a(json->allocator, parent->array, &v)) {
                 goto create_json_value_exit_error; // LCOV_EXCL_LINE
             }
         } else if (parent->type == CX_JSON_OBJECT) {
@@ -503,10 +503,19 @@
 
     // add the new value to the stack, if it is an array or object
     if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) {
-        CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal);
-        if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) {
-            goto create_json_value_exit_error; // LCOV_EXCL_LINE
+        if (json->vbuf.size >= json->vbuf.capacity) {
+            int alloc_error;
+            if (json->vbuf.data == json->vbuf_internal) {
+                alloc_error = cx_array_copy_to_new(json->vbuf, json->vbuf.size+1);
+            } else {
+                alloc_error = cx_array_reserve(json->vbuf, json->vbuf.size+1);
+            }
+            if (alloc_error) {
+                goto create_json_value_exit_error; // LCOV_EXCL_LINE
+            }
         }
+        json->vbuf.data[json->vbuf.size] = v;
+        json->vbuf.size++;
     }
 
     // if currently no value is parsed, this is now the value of interest
@@ -540,22 +549,18 @@
     memset(json, 0, sizeof(CxJson));
     json->allocator = allocator;
 
-    json->states = json->states_internal;
-    json->states_capacity = cx_nmemb(json->states_internal);
-    json->states[0] = JP_STATE_VALUE_BEGIN;
-    json->states_size = 1;
-
-    json->vbuf = json->vbuf_internal;
-    json->vbuf_capacity = cx_nmemb(json->vbuf_internal);
+    cx_array_init_fixed(json->states, json->states_internal, 1);
+    json->states.data[0] = JP_STATE_VALUE_BEGIN;
+    cx_array_init_fixed(json->vbuf, json->vbuf_internal, 0);
 }
 
 void cxJsonDestroy(CxJson *json) {
     cxBufferDestroy(&json->buffer);
-    if (json->states != json->states_internal) {
-        cxFreeDefault(json->states);
+    if (json->states.data != json->states_internal) {
+        cx_array_free(json->states);
     }
-    if (json->vbuf != json->vbuf_internal) {
-        cxFreeDefault(json->vbuf);
+    if (json->vbuf.data != json->vbuf_internal) {
+        cx_array_free(json->vbuf);
     }
     cxJsonValueFree(json->parsed);
     json->parsed = NULL;
@@ -574,8 +579,8 @@
         // reinitialize the buffer
         cxBufferDestroy(&json->buffer);
         if (buf == NULL) buf = ""; // buffer must not be initialized with NULL
-        cxBufferInit(&json->buffer, (char*) buf, size,
-            NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE);
+        cxBufferInit(&json->buffer, NULL, (char*) buf,
+                     size, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE);
         json->buffer.size = size;
         return 0;
     } else {
@@ -584,9 +589,9 @@
 }
 
 static void json_add_state(CxJson *json, int state) {
-    // we have guaranteed the necessary space with cx_array_simple_reserve()
+    // we have guaranteed the necessary space
     // therefore, we can safely add the state in the simplest way possible
-    json->states[json->states_size++] = state;
+    json->states.data[json->states.size++] = state;
 }
 
 #define return_rec(code) \
@@ -607,13 +612,21 @@
     }
 
     // pop the current state
-    assert(json->states_size > 0);
-    int state = json->states[--json->states_size];
+    assert(json->states.size > 0);
+    int state = json->states.data[--json->states.size];
 
-    // guarantee that at least two more states fit on the stack
-    CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal);
-    if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) {
-        return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+    // guarantee that at least two more states fit into the array
+    const size_t required_states_depth = json->states.size + 2;
+    if (required_states_depth >= json->states.capacity) {
+        int alloc_error;
+        if (json->states.data == json->states_internal) {
+            alloc_error = cx_array_copy_to_new(json->states, required_states_depth);
+        } else {
+            alloc_error = cx_array_reserve(json->states, required_states_depth);
+        }
+        if (alloc_error) {
+            return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+        }
     }
 
 
@@ -645,6 +658,16 @@
                 json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE);
                 return_rec(CX_JSON_NO_ERROR);
             }
+            case CX_JSON_TOKEN_END_ARRAY: {
+                if (state == JP_STATE_VALUE_BEGIN_AR) {
+                    // discard the array from the value buffer
+                    json->vbuf.size--;
+                    json->states.size--;
+                    return_rec(CX_JSON_NO_ERROR);
+                } else {
+                    return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+                }
+            }
             case CX_JSON_TOKEN_STRING: {
                 if ((vbuf = json_create_value(json, CX_JSON_STRING)) == NULL) {
                     return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
@@ -678,9 +701,9 @@
                 if ((vbuf = json_create_value(json, CX_JSON_LITERAL)) == NULL) {
                     return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
                 }
-                if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) {
+                if (0 == cx_strcmp(token.content, "true")) {
                     vbuf->literal = CX_JSON_TRUE;
-                } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) {
+                } else if (0 == cx_strcmp(token.content, "false")) {
                     vbuf->literal = CX_JSON_FALSE;
                 } else {
                     vbuf->literal = CX_JSON_NULL;
@@ -698,7 +721,7 @@
             return_rec(CX_JSON_NO_ERROR);
         } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) {
             // discard the array from the value buffer
-            json->vbuf_size--;
+            json->vbuf.size--;
             return_rec(CX_JSON_NO_ERROR);
         } else {
             return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
@@ -706,7 +729,7 @@
     } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) {
         if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) {
             // discard the obj from the value buffer
-            json->vbuf_size--;
+            json->vbuf.size--;
             return_rec(CX_JSON_NO_ERROR);
         } else {
             // expect string
@@ -721,7 +744,7 @@
             }
             assert(json->uncompleted_member_name.ptr == NULL);
             json->uncompleted_member_name = name;
-            assert(json->vbuf_size > 0);
+            assert(json->vbuf.size > 0);
 
             // next state
             json_add_state(json, JP_STATE_OBJ_COLON);
@@ -742,7 +765,7 @@
             return_rec(CX_JSON_NO_ERROR);
         } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) {
             // discard the obj from the value buffer
-            json->vbuf_size--;
+            json->vbuf.size--;
             return_rec(CX_JSON_NO_ERROR);
         } else {
             return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
@@ -767,17 +790,17 @@
     CxJsonStatus result;
     do {
         result = json_parse(json);
-        if (result == CX_JSON_NO_ERROR && json->states_size == 1) {
+        if (result == CX_JSON_NO_ERROR && json->states.size == 1) {
             // final state reached
-            assert(json->states[0] == JP_STATE_VALUE_END);
-            assert(json->vbuf_size == 0);
+            assert(json->states.data[0] == JP_STATE_VALUE_END);
+            assert(json->vbuf.size == 0);
 
             // write output value
             *value = json->parsed;
             json->parsed = NULL;
 
             // re-initialize state machine
-            json->states[0] = JP_STATE_VALUE_BEGIN;
+            json->states.data[0] = JP_STATE_VALUE_BEGIN;
 
             return CX_JSON_NO_ERROR;
         }
@@ -786,7 +809,7 @@
     // the parser might think there is no data
     // but when we did not reach the final state,
     // we know that there must be more to come
-    if (result == CX_JSON_NO_DATA && json->states_size > 1) {
+    if (result == CX_JSON_NO_DATA && json->states.size > 1) {
         return CX_JSON_INCOMPLETE_DATA;
     }
 
@@ -832,11 +855,10 @@
             break;
         }
         case CX_JSON_ARRAY: {
-            CxJsonArray array = value->array;
-            for (size_t i = 0; i < array.data_size; i++) {
-                cxJsonValueFree(array.data[i]);
+            for (size_t i = 0; i < value->array.size; i++) {
+                cxJsonValueFree(value->array.data[i]);
             }
-            cxFree(value->allocator, array.data);
+            cx_array_free_a(value->allocator, value->array);
             break;
         }
         case CX_JSON_STRING: {
@@ -865,14 +887,23 @@
     return v;
 }
 
-CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) {
+CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator, size_t capacity) {
     if (allocator == NULL) allocator = cxDefaultAllocator;
     CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
     if (v == NULL) return NULL;
     v->allocator = allocator;
     v->type = CX_JSON_ARRAY;
-    cx_array_initialize_a(allocator, v->array.data, 16);
-    if (v->array.data == NULL) { cxFree(allocator, v); return NULL; }
+    if (capacity > 0) {
+        if (cx_array_init_a(allocator, v->array, capacity)) {
+            // LCOV_EXCL_START
+            cxFree(allocator, v);
+            return NULL;
+            // LCOV_EXCL_STOP
+        }
+    } else {
+        v->array.data = NULL;
+        v->array.size = v->array.capacity = 0;
+    }
     return v;
 }
 
@@ -990,13 +1021,8 @@
 }
 
 int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) {
-    CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL);
     assert(arr->type == CX_JSON_ARRAY);
-    return cx_array_simple_copy_a(&value_realloc,
-            arr->array.data,
-            arr->array.data_size,
-            val, count
-    );
+    return cx_array_add_array_a(arr->allocator, arr->array, val, count);
 }
 
 int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) {
@@ -1010,8 +1036,8 @@
     return v;
 }
 
-CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name) {
-    CxJsonValue* v = cxJsonCreateArr(obj->allocator);
+CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name, size_t capacity) {
+    CxJsonValue* v = cxJsonCreateArr(obj->allocator, capacity);
     if (v == NULL) return NULL;
     if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
     return v;
@@ -1046,23 +1072,23 @@
 }
 
 CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) {
-    if (index >= value->array.data_size) {
+    if (index >= value->array.size) {
         return &cx_json_value_nothing;
     }
     return value->array.data[index];
 }
 
 CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) {
-    if (index >= value->array.data_size) {
+    if (index >= value->array.size) {
         return NULL;
     }
     CxJsonValue *ret = value->array.data[index];
     // TODO: replace with a low level cx_array_remove()
-    size_t count = value->array.data_size - index - 1;
+    size_t count = value->array.size - index - 1;
     if (count > 0) {
         memmove(value->array.data + index, value->array.data + index + 1, count * sizeof(CxJsonValue*));
     }
-    value->array.data_size--;
+    value->array.size--;
     return ret;
 }
 
@@ -1095,11 +1121,7 @@
 }
 
 CxIterator cxJsonArrIter(const CxJsonValue *value) {
-    return cxIteratorPtr(
-        value->array.data,
-        value->array.data_size,
-        true // arrays need to keep order
-    );
+    return cx_array_iterator_ptr(value->array);
 }
 
 CxMapIterator cxJsonObjIter(const CxJsonValue *value) {
@@ -1413,8 +1435,8 @@
 static cxmutstr cx_json_to_string(CxJsonValue *value, const CxAllocator *allocator, CxJsonWriter *writer) {
     if (allocator == NULL) allocator = cxDefaultAllocator;
     CxBuffer buffer;
-    if (cxBufferInit(&buffer, NULL, 128, allocator,
-        CX_BUFFER_AUTO_EXTEND | CX_BUFFER_DO_NOT_FREE)) {
+    if (cxBufferInit(&buffer, allocator, NULL, 128,
+                     CX_BUFFER_AUTO_EXTEND | CX_BUFFER_DO_NOT_FREE)) {
         return (cxmutstr){NULL, 0};
     }
     if (cx_json_write_rec(&buffer, value, cxBufferWriteFunc, writer, 0)
@@ -1432,12 +1454,121 @@
 
 }
 
-cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator) {
+cxmutstr cxJsonToString(const CxAllocator *allocator, CxJsonValue *value) {
     CxJsonWriter writer = cxJsonWriterCompact();
     return cx_json_to_string(value, allocator, &writer);
 }
 
-cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator) {
+cxmutstr cxJsonToPrettyString(const CxAllocator *allocator, CxJsonValue *value) {
     CxJsonWriter writer = cxJsonWriterPretty(true);
     return cx_json_to_string(value, allocator, &writer);
 }
+
+int cxJsonCompare(const CxJsonValue *json, const CxJsonValue *other) {
+    if (json == other) return 0;
+    if (json == NULL || other == NULL) return -1;
+    if (json->type != other->type) {
+        if (!cxJsonIsNumber(json)) return -1;
+        if (!cxJsonIsNumber(other)) return -1;
+    }
+    switch (json->type) {
+        case CX_JSON_NOTHING:
+            return 0;
+        case CX_JSON_OBJECT:
+            return cxMapCompare(json->object, other->object);
+        case CX_JSON_ARRAY:
+            if (json->array.size != other->array.size) return -1;
+            for (size_t i = 0; i < json->array.size; i++) {
+                const int d = cxJsonCompare(json->array.data[i], other->array.data[i]);
+                if (d != 0) return d;
+            }
+            return 0;
+        case CX_JSON_STRING:
+            return cx_strcmp(json->string, other->string);
+        case CX_JSON_INTEGER:
+            if (other->type == CX_JSON_INTEGER) {
+                return cx_vcmp_int64(json->integer, other->integer);
+            } else {
+                return cx_vcmp_double(cxJsonAsDouble(json), other->number);
+            }
+        case CX_JSON_NUMBER:
+            return cx_vcmp_double(json->number, cxJsonAsDouble(other));
+        case CX_JSON_LITERAL:
+            return json->literal == other->literal ? 0 : -1;
+        default:
+            // LCOV_EXCL_START
+            // unreachable
+            assert(false);
+            return -1;
+            // LCOV_EXCL_STOP
+    }
+}
+
+CxJsonValue* cxJsonClone(const CxJsonValue* value, const CxAllocator* allocator) {
+    return cx_json_clone_func(NULL, value, allocator, NULL);
+}
+
+CxJsonValue* cx_json_clone_func(CxJsonValue* target, const CxJsonValue* source,
+        const CxAllocator* allocator, cx_attr_unused void *data) {
+    if (source == NULL || source->type == CX_JSON_NOTHING) {
+        return &cx_json_value_nothing;
+    }
+    if (allocator == NULL) allocator = cxDefaultAllocator;
+
+#define return_value(v) { \
+        CxJsonValue *ret = v; \
+        if (target == NULL) { \
+            return ret; \
+        } else { \
+            *target = *ret; \
+            cxFree(allocator, ret); \
+            return target; \
+        } \
+    }
+
+    switch (source->type) {
+        case CX_JSON_OBJECT: {
+            CxJsonValue *obj = cxJsonCreateObj(allocator);
+            if (obj == NULL) return NULL; // LCOV_EXCL_LINE
+            if (cxMapClone(obj->object, source->object, cxJsonCloneFunc, allocator, NULL)) {
+                // LCOV_EXCL_START
+                cxJsonValueFree(obj);
+                return NULL;
+                // LCOV_EXCL_STOP
+            }
+            return_value(obj);
+        }
+        case CX_JSON_ARRAY: {
+            const size_t elem_count = source->array.size;
+            CxJsonValue *arr = cxJsonCreateArr(allocator, elem_count);
+            if (arr == NULL) return NULL; // LCOV_EXCL_LINE
+            arr->array.size = elem_count;
+            for (size_t i = 0 ; i < elem_count ; i++) {
+                CxJsonValue *e = cx_json_clone_func(NULL, source->array.data[i], allocator, NULL);
+                if (e == NULL) {
+                    // LCOV_EXCL_START
+                    cxJsonValueFree(arr);
+                    return NULL;
+                    // LCOV_EXCL_STOP
+                }
+                arr->array.data[i] = e;
+            }
+            return_value(arr);
+        }
+        case CX_JSON_STRING:
+            return_value(cxJsonCreateString(allocator, source->string));
+        case CX_JSON_INTEGER:
+            return_value(cxJsonCreateInteger(allocator, source->integer));
+        case CX_JSON_NUMBER:
+            return_value(cxJsonCreateNumber(allocator, source->number));
+        case CX_JSON_LITERAL:
+            return_value(cxJsonCreateLiteral(allocator, source->literal));
+        default:
+            // LCOV_EXCL_START
+            // unreachable
+            assert(false);
+            return NULL;
+            // LCOV_EXCL_STOP
+    }
+#undef return_value
+}
--- a/ucx/kv_list.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/kv_list.c	Thu Dec 18 17:50:15 2025 +0100
@@ -221,11 +221,12 @@
 
     size_t index;
     cx_linked_list *ll = &kv_list->list;
-    char *node = cx_linked_list_find(
+    char *node = cx_linked_list_find_c(
             ll->begin,
             ll->loc_next, ll->loc_data,
-            list->collection.cmpfunc, elem,
-            &index
+            elem, &index,
+            cx_list_compare_wrapper,
+            list
     );
     if (node == NULL) {
         return list->collection.size;
@@ -348,7 +349,7 @@
     return 0;
 }
 
-static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) {
+static CxMapEntry cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) {
     cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list;
     // if the hash has not yet been computed, do it now
     if (key.hash == 0) {
@@ -359,8 +360,8 @@
     cx_kvl_map_remove(map, key, NULL);
 
     // now reserve new memory in the map
-    void **map_data = kv_list->map_methods->put(map, key, NULL);
-    if (map_data == NULL) return NULL; // LCOV_EXCL_LINE
+    CxMapEntry map_entry = kv_list->map_methods->put(map, key, NULL);
+    if (map_entry.key == NULL) return (CxMapEntry){NULL, NULL}; // LCOV_EXCL_LINE
 
     // insert the data into the list (which most likely destroys the sorted property)
     kv_list->list.base.collection.sorted = false;
@@ -369,20 +370,20 @@
         kv_list->list.base.collection.store_pointer ? &value : value);
     if (node_data == NULL) { // LCOV_EXCL_START
         // non-destructively remove the key again
-        kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data);
-        return NULL;
+        void *dummy;
+        kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy);
+        return (CxMapEntry){NULL, NULL};
     } // LCOV_EXCL_STOP
 
     // write the node pointer to the map entry
-    *map_data = node_data;
+    *(void**)map_entry.value = node_data;
 
     // copy the key to the node data
     CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data);
-    *key_ptr = key;
+    *key_ptr = *map_entry.key;
 
-    // we must return node_data here and not map_data,
-    // because the node_data is the actual element of this collection
-    return node_data;
+    // we must return an entry that points to the node data!
+    return (CxMapEntry ){key_ptr, node_data};
 }
 
 static void *cx_kvl_iter_current_entry(const void *it) {
@@ -552,7 +553,6 @@
 
 CxList *cxKvListCreate(
         const CxAllocator *allocator,
-        cx_compare_func comparator,
         size_t elem_size
 ) {
     if (allocator == NULL) {
@@ -560,7 +560,7 @@
     }
 
     // create a normal linked list and a normal hash map, first
-    CxList *list = cxLinkedListCreate(allocator, comparator, elem_size);
+    CxList *list = cxLinkedListCreate(allocator, elem_size);
     if (list == NULL) return NULL; // LCOV_EXCL_LINE
     cx_linked_list *ll = (cx_linked_list*)list;
     cx_linked_list_extra_data(ll, sizeof(CxHashKey));
@@ -600,23 +600,17 @@
     // remember the base methods and override them
     kv_list->map_methods = map->cl;
     map->cl = &cx_kv_map_class;
-    if (list->climpl == NULL) {
-        kv_list->list_methods = list->cl;
-        list->cl = &cx_kv_list_class;
-    } else {
-        kv_list->list_methods = list->climpl;
-        list->climpl = &cx_kv_list_class;
-    }
+    kv_list->list_methods = list->cl;
+    list->cl = &cx_kv_list_class;
 
     return list;
 }
 
 CxMap *cxKvListCreateAsMap(
         const CxAllocator *allocator,
-        cx_compare_func comparator,
         size_t elem_size
 ) {
-    CxList *list = cxKvListCreate(allocator, comparator, elem_size);
+    CxList *list = cxKvListCreate(allocator, elem_size);
     return list == NULL ? NULL : cxKvListAsMap(list);
 }
 
@@ -649,14 +643,14 @@
         return 1;
     }
 
-    // add the key to the map;
-    if (NULL == kv_list->map_methods->put(&kv_list->map->map_base.base, key, node_data)) {
-        return 1; // LCOV_EXCL_LINE
-    }
+    // add the key to the map
+    const CxMapEntry entry = kv_list->map_methods->put(
+            &kv_list->map->map_base.base, key, node_data);
+    if (entry.key == NULL) return 1; // LCOV_EXCL_LINE
 
     // write the key to the list's node
     CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data);
-    *loc_key = key;
+    *loc_key = *entry.key;
 
     return 0;
 }
@@ -698,22 +692,23 @@
     cx_kv_list *kv_list = (cx_kv_list*)list;
 
     // reserve memory in the map
-    void **map_data = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL);
-    if (map_data == NULL) return 1; // LCOV_EXCL_LINE
+    CxMapEntry map_entry = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL);
+    if (map_entry.key == NULL) return 1; // LCOV_EXCL_LINE
 
     // insert the node
     void *node_data = kv_list->list_methods->insert_element(&kv_list->list.base, index,
         kv_list->list.base.collection.store_pointer ? &value : value);
     if (node_data == NULL) { // LCOV_EXCL_START
         // non-destructively remove the key again
-        kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data);
+        void *dummy;
+        kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &dummy);
         return 1;
     } // LCOV_EXCL_STOP
-    *map_data = node_data;
+    *(void**)map_entry.value = node_data;
 
     // write the key to the node
     CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data);
-    *loc_key = key;
+    *loc_key = *map_entry.key;
 
     return 0;
 }
--- a/ucx/linked_list.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/linked_list.c	Thu Dec 18 17:50:15 2025 +0100
@@ -65,13 +65,14 @@
     return (void *) cur;
 }
 
-void *cx_linked_list_find(
+void *cx_linked_list_find_c(
         const void *start,
         ptrdiff_t loc_advance,
         ptrdiff_t loc_data,
-        cx_compare_func cmp_func,
         const void *elem,
-        size_t *found_index
+        size_t *found_index,
+        cx_compare_func2 cmp_func,
+        void *context
 ) {
     assert(start != NULL);
     assert(loc_advance >= 0);
@@ -82,7 +83,7 @@
     size_t index = 0;
     do {
         void *current = ll_data(node);
-        if (cmp_func(current, elem) == 0) {
+        if (cmp_func(current, elem, context) == 0) {
             if (found_index != NULL) {
                 *found_index = index;
             }
@@ -94,6 +95,19 @@
     return NULL;
 }
 
+void *cx_linked_list_find(
+        const void *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        const void *elem,
+        size_t *found_index,
+        cx_compare_func cmp_func
+) {
+    cx_compare_func_wrapper wrapper = {cmp_func};
+    return cx_linked_list_find_c(start, loc_advance, loc_data,
+        elem, found_index, cx_acmp_wrap, &wrapper);
+}
+
 void *cx_linked_list_first(
         const void *node,
         ptrdiff_t loc_prev
@@ -259,7 +273,8 @@
         ptrdiff_t loc_prev,
         ptrdiff_t loc_next,
         void *insert_begin,
-        cx_compare_func cmp_func,
+        cx_compare_func2 cmp_func,
+        void *context,
         bool allow_duplicates
 ) {
     assert(begin != NULL);
@@ -276,7 +291,7 @@
 
     // determine the new start
     {
-        int d = source_original ==  NULL ? 1 : cmp_func(source_original, source_argument);
+        int d = source_original ==  NULL ? 1 : cmp_func(source_original, source_argument, context);
         if (d <= 0) {
             // the new chain starts with the original chain
             new_begin = new_end = source_original;
@@ -302,7 +317,7 @@
 
     // now successively compare the elements and add them to the correct chains
     while (source_original != NULL && source_argument != NULL) {
-        int d = cmp_func(source_original, source_argument);
+        int d = cmp_func(source_original, source_argument, context);
         if (d <= 0) {
             // the original is not larger, add it to the chain
             cx_linked_list_link(new_end, source_original, loc_prev, loc_next);
@@ -327,7 +342,7 @@
         } else {
             // the original is larger, append the source argument to the chain
             // check if we must discard the source argument as duplicate
-            if (!allow_duplicates && cmp_func(new_end, source_argument) == 0) {
+            if (!allow_duplicates && cmp_func(new_end, source_argument, context) == 0) {
                 if (dup_end == NULL) {
                     dup_begin = dup_end = source_argument;
                 } else {
@@ -356,7 +371,7 @@
         } else {
             // otherwise we must check one-by-one
             while (source_argument != NULL) {
-                if (cmp_func(new_end, source_argument) == 0) {
+                if (cmp_func(new_end, source_argument, context) == 0) {
                     if (dup_end == NULL) {
                         dup_begin = dup_end = source_argument;
                     } else {
@@ -402,9 +417,10 @@
         void *insert_begin,
         cx_compare_func cmp_func
 ) {
+    cx_compare_func_wrapper wrapper = {cmp_func};
     cx_linked_list_insert_sorted_chain_impl(
             begin, end, loc_prev, loc_next,
-            insert_begin, cmp_func, true);
+            insert_begin, cx_acmp_wrap, &wrapper, true);
 }
 
 int cx_linked_list_insert_unique(
@@ -428,9 +444,10 @@
         void *insert_begin,
         cx_compare_func cmp_func
 ) {
+    cx_compare_func_wrapper wrapper = {cmp_func};
     return cx_linked_list_insert_sorted_chain_impl(
             begin, end, loc_prev, loc_next,
-            insert_begin, cmp_func, false);
+            insert_begin, cx_acmp_wrap, &wrapper, false);
 }
 
 size_t cx_linked_list_remove_chain(
@@ -511,6 +528,8 @@
 #endif
 
 static void cx_linked_list_sort_merge(
+        void **begin,
+        void **end,
         ptrdiff_t loc_prev,
         ptrdiff_t loc_next,
         ptrdiff_t loc_data,
@@ -518,9 +537,8 @@
         void *ls,
         void *le,
         void *re,
-        cx_compare_func cmp_func,
-        void **begin,
-        void **end
+        cx_compare_func2 cmp_func,
+        void *context
 ) {
     void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
     void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
@@ -532,7 +550,7 @@
     rc = le;
     size_t n = 0;
     while (lc && lc != le && rc != re) {
-        if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) {
+        if (cmp_func(ll_data(lc), ll_data(rc), context) <= 0) {
             sorted[n] = lc;
             lc = ll_next(lc);
         } else {
@@ -566,13 +584,14 @@
     }
 }
 
-void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function
+void cx_linked_list_sort_c( // NOLINT(misc-no-recursion) - purposely recursive function
         void **begin,
         void **end,
         ptrdiff_t loc_prev,
         ptrdiff_t loc_next,
         ptrdiff_t loc_data,
-        cx_compare_func cmp_func
+        cx_compare_func2 cmp_func,
+        void *context
 ) {
     assert(begin != NULL);
     assert(loc_next >= 0);
@@ -590,7 +609,7 @@
     // check how many elements are already sorted
     lc = ls;
     size_t ln = 1;
-    while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) {
+    while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc), context) > 0) {
         lc = ll_next(lc);
         ln++;
     }
@@ -602,7 +621,7 @@
         size_t rn = 1;
         rc = le;
         // skip already sorted elements
-        while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) {
+        while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc), context) > 0) {
             rc = ll_next(rc);
             rn++;
         }
@@ -610,27 +629,65 @@
 
         // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
         void *sorted_begin, *sorted_end;
-        cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+        cx_linked_list_sort_merge(&sorted_begin, &sorted_end,
+                                  loc_prev, loc_next, loc_data,
                                   ln + rn, ls, le, re, cmp_func,
-                                  &sorted_begin, &sorted_end);
+                                  context);
 
         // Something left? Sort it!
         size_t remainder_length = cx_linked_list_size(re, loc_next);
         if (remainder_length > 0) {
             void *remainder = re;
-            cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func);
+            cx_linked_list_sort_c(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func, context);
 
             // merge sorted list with (also sorted) remainder
-            cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+            cx_linked_list_sort_merge(&sorted_begin, &sorted_end,
+                                      loc_prev, loc_next, loc_data,
                                       ln + rn + remainder_length,
                                       sorted_begin, remainder, NULL, cmp_func,
-                                      &sorted_begin, &sorted_end);
+                                      context);
         }
         *begin = sorted_begin;
         if (end) *end = sorted_end;
     }
 }
 
+void cx_linked_list_sort(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    cx_compare_func_wrapper wrapper = {cmp_func};
+    cx_linked_list_sort_c(begin, end, loc_prev, loc_next, loc_data, cx_acmp_wrap, &wrapper);
+}
+
+int cx_linked_list_compare_c(
+        const void *begin_left,
+        const void *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func2 cmp_func,
+        void *context
+) {
+    const void *left = begin_left, *right = begin_right;
+
+    while (left != NULL && right != NULL) {
+        const void *left_data = ll_data(left);
+        const void *right_data = ll_data(right);
+        int result = cmp_func(left_data, right_data, context);
+        if (result != 0) return result;
+        left = ll_advance(left);
+        right = ll_advance(right);
+    }
+
+    if (left != NULL) { return 1; }
+    else if (right != NULL) { return -1; }
+    else { return 0; }
+}
+
 int cx_linked_list_compare(
         const void *begin_left,
         const void *begin_right,
@@ -638,20 +695,9 @@
         ptrdiff_t loc_data,
         cx_compare_func cmp_func
 ) {
-    const void *left = begin_left, *right = begin_right;
-
-    while (left != NULL && right != NULL) {
-        const void *left_data = ll_data(left);
-        const void *right_data = ll_data(right);
-        int result = cmp_func(left_data, right_data);
-        if (result != 0) return result;
-        left = ll_advance(left);
-        right = ll_advance(right);
-    }
-
-    if (left != NULL) { return 1; }
-    else if (right != NULL) { return -1; }
-    else { return 0; }
+    cx_compare_func_wrapper wrapper = {cmp_func};
+    return cx_linked_list_compare_c(begin_left, begin_right,
+            loc_advance, loc_data, cx_acmp_wrap, &wrapper);
 }
 
 void cx_linked_list_reverse(
@@ -798,13 +844,11 @@
     }
 }
 
-static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func;
-static _Thread_local off_t cx_ll_insert_sorted_loc_data;
-
-static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) {
-    const char *left = (const char*)l + cx_ll_insert_sorted_loc_data;
-    const char *right = (const char*)r + cx_ll_insert_sorted_loc_data;
-    return cx_ll_insert_sorted_cmp_func(left, right);
+static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r, void *c) {
+    cx_linked_list *list = c;
+    const char *left = (const char*)l + list->loc_data;
+    const char *right = (const char*)r + list->loc_data;
+    return cx_list_compare_wrapper(left, right, list);
 }
 
 static size_t cx_ll_insert_sorted_impl(
@@ -839,29 +883,19 @@
     }
     CX_LL_PTR(prev, ll->loc_next) = NULL;
 
-    // invoke the low level function
-    cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc;
-    cx_ll_insert_sorted_loc_data = ll->loc_data;
-    if (allow_duplicates) {
-        cx_linked_list_insert_sorted_chain(
-                &ll->begin,
-                &ll->end,
-                ll->loc_prev,
-                ll->loc_next,
-                chain,
-                cx_ll_insert_sorted_cmp_helper
-        );
-        list->collection.size += inserted;
-    } else {
-        void *duplicates = cx_linked_list_insert_unique_chain(
-                &ll->begin,
-                &ll->end,
-                ll->loc_prev,
-                ll->loc_next,
-                chain,
-                cx_ll_insert_sorted_cmp_helper
-        );
-        list->collection.size += inserted;
+    // invoke the low-level function
+    void *duplicates = cx_linked_list_insert_sorted_chain_impl(
+            &ll->begin,
+            &ll->end,
+            ll->loc_prev,
+            ll->loc_next,
+            chain,
+            cx_ll_insert_sorted_cmp_helper,
+            list,
+            allow_duplicates
+    );
+    list->collection.size += inserted;
+    if (!allow_duplicates) {
         // free the nodes that did not make it into the list
         while (duplicates != NULL) {
             void *next = CX_LL_PTR(duplicates, ll->loc_next);
@@ -1090,12 +1124,12 @@
 
     size_t index;
     cx_linked_list *ll = (cx_linked_list *) list;
-    char *node = cx_linked_list_find(
+    char *node = cx_linked_list_find_c(
             ll->begin,
             ll->loc_next, ll->loc_data,
-            list->collection.cmpfunc, elem,
-            &index
-    );
+            elem, &index,
+            cx_list_compare_wrapper,
+            list);
     if (node == NULL) {
         return list->collection.size;
     }
@@ -1111,9 +1145,9 @@
 
 static void cx_ll_sort(struct cx_list_s *list) {
     cx_linked_list *ll = (cx_linked_list *) list;
-    cx_linked_list_sort(&ll->begin, &ll->end,
+    cx_linked_list_sort_c(&ll->begin, &ll->end,
                         ll->loc_prev, ll->loc_next, ll->loc_data,
-                        list->collection.cmpfunc);
+                        cx_list_compare_wrapper, list);
 }
 
 static void cx_ll_reverse(struct cx_list_s *list) {
@@ -1129,9 +1163,9 @@
     cx_linked_list *right = (cx_linked_list *) other;
     assert(left->loc_next == right->loc_next);
     assert(left->loc_data == right->loc_data);
-    return cx_linked_list_compare(left->begin, right->begin,
+    return cx_linked_list_compare_c(left->begin, right->begin,
                                   left->loc_next, left->loc_data,
-                                  list->collection.cmpfunc);
+                                  cx_list_compare_wrapper, (void*)list);
 }
 
 static bool cx_ll_iter_valid(const void *it) {
@@ -1272,7 +1306,6 @@
 
 CxList *cxLinkedListCreate(
         const CxAllocator *allocator,
-        cx_compare_func comparator,
         size_t elem_size
 ) {
     if (allocator == NULL) {
@@ -1287,7 +1320,7 @@
     list->loc_extra = -1;
     list->extra_data_len = 0;
     cx_list_init((CxList*)list, &cx_linked_list_class,
-            allocator, comparator, elem_size);
+            allocator, elem_size);
 
     return (CxList *) list;
 }
--- a/ucx/list.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/list.c	Thu Dec 18 17:50:15 2025 +0100
@@ -31,198 +31,29 @@
 #include <string.h>
 #include <assert.h>
 
-// <editor-fold desc="Store Pointers Functionality">
-
-static _Thread_local cx_compare_func cx_pl_cmpfunc_impl;
-
-static int cx_pl_cmpfunc(
-        const void *l,
-        const void *r
-) {
-    // l and r are guaranteed to be non-NULL pointing to the list's memory
-    void *const *lptr = l;
-    void *const *rptr = r;
-    const void *left = *lptr;
-    const void *right = *rptr;
-    if (left == NULL) {
-        // NULL is smaller than any value except NULL
-        return right == NULL ? 0 : -1;
-    } else if (right == NULL) {
-        // any value is larger than NULL
-        return 1;
+int cx_list_compare_wrapper(const void *l, const void *r, void *c) {
+    CxList *list = c;
+    const void *left;
+    const void *right;
+    if (cxCollectionStoresPointers(list)) {
+        left = *(void**)l;
+        right = *(void**)r;
+        // for historic reasons, we are handling the NULL case here
+        // because every UCX compare function does not support NULL arguments
+        if (left == NULL) {
+            if (right == NULL) return 0;
+            return -1;
+        } else if (right == NULL) {
+            return 1;
+        }
+    } else {
+        left = l;
+        right = r;
     }
-    return cx_pl_cmpfunc_impl(left, right);
-}
-
-static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) {
-    // cast away const - this is the hacky thing
-    struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
-    cx_pl_cmpfunc_impl = l->cmpfunc;
-    l->cmpfunc = cx_pl_cmpfunc;
-}
-
-static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) {
-    // cast away const - this is the hacky thing
-    struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
-    l->cmpfunc = cx_pl_cmpfunc_impl;
-}
-
-static void cx_pl_destructor(struct cx_list_s *list) {
-    list->climpl->deallocate(list);
-}
-
-static void *cx_pl_insert_element(
-        struct cx_list_s *list,
-        size_t index,
-        const void *element
-) {
-    return list->climpl->insert_element(list, index, &element);
-}
-
-static size_t cx_pl_insert_array(
-        struct cx_list_s *list,
-        size_t index,
-        const void *array,
-        size_t n
-) {
-    return list->climpl->insert_array(list, index, array, n);
-}
-
-static size_t cx_pl_insert_sorted(
-        struct cx_list_s *list,
-        const void *array,
-        size_t n
-) {
-    cx_pl_hack_cmpfunc(list);
-    size_t result = list->climpl->insert_sorted(list, array, n);
-    cx_pl_unhack_cmpfunc(list);
-    return result;
-}
-
-static size_t cx_pl_insert_unique(
-        struct cx_list_s *list,
-        const void *array,
-        size_t n
-) {
-    cx_pl_hack_cmpfunc(list);
-    size_t result = list->climpl->insert_unique(list, array, n);
-    cx_pl_unhack_cmpfunc(list);
-    return result;
-}
-
-static int cx_pl_insert_iter(
-        struct cx_iterator_s *iter,
-        const void *elem,
-        int prepend
-) {
-    struct cx_list_s *list = iter->src_handle;
-    return list->climpl->insert_iter(iter, &elem, prepend);
+    return cx_invoke_compare_func(list, left, right);
 }
 
-static size_t cx_pl_remove(
-        struct cx_list_s *list,
-        size_t index,
-        size_t num,
-        void *targetbuf
-) {
-    return list->climpl->remove(list, index, num, targetbuf);
-}
-
-static void cx_pl_clear(struct cx_list_s *list) {
-    list->climpl->clear(list);
-}
-
-static int cx_pl_swap(
-        struct cx_list_s *list,
-        size_t i,
-        size_t j
-) {
-    return list->climpl->swap(list, i, j);
-}
-
-static void *cx_pl_at(
-        const struct cx_list_s *list,
-        size_t index
-) {
-    void **ptr = list->climpl->at(list, index);
-    return ptr == NULL ? NULL : *ptr;
-}
-
-static size_t cx_pl_find_remove(
-        struct cx_list_s *list,
-        const void *elem,
-        bool remove
-) {
-    cx_pl_hack_cmpfunc(list);
-    size_t ret = list->climpl->find_remove(list, &elem, remove);
-    cx_pl_unhack_cmpfunc(list);
-    return ret;
-}
-
-static void cx_pl_sort(struct cx_list_s *list) {
-    cx_pl_hack_cmpfunc(list);
-    list->climpl->sort(list);
-    cx_pl_unhack_cmpfunc(list);
-}
-
-static int cx_pl_compare(
-        const struct cx_list_s *list,
-        const struct cx_list_s *other
-) {
-    cx_pl_hack_cmpfunc(list);
-    int ret = list->climpl->compare(list, other);
-    cx_pl_unhack_cmpfunc(list);
-    return ret;
-}
-
-static void cx_pl_reverse(struct cx_list_s *list) {
-    list->climpl->reverse(list);
-}
-
-static void *cx_pl_iter_current(const void *it) {
-    const struct cx_iterator_s *iter = it;
-    void **ptr = iter->base.current_impl(it);
-    return ptr == NULL ? NULL : *ptr;
-}
-
-static int cx_pl_change_capacity(struct cx_list_s *list, size_t cap) {
-    if (list->climpl->change_capacity == NULL) {
-        return 0;
-    } else {
-        return list->climpl->change_capacity(list, cap);
-    }
-}
-
-static struct cx_iterator_s cx_pl_iterator(
-        const struct cx_list_s *list,
-        size_t index,
-        bool backwards
-) {
-    struct cx_iterator_s iter = list->climpl->iterator(list, index, backwards);
-    iter.base.current_impl = iter.base.current;
-    iter.base.current = cx_pl_iter_current;
-    return iter;
-}
-
-static cx_list_class cx_pointer_list_class = {
-        cx_pl_destructor,
-        cx_pl_insert_element,
-        cx_pl_insert_array,
-        cx_pl_insert_sorted,
-        cx_pl_insert_unique,
-        cx_pl_insert_iter,
-        cx_pl_remove,
-        cx_pl_clear,
-        cx_pl_swap,
-        cx_pl_at,
-        cx_pl_find_remove,
-        cx_pl_sort,
-        cx_pl_compare,
-        cx_pl_reverse,
-        cx_pl_change_capacity,
-        cx_pl_iterator,
-};
-// </editor-fold>
+#define cx_list_compare_wrapper(l, r, c) cx_list_compare_wrapper(l, r, (void*)c)
 
 // <editor-fold desc="empty list implementation">
 
@@ -283,27 +114,24 @@
 CxList cx_empty_list = {
     {
         NULL,
-        NULL,
         0,
         0,
         NULL,
         NULL,
         NULL,
+        NULL,
+        NULL,
+        NULL,
         false,
         true,
     },
     &cx_empty_list_class,
-    NULL
 };
 
 CxList *const cxEmptyList = &cx_empty_list;
 
 // </editor-fold>
 
-#define invoke_list_func(name, list, ...) \
-    ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \
-    (list, __VA_ARGS__)
-
 size_t cx_list_default_insert_array(
         struct cx_list_s *list,
         size_t index,
@@ -313,8 +141,7 @@
     const char *src = data;
     size_t i = 0;
     for (; i < n; i++) {
-        if (NULL == invoke_list_func(
-            insert_element, list, index + i, src)
+        if (NULL == list->cl->insert_element(list, index + i, src)
         ) {
             return i; // LCOV_EXCL_LINE
         }
@@ -335,7 +162,6 @@
     if (n == 0) return 0;
 
     size_t elem_size = list->collection.elem_size;
-    cx_compare_func cmp = list->collection.cmpfunc;
     const char *src = sorted_data;
 
     // track indices and number of inserted items
@@ -343,19 +169,19 @@
 
     // search the list for insertion points
     while (di < list->collection.size) {
-        const void *list_elm = invoke_list_func(at, list, di);
+        const void *list_elm = list->cl->at(list, di);
 
         // compare the current list element with the first source element
         // if less, skip the list elements
         // if equal, skip the list elements and optionally the source elements
         {
-            int d = cmp(list_elm, src);
+            int d = cx_list_compare_wrapper(list_elm, src, list);
             if (d <= 0) {
                 if (!allow_duplicates && d == 0) {
                     src += elem_size;
                     si++;
                     processed++; // we also count duplicates for the return value
-                    while (si < n && cmp(list_elm, src) == 0) {
+                    while (si < n && cx_list_compare_wrapper(list_elm, src, list) == 0) {
                         src += elem_size;
                         si++;
                         processed++;
@@ -375,7 +201,7 @@
         while (++si < n) {
             if (!allow_duplicates) {
                 // skip duplicates within the source
-                if (cmp(next, next + elem_size) == 0) {
+                if (cx_list_compare_wrapper(next, next + elem_size, list) == 0) {
                     next += elem_size;
                     skip++;
                     continue;
@@ -389,7 +215,7 @@
             }
             next += elem_size;
             // once we become larger than the list elem, break
-            if (cmp(list_elm, next) <= 0) {
+            if (cx_list_compare_wrapper(list_elm, next, list) <= 0) {
                 break;
             }
             // otherwise, we can insert one more
@@ -398,11 +224,11 @@
 
         // insert the elements at location si
         if (ins == 1) {
-            if (NULL == invoke_list_func(insert_element, list, di, src)) {
+            if (NULL == list->cl->insert_element(list, di, src)) {
                 return processed; // LCOV_EXCL_LINE
             }
         } else {
-            size_t r = invoke_list_func(insert_array, list, di, src, ins);
+            size_t r = list->cl->insert_array(list, di, src, ins);
             if (r < ins) {
                 return processed + r;  // LCOV_EXCL_LINE
             }
@@ -420,13 +246,13 @@
     // insert remaining items
     if (si < n) {
         if (allow_duplicates) {
-            processed += invoke_list_func(insert_array, list, di, src, n - si);
+            processed += list->cl->insert_array(list, di, src, n - si);
         } else {
-            const void *last = di == 0 ? NULL : invoke_list_func(at, list, di - 1);
+            const void *last = di == 0 ? NULL : list->cl->at(list, di - 1);
             for (; si < n; si++) {
                 // skip duplicates within the source
-                if (last == NULL || cmp(last, src) != 0) {
-                    if (NULL == invoke_list_func(insert_element, list, di, src)) {
+                if (last == NULL || cx_list_compare_wrapper(last, src, list) != 0) {
+                    if (NULL == list->cl->insert_element(list, di, src)) {
                         return processed; // LCOV_EXCL_LINE
                     }
                     last = src;
@@ -457,6 +283,12 @@
     return cx_list_default_insert_sorted_impl(list, sorted_data, n, false);
 }
 
+// TODO: remove this hack once we have a solution for qsort() / qsort_s()
+static _Thread_local CxList *cx_hack_for_qsort_list;
+static int cx_hack_cmp_for_qsort(const void *l, const void *r) {
+    return cx_list_compare_wrapper(l, r, cx_hack_for_qsort_list);
+}
+
 void cx_list_default_sort(struct cx_list_s *list) {
     size_t elem_size = list->collection.elem_size;
     size_t list_size = list->collection.size;
@@ -466,19 +298,20 @@
     // copy elements from source array
     char *loc = tmp;
     for (size_t i = 0; i < list_size; i++) {
-        void *src = invoke_list_func(at, list, i);
+        void *src = list->cl->at(list, i);
         memcpy(loc, src, elem_size);
         loc += elem_size;
     }
 
     // qsort
-    qsort(tmp, list_size, elem_size,
-          list->collection.cmpfunc);
+    // TODO: qsort_s() is not as nice as we thought
+    cx_hack_for_qsort_list = list;
+    qsort(tmp, list_size, elem_size, cx_hack_cmp_for_qsort);
 
     // copy elements back
     loc = tmp;
     for (size_t i = 0; i < list_size; i++) {
-        void *dest = invoke_list_func(at, list, i);
+        void *dest = list->cl->at(list, i);
         memcpy(dest, loc, elem_size);
         loc += elem_size;
     }
@@ -496,8 +329,8 @@
     void *tmp = cxMallocDefault(elem_size);
     if (tmp == NULL) return 1; // LCOV_EXCL_LINE
 
-    void *ip = invoke_list_func(at, list, i);
-    void *jp = invoke_list_func(at, list, j);
+    void *ip = list->cl->at(list, i);
+    void *jp = list->cl->at(list, j);
 
     memcpy(tmp, ip, elem_size);
     memcpy(ip, jp, elem_size);
@@ -512,22 +345,24 @@
     struct cx_list_s *list,
     struct cx_list_class_s *cl,
     const struct cx_allocator_s *allocator,
-    cx_compare_func comparator,
     size_t elem_size
 ) {
     list->cl = cl;
     list->collection.allocator = allocator;
-    list->collection.cmpfunc = comparator;
+    list->collection.size = 0;
+    list->collection.sorted = false; // should be set by the implementation
     if (elem_size > 0) {
         list->collection.elem_size = elem_size;
+        list->collection.simple_cmp = NULL;
+        list->collection.advanced_cmp = cx_acmp_memcmp;
+        list->collection.cmp_data = &list->collection.elem_size;
+        list->collection.store_pointer = false;
     } else {
         list->collection.elem_size = sizeof(void *);
-        if (list->collection.cmpfunc == NULL) {
-            list->collection.cmpfunc = cx_cmp_ptr;
-        }
+        list->collection.simple_cmp = cx_cmp_ptr;
+        list->collection.advanced_cmp = NULL;
+        list->collection.cmp_data = NULL;
         list->collection.store_pointer = true;
-        list->climpl = list->cl;
-        list->cl = &cx_pointer_list_class;
     }
 }
 
@@ -535,33 +370,28 @@
         const CxList *list,
         const CxList *other
 ) {
+    // check if we cannot use the list internal function
     bool cannot_optimize = false;
 
     // if one is storing pointers but the other is not
     cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer;
 
-    // if one class is wrapped but the other is not
-    cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL);
-
-    // if the compare functions do not match or both are NULL
-    if (!cannot_optimize) {
-        cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ?
-                                                      list->climpl->compare : list->cl->compare);
-        cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ?
-                                                       other->climpl->compare : other->cl->compare);
-        cannot_optimize |= list_cmp != other_cmp;
-        cannot_optimize |= list_cmp == NULL;
-    }
+    // check if the lists are incompatible or this list does not implement compare
+    cx_compare_func list_cmp = (cx_compare_func) list->cl->compare;
+    cx_compare_func other_cmp = (cx_compare_func) other->cl->compare;
+    cannot_optimize |= list_cmp != other_cmp;
+    cannot_optimize |= list_cmp == NULL;
 
     if (cannot_optimize) {
         // lists are definitely different - cannot use internal compare function
         if (list->collection.size == other->collection.size) {
-            CxIterator left = list->cl->iterator(list, 0, false);
-            CxIterator right = other->cl->iterator(other, 0, false);
+            CxIterator left = cxListIterator(list);
+            CxIterator right = cxListIterator(other);
             for (size_t i = 0; i < list->collection.size; i++) {
                 void *leftValue = cxIteratorCurrent(left);
                 void *rightValue = cxIteratorCurrent(right);
-                int d = list->collection.cmpfunc(leftValue, rightValue);
+                // values are already unwrapped, invoke immediately
+                int d = cx_invoke_compare_func(list, leftValue, rightValue);
                 if (d != 0) {
                     return d;
                 }
@@ -584,7 +414,7 @@
 
 int cxListAdd(CxList *list, const void *elem) {
     list->collection.sorted = false;
-    return list->cl->insert_element(list, list->collection.size, elem) == NULL;
+    return list->cl->insert_element(list, list->collection.size, cx_ref(list, elem)) == NULL;
 }
 
 size_t cxListAddArray(CxList *list, const void *array, size_t n) {
@@ -594,7 +424,7 @@
 
 int cxListInsert(CxList *list, size_t index, const void *elem) {
     list->collection.sorted = false;
-    return list->cl->insert_element(list, index, elem) == NULL;
+    return list->cl->insert_element(list, index, cx_ref(list, elem)) == NULL;
 }
 
 void *cxListEmplaceAt(CxList *list, size_t index) {
@@ -621,11 +451,6 @@
     iter.index = 0;
     // replace the valid function to abort iteration when c is reached
     iter.base.valid = cx_list_emplace_iterator_valid;
-    // if we are storing pointers, we want to return the pure pointers.
-    // therefore, we must unwrap the "current" method
-    if (list->collection.store_pointer) {
-        iter.base.current = iter.base.current_impl;
-    }
     return iter;
 }
 
@@ -636,15 +461,13 @@
 int cxListInsertSorted(CxList *list, const void *elem) {
     assert(cxCollectionSorted(list));
     list->collection.sorted = true;
-    const void *data = list->collection.store_pointer ? &elem : elem;
-    return list->cl->insert_sorted(list, data, 1) == 0;
+    return list->cl->insert_sorted(list, cx_ref(list, elem), 1) == 0;
 }
 
 int cxListInsertUnique(CxList *list, const void *elem) {
     if (cxCollectionSorted(list)) {
         list->collection.sorted = true;
-        const void *data = list->collection.store_pointer ? &elem : elem;
-        return list->cl->insert_unique(list, data, 1) == 0;
+        return list->cl->insert_unique(list, cx_ref(list, elem), 1) == 0;
     } else {
         if (cxListContains(list, elem)) {
             return 0;
@@ -673,8 +496,7 @@
         const char *source = array;
         for (size_t i = 0 ; i < n; i++) {
             // note: this also checks elements added in a previous iteration
-            const void *data = list->collection.store_pointer ?
-                    *((const void**)source) : source;
+            const void *data = cx_deref(list, source);
             if (!cxListContains(list, data)) {
                 if (cxListAdd(list, data)) {
                     return i; // LCOV_EXCL_LINE
@@ -687,15 +509,15 @@
 }
 
 int cxListInsertAfter(CxIterator *iter, const void *elem) {
-    CxList* list = (CxList*)iter->src_handle;
+    CxList* list = iter->src_handle;
     list->collection.sorted = false;
-    return list->cl->insert_iter(iter, elem, 0);
+    return list->cl->insert_iter(iter, cx_ref(list, elem), 0);
 }
 
 int cxListInsertBefore(CxIterator *iter, const void *elem) {
-    CxList* list = (CxList*)iter->src_handle;
+    CxList* list = iter->src_handle;
     list->collection.sorted = false;
-    return list->cl->insert_iter(iter, elem, 1);
+    return list->cl->insert_iter(iter, cx_ref(list, elem), 1);
 }
 
 int cxListRemove(CxList *list, size_t index) {
@@ -734,15 +556,17 @@
 }
 
 void *cxListAt(const CxList *list, size_t index) {
-    return list->cl->at(list, index);
+    void *result = list->cl->at(list, index);
+    if (result == NULL) return NULL;
+    return cx_deref(list, result);
 }
 
 void *cxListFirst(const CxList *list) {
-    return list->cl->at(list, 0);
+    return cxListAt(list, 0);
 }
 
 void *cxListLast(const CxList *list) {
-    return list->cl->at(list, list->collection.size - 1);
+    return cxListAt(list, list->collection.size - 1);
 }
 
 int cxListSet(CxList *list, size_t index, const void *elem) {
@@ -751,8 +575,7 @@
     }
 
     if (list->collection.store_pointer) {
-        // For pointer collections, always use climpl
-        void **target = list->climpl->at(list, index);
+        void **target = list->cl->at(list, index);
         *target = (void *)elem;
     } else {
         void *target = list->cl->at(list, index);
@@ -762,32 +585,48 @@
     return 0;
 }
 
+static void *cx_pl_iter_current(const void *it) {
+    const struct cx_iterator_s *iter = it;
+    void **ptr = iter->base.current_impl(it);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+CX_INLINE CxIterator cx_pl_iter_wrap(const CxList *list, CxIterator iter) {
+    if (cxCollectionStoresPointers(list)) {
+        iter.base.current_impl = iter.base.current;
+        iter.base.current = cx_pl_iter_current;
+        return iter;
+    } else {
+        return iter;
+    }
+}
+
 CxIterator cxListIteratorAt(const CxList *list, size_t index) {
     if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, index, false);
+    return cx_pl_iter_wrap(list, list->cl->iterator(list, index, false));
 }
 
 CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index) {
     if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, index, true);
+    return cx_pl_iter_wrap(list, list->cl->iterator(list, index, true));
 }
 
 CxIterator cxListIterator(const CxList *list) {
     if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, 0, false);
+    return cx_pl_iter_wrap(list, list->cl->iterator(list, 0, false));
 }
 
 CxIterator cxListBackwardsIterator(const CxList *list) {
     if (list == NULL) list = cxEmptyList;
-    return list->cl->iterator(list, list->collection.size - 1, true);
+    return cx_pl_iter_wrap(list, list->cl->iterator(list, list->collection.size - 1, true));
 }
 
 size_t cxListFind(const CxList *list, const void *elem) {
-    return list->cl->find_remove((CxList*)list, elem, false);
+    return list->cl->find_remove((CxList*)list, cx_ref(list, elem), false);
 }
 
 bool cxListContains(const CxList* list, const void* elem) {
-    return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size;
+    return list->cl->find_remove((CxList*)list, cx_ref(list, elem), false) < list->collection.size;
 }
 
 bool cxListIndexValid(const CxList *list, size_t index) {
@@ -795,7 +634,7 @@
 }
 
 size_t cxListFindRemove(CxList *list, const void *elem) {
-    return list->cl->find_remove(list, elem, true);
+    return list->cl->find_remove(list, cx_ref(list, elem), true);
 }
 
 void cxListSort(CxList *list) {
@@ -829,14 +668,14 @@
     list->collection.advanced_destructor = destr2_bak;
 }
 
-static void* cx_list_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) {
+static void* cx_list_shallow_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) {
     size_t elem_size = *(size_t*)data;
     if (dst == NULL) dst = cxMalloc(al, elem_size);
     if (dst != NULL) memcpy(dst, src, elem_size);
     return dst;
 }
 
-#define use_simple_clone_func(list) cx_list_simple_clone_func, NULL, (void*)&((list)->collection.elem_size)
+#define use_shallow_clone_func(list) cx_list_shallow_clone_func, NULL, (void*)&((list)->collection.elem_size)
 
 int cxListClone(CxList *dst, const CxList *src, cx_clone_func clone_func,
         const CxAllocator *clone_allocator, void *data) {
@@ -902,8 +741,7 @@
             int d;
             if (cxIteratorValid(sub_iter)) {
                 sub_elem = cxIteratorCurrent(sub_iter);
-                cx_compare_func cmp = subtrahend->collection.cmpfunc;
-                d = cmp(sub_elem, min_elem);
+                d = cx_list_compare_wrapper(sub_elem, min_elem, subtrahend);
             } else {
                 // no more elements in the subtrahend,
                 // i.e., the min_elem is larger than any elem of the subtrahend
@@ -971,7 +809,7 @@
         while (cxIteratorValid(src_iter) && cxIteratorValid(other_iter)) {
             void *src_elem = cxIteratorCurrent(src_iter);
             void *other_elem = cxIteratorCurrent(other_iter);
-            int d = src->collection.cmpfunc(src_elem, other_elem);
+            int d = cx_list_compare_wrapper(src_elem, other_elem, src);
             if (d == 0) {
                 // is contained, clone it
                 void **dst_mem = cxListEmplace(dst);
@@ -1041,7 +879,7 @@
             } else {
                 src_elem = cxIteratorCurrent(src_iter);
                 other_elem = cxIteratorCurrent(other_iter);
-                d = src->collection.cmpfunc(src_elem, other_elem);
+                d = cx_list_compare_wrapper(src_elem, other_elem, src);
             }
             void *clone_from;
             if (d < 0) {
@@ -1097,20 +935,20 @@
     return 0;
 }
 
-int cxListCloneSimple(CxList *dst, const CxList *src) {
-    return cxListClone(dst, src, use_simple_clone_func(src));
+int cxListCloneShallow(CxList *dst, const CxList *src) {
+    return cxListClone(dst, src, use_shallow_clone_func(src));
 }
 
-int cxListDifferenceSimple(CxList *dst, const CxList *minuend, const CxList *subtrahend) {
-    return cxListDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend));
+int cxListDifferenceShallow(CxList *dst, const CxList *minuend, const CxList *subtrahend) {
+    return cxListDifference(dst, minuend, subtrahend, use_shallow_clone_func(minuend));
 }
 
-int cxListIntersectionSimple(CxList *dst, const CxList *src, const CxList *other) {
-    return cxListIntersection(dst, src, other, use_simple_clone_func(src));
+int cxListIntersectionShallow(CxList *dst, const CxList *src, const CxList *other) {
+    return cxListIntersection(dst, src, other, use_shallow_clone_func(src));
 }
 
-int cxListUnionSimple(CxList *dst, const CxList *src, const CxList *other) {
-    return cxListUnion(dst, src, other, use_simple_clone_func(src));
+int cxListUnionShallow(CxList *dst, const CxList *src, const CxList *other) {
+    return cxListUnion(dst, src, other, use_shallow_clone_func(src));
 }
 
 int cxListReserve(CxList *list, size_t capacity) {
--- a/ucx/map.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/map.c	Thu Dec 18 17:50:15 2025 +0100
@@ -70,12 +70,14 @@
 CxMap cx_empty_map = {
     {
         NULL,
-        NULL,
         0,
         0,
         NULL,
         NULL,
         NULL,
+        NULL,
+        NULL,
+        NULL,
         false,
         true
     },
@@ -110,11 +112,13 @@
 }
 
 int cx_map_put(CxMap *map, CxHashKey key, void *value) {
-    return map->cl->put(map, key, value) == NULL;
+    return map->cl->put(map, key, value).key == NULL;
 }
 
 void *cx_map_emplace(CxMap *map, CxHashKey key) {
-    return map->cl->put(map, key, NULL);
+    const CxMapEntry entry = map->cl->put(map, key, NULL);
+    if (entry.key == NULL) return NULL;
+    return entry.value;
 }
 
 void *cx_map_get(const CxMap *map, CxHashKey key) {
@@ -140,14 +144,14 @@
     map->collection.advanced_destructor = destr2_bak;
 }
 
-static void* cx_map_simple_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) {
+static void* cx_map_shallow_clone_func(void *dst, const void *src, const CxAllocator *al, void *data) {
     size_t elem_size = *(size_t*)data;
     if (dst == NULL) dst = cxMalloc(al, elem_size);
     if (dst != NULL) memcpy(dst, src, elem_size);
     return dst;
 }
 
-#define use_simple_clone_func(map) cx_map_simple_clone_func, NULL, (void*)&((map)->collection.elem_size)
+#define use_shallow_clone_func(map) cx_map_shallow_clone_func, NULL, (void*)&((map)->collection.elem_size)
 
 int cxMapClone(CxMap *dst, const CxMap *src, cx_clone_func clone_func,
         const CxAllocator *clone_allocator, void *data) {
@@ -303,26 +307,55 @@
     return 0;
 }
 
-int cxMapCloneSimple(CxMap *dst, const CxMap *src) {
-    return cxMapClone(dst, src, use_simple_clone_func(src));
+int cxMapCloneShallow(CxMap *dst, const CxMap *src) {
+    return cxMapClone(dst, src, use_shallow_clone_func(src));
+}
+
+int cxMapDifferenceShallow(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) {
+    return cxMapDifference(dst, minuend, subtrahend, use_shallow_clone_func(minuend));
 }
 
-int cxMapDifferenceSimple(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend) {
-    return cxMapDifference(dst, minuend, subtrahend, use_simple_clone_func(minuend));
+int cxMapListDifferenceShallow(CxMap *dst, const CxMap *src, const CxList *keys) {
+    return cxMapListDifference(dst, src, keys, use_shallow_clone_func(src));
 }
 
-int cxMapListDifferenceSimple(CxMap *dst, const CxMap *src, const CxList *keys) {
-    return cxMapListDifference(dst, src, keys, use_simple_clone_func(src));
+int cxMapIntersectionShallow(CxMap *dst, const CxMap *src, const CxMap *other) {
+    return cxMapIntersection(dst, src, other, use_shallow_clone_func(src));
+}
+
+int cxMapListIntersectionShallow(CxMap *dst, const CxMap *src, const CxList *keys) {
+    return cxMapListIntersection(dst, src, keys, use_shallow_clone_func(src));
+}
+
+int cxMapUnionShallow(CxMap *dst, const CxMap *src) {
+    return cxMapUnion(dst, src, use_shallow_clone_func(src));
 }
 
-int cxMapIntersectionSimple(CxMap *dst, const CxMap *src, const CxMap *other) {
-    return cxMapIntersection(dst, src, other, use_simple_clone_func(src));
-}
+int cxMapCompare(const CxMap *map, const CxMap *other) {
+    // compare map sizes
+    const size_t size_left = cxMapSize(map);
+    const size_t size_right = cxMapSize(other);
+    if (size_left < size_right) {
+        return -1;
+    } else if (size_left > size_right) {
+        return 1;
+    }
 
-int cxMapListIntersectionSimple(CxMap *dst, const CxMap *src, const CxList *keys) {
-    return cxMapListIntersection(dst, src, keys, use_simple_clone_func(src));
-}
+    // iterate through the first map
+    CxMapIterator iter = cxMapIterator(map);
+    cx_foreach(const CxMapEntry *, entry, iter) {
+        const void *value_left = entry->value;
+        const void *value_right = cxMapGet(other, *entry->key);
+        // if the other map does not have the key, we are done
+        if (value_right == NULL) {
+            return -1;
+        }
+        // compare the values
+        const int d = cx_invoke_compare_func(map, value_left, value_right);
+        if (d != 0) {
+            return d;
+        }
+    }
 
-int cxMapUnionSimple(CxMap *dst, const CxMap *src) {
-    return cxMapUnion(dst, src, use_simple_clone_func(src));
+    return 0;
 }
--- a/ucx/properties.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/properties.c	Thu Dec 18 17:50:15 2025 +0100
@@ -68,8 +68,8 @@
     if (cxBufferEof(&prop->input)) {
         // destroy a possible previously initialized buffer
         cxBufferDestroy(&prop->input);
-        cxBufferInit(&prop->input, (void*) buf, len,
-            NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND);
+        cxBufferInit(&prop->input, NULL, (void*) buf,
+                     len, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND);
         prop->input.size = len;
     } else {
         if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1;
@@ -82,7 +82,7 @@
         char *buf,
         size_t capacity
 ) {
-    cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND);
+    cxBufferInit(&prop->buffer, NULL, buf, capacity, CX_BUFFER_COPY_ON_EXTEND);
 }
 
 CxPropertiesStatus cxPropertiesNext(
@@ -190,7 +190,7 @@
             assert(cxBufferEof(&prop->buffer));
             if (prop->buffer.space == NULL) {
                 // initialize a rescue buffer, if the user did not provide one
-                cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND);
+                cxBufferInit(&prop->buffer, NULL, NULL, 256, CX_BUFFER_AUTO_EXTEND);
             } else {
                 // from a previous rescue there might be already read data
                 // reset the buffer to avoid unnecessary buffer extension
@@ -251,7 +251,7 @@
                     if (current_buffer != &prop->buffer) {
                         // move value to the rescue buffer
                         if (prop->buffer.space == NULL) {
-                            cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND);
+                            cxBufferInit(&prop->buffer, NULL, NULL, 256, CX_BUFFER_AUTO_EXTEND);
                         }
                         prop->buffer.size = 0;
                         prop->buffer.pos = 0;
@@ -312,8 +312,8 @@
 #endif
 const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE;
 
-CxPropertiesStatus cx_properties_load(CxPropertiesConfig config,
-        const CxAllocator *allocator, cxstring filename, CxMap *target) {
+CxPropertiesStatus cx_properties_load(const CxAllocator *allocator,
+                                      cxstring filename, CxMap *target, CxPropertiesConfig config) {
     if (allocator == NULL) {
         allocator = cxDefaultAllocator;
     }
--- a/ucx/streams.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/streams.c	Thu Dec 18 17:50:15 2025 +0100
@@ -29,12 +29,12 @@
 #include "cx/streams.h"
 #include "cx/allocator.h"
 
-#ifndef CX_STREAM_BCOPY_BUF_SIZE
-#define CX_STREAM_BCOPY_BUF_SIZE 8192
+#ifndef cx_strEAM_BCOPY_BUF_SIZE
+#define cx_strEAM_BCOPY_BUF_SIZE 8192
 #endif
 
-#ifndef CX_STREAM_COPY_BUF_SIZE
-#define CX_STREAM_COPY_BUF_SIZE 1024
+#ifndef cx_strEAM_COPY_BUF_SIZE
+#define cx_strEAM_COPY_BUF_SIZE 1024
 #endif
 
 size_t cx_stream_bncopy(
@@ -57,7 +57,7 @@
         if (bufsize == 0) return 0;
         lbuf = buf;
     } else {
-        if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE;
+        if (bufsize == 0) bufsize = cx_strEAM_BCOPY_BUF_SIZE;
         lbuf = cxMallocDefault(bufsize);
         if (lbuf == NULL) return 0;
     }
@@ -88,7 +88,7 @@
         cx_write_func wfnc,
         size_t n
 ) {
-    char buf[CX_STREAM_COPY_BUF_SIZE];
+    char buf[cx_strEAM_COPY_BUF_SIZE];
     return cx_stream_bncopy(src, dest, rfnc, wfnc,
-                            buf, CX_STREAM_COPY_BUF_SIZE, n);
+                            buf, cx_strEAM_COPY_BUF_SIZE, n);
 }
--- a/ucx/string.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/string.c	Thu Dec 18 17:50:15 2025 +0100
@@ -25,9 +25,9 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  */
-#ifdef MEMRCHR_NEED_GNU
+
+// for memrchr in glibc
 #define _GNU_SOURCE
-#endif
 
 #include "cx/string.h"
 
@@ -46,28 +46,6 @@
 #define cx_strcasecmp_impl strncasecmp
 #endif
 
-cxmutstr cx_mutstr(char *cstring) {
-    return (cxmutstr) {cstring, cstring == NULL ? 0 : strlen(cstring)};
-}
-
-cxmutstr cx_mutstrn(
-        char *cstring,
-        size_t length
-) {
-    return (cxmutstr) {cstring, length};
-}
-
-cxstring cx_str(const char *cstring) {
-    return (cxstring) {cstring, cstring == NULL ? 0 : strlen(cstring)};
-}
-
-cxstring cx_strn(
-        const char *cstring,
-        size_t length
-) {
-    return (cxstring) {cstring, length};
-}
-
 void cx_strfree(cxmutstr *str) {
     if (str == NULL) return;
     cxFreeDefault(str->ptr);
@@ -264,10 +242,10 @@
     return (cxmutstr) {(char *) result.ptr, result.length};
 }
 
-#ifndef CX_STRSTR_SBO_SIZE
-#define CX_STRSTR_SBO_SIZE 128
+#ifndef cx_strSTR_SBO_SIZE
+#define cx_strSTR_SBO_SIZE 128
 #endif
-const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
+const unsigned cx_strstr_sbo_size = cx_strSTR_SBO_SIZE;
 
 cxstring cx_strstr(
         cxstring haystack,
@@ -291,11 +269,11 @@
      */
 
     // local prefix table
-    size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
+    size_t s_prefix_table[cx_strSTR_SBO_SIZE];
 
     // check needle length and use appropriate prefix table
     // if the pattern exceeds static prefix table, allocate on the heap
-    const bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
+    const bool useheap = needle.length >= cx_strSTR_SBO_SIZE;
     register size_t *ptable = useheap
         ? cxCallocDefault(needle.length + 1, sizeof(size_t))
         : s_prefix_table;
--- a/ucx/tree.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ucx/tree.c	Thu Dec 18 17:50:15 2025 +0100
@@ -28,8 +28,6 @@
 
 #include "cx/tree.h"
 
-#include "cx/array_list.h"
-
 #include <assert.h>
 
 #define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
@@ -352,7 +350,16 @@
         }
     } else {
         // node has children, push the first child onto the stack and enter it
-        cx_array_simple_add(iter->stack, children);
+        if (iter->stack_size >= iter->stack_capacity) {
+            const size_t newcap = iter->stack_capacity + 8;
+            if (cxReallocArrayDefault(&iter->stack, newcap, sizeof(void*))) {
+                // we cannot return an error in this function
+                abort(); // LCOV_EXCL_LINE
+            }
+            iter->stack_capacity = newcap;
+        }
+        iter->stack[iter->stack_size] = children;
+        iter->stack_size++;
         iter->node = children;
         iter->counter++;
     }
@@ -717,7 +724,7 @@
     }
 
     // otherwise, create iterator and hand over to other function
-    CxIterator iter = cxIterator(src, elem_size, num, false);
+    CxIterator iter = cxIterator(src, elem_size, num);
     return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc,
                             cfunc, cdata, failed, root,
                             loc_parent, loc_children, loc_last_child,
@@ -902,7 +909,7 @@
 size_t cxTreeInsertArray(CxTree *tree, const void *data, size_t elem_size, size_t n) {
     if (n == 0) return 0;
     if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0;
-    CxIterator iter = cxIterator(data, elem_size, n, false);
+    CxIterator iter = cxIterator(data, elem_size, n);
     return cxTreeInsertIter(tree, cxIteratorRef(iter), n);
 }
 
--- a/ui/cocoa/button.m	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/cocoa/button.m	Thu Dec 18 17:50:15 2025 +0100
@@ -353,19 +353,19 @@
 static char* create_linkbutton_jsonvalue(const char *label, const char *uri, UiBool include_null, UiBool visited, UiBool set_visited) {
     CxJsonValue *obj = cxJsonCreateObj(NULL);
     if(label) {
-        cxJsonObjPutString(obj, CX_STR("label"), label);
+        cxJsonObjPutString(obj, cx_str("label"), label);
     } else if(include_null) {
-        cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL);
+        cxJsonObjPutLiteral(obj, cx_str("label"), CX_JSON_NULL);
     }
     
     if(uri) {
-        cxJsonObjPutString(obj, CX_STR("uri"), uri);
+        cxJsonObjPutString(obj, cx_str("uri"), uri);
     } else if(include_null) {
-        cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL);
+        cxJsonObjPutLiteral(obj, cx_str("uri"), CX_JSON_NULL);
     }
     
     if(set_visited) {
-        cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE);
+        cxJsonObjPutLiteral(obj, cx_str("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE);
     }
     
     CxJsonWriter writer = cxJsonWriterCompact();
--- a/ui/common/context.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/context.c	Thu Dec 18 17:50:15 2025 +0100
@@ -59,13 +59,14 @@
     memset(ctx, 0, sizeof(UiContext));
     ctx->mp = mp;
     ctx->allocator = mp->allocator;
-    ctx->destroy_handler = cxArrayListCreate(ctx->allocator, NULL, sizeof(UiDestroyHandler), 16);
+    ctx->destroy_handler = cxArrayListCreate(ctx->allocator, sizeof(UiDestroyHandler), 16);
     ctx->obj = toplevel;
     ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16);
     
-    ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, CX_STORE_POINTERS);
-    ctx->state_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiStateWidget));
-    ctx->states = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32);
+    ctx->documents = cxLinkedListCreate(mp->allocator, CX_STORE_POINTERS);
+    ctx->state_widgets = cxLinkedListCreate(mp->allocator, sizeof(UiStateWidget));
+    ctx->states = cxArrayListCreate(mp->allocator, sizeof(int), 32);
+    cxSetCompareFunc(ctx->states, cx_cmp_int);
     
     ctx->attach_document = uic_context_attach_document;
     ctx->detach_document2 = uic_context_detach_document;
@@ -173,7 +174,7 @@
 
 void uic_context_detach_all(UiContext *ctx) {
     // copy list
-    CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    CxList *ls = cxLinkedListCreate(cxDefaultAllocator, CX_STORE_POINTERS);
     CxIterator i = cxListIterator(ctx->documents);
     cx_foreach(void *, doc, i) {
         cxListAdd(ls, doc);
@@ -590,7 +591,7 @@
         enable = (ui_enablefunc)ui_set_enabled;
     }
     // get states
-    CxList *states = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+    CxList *states = cxArrayListCreate(cxDefaultAllocator, sizeof(int), 16);
     va_list ap;
     va_start(ap, enable);
     int state;
@@ -608,7 +609,7 @@
     if(enable == NULL) {
         enable = (ui_enablefunc)ui_set_enabled;
     }
-    CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), nstates);
+    CxList *ls = cxArrayListCreate(cxDefaultAllocator, sizeof(int), nstates);
     for(int i=0;i<nstates;i++) {
         cxListAdd(ls, states+i);
     }
--- a/ui/common/menu.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/menu.c	Thu Dec 18 17:50:15 2025 +0100
@@ -58,7 +58,7 @@
 }
 
 void uic_menu_init(void) {
-    global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    global_builder.current = cxLinkedListCreate(cxDefaultAllocator,  CX_STORE_POINTERS);
     current_builder = &global_builder;
 }
 
@@ -270,7 +270,7 @@
     UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder));
     builder->menus_begin = NULL;
     builder->menus_end = NULL;
-    builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    builder->current = cxLinkedListCreate(cxDefaultAllocator, CX_STORE_POINTERS);
     builder->ref = 1;
     current_builder = builder;
     *out_builder = builder;
--- a/ui/common/message.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/message.c	Thu Dec 18 17:50:15 2025 +0100
@@ -46,7 +46,7 @@
     handler->handler.callback = callback;
     handler->in = in;
     handler->out = out;
-    handler->outbuf = cxBufferCreate(NULL, 4096, NULL, CX_BUFFER_FREE_CONTENTS | CX_BUFFER_AUTO_EXTEND);
+    handler->outbuf = cxBufferCreate(NULL, NULL, 4096, CX_BUFFER_FREE_CONTENTS | CX_BUFFER_AUTO_EXTEND);
     handler->stop = 0;
     pthread_mutex_init(&handler->queue_lock, NULL);
     pthread_mutex_init(&handler->avlbl_lock, NULL);
--- a/ui/common/object.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/object.c	Thu Dec 18 17:50:15 2025 +0100
@@ -46,7 +46,7 @@
 
 void ui_register_object_creation_callback(ui_object_callback func, void *userdata) {
     if(!creation_callbacks) {
-        creation_callbacks = cxLinkedListCreateSimple(sizeof(objcallback));
+        creation_callbacks = cxLinkedListCreate(NULL, sizeof(objcallback));
     }
     objcallback cb = { func, userdata };
     cxListAdd(creation_callbacks, &cb);
@@ -54,7 +54,7 @@
 
 void ui_register_object_destruction_callback(ui_object_callback func, void *userdata) {
     if(!destruction_callbacks) {
-        destruction_callbacks = cxLinkedListCreateSimple(sizeof(objcallback));
+        destruction_callbacks = cxLinkedListCreate(NULL, sizeof(objcallback));
     }
     objcallback cb = { func, userdata };
     cxListAdd(destruction_callbacks, &cb);
--- a/ui/common/properties.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/properties.c	Thu Dec 18 17:50:15 2025 +0100
@@ -101,7 +101,7 @@
     }
     
     CxBuffer buf;
-    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    cxBufferInit(&buf, cxDefaultAllocator, NULL, 128, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     
     // add base dir
     char *homeenv = getenv(UI_ENV_HOME);
@@ -336,7 +336,7 @@
 static char* uic_concat_path(const char *base, const char *p, const char *ext) {
     size_t baselen = strlen(base);
     
-    CxBuffer *buf = cxBufferCreate(NULL, 32, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
+    CxBuffer *buf = cxBufferCreate(cxDefaultAllocator, NULL, 32, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     if(baselen > 0) {
         cxBufferWrite(base, 1, baselen, buf);
         if(base[baselen - 1] != '/') {
--- a/ui/common/toolbar.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/toolbar.c	Thu Dec 18 17:50:15 2025 +0100
@@ -42,7 +42,7 @@
 void uic_toolbar_init(void) {
     toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
     for(int i=0;i<8;i++) {
-        toolbar_defaults[i] = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+        toolbar_defaults[i] = cxLinkedListCreate(NULL, CX_STORE_POINTERS);
     }
 }
 
--- a/ui/common/types.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/types.c	Thu Dec 18 17:50:15 2025 +0100
@@ -100,7 +100,7 @@
 /* --------------------------- UiList --------------------------- */
 
 void uic_ucx_list_init(UiContext *ctx, UiList *list, void *unused) {
-    list->data = cxArrayListCreate(ctx->mp->allocator, NULL, CX_STORE_POINTERS, 32);
+    list->data = cxArrayListCreate(ctx->mp->allocator, CX_STORE_POINTERS, 32);
     list->first = ui_list_first;
     list->next = ui_list_next;
     list->get = ui_list_get;
@@ -217,7 +217,7 @@
     va_list ap;
     va_start(ap, ctx);
     
-    CxList *cols = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(UiColumn), 32);
+    CxList *cols = cxArrayListCreate(cxDefaultAllocator, sizeof(UiColumn), 32);
     int type;
     while((type = va_arg(ap, int)) != -1) {
         char *name = va_arg(ap, char*);
--- a/ui/common/utils.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/common/utils.c	Thu Dec 18 17:50:15 2025 +0100
@@ -37,7 +37,7 @@
 
 UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
     cxstring *pathelms;
-    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
+    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), cx_str("/"), 4096, &pathelms);
 
     if (nelm == 0) {
         *ret_nelm = 0;
@@ -104,7 +104,7 @@
             size_t capa = str.length + 32;
             char *space = cxMallocDefault(capa);
             if (space == NULL) return cx_mutstrn(NULL, 0);
-            cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND);
+            cxBufferInit(&buf, NULL, space, capa, CX_BUFFER_AUTO_EXTEND);
             cxBufferWrite(str.ptr, 1, i, &buf);
             all_printable = false;
         }
--- a/ui/gtk/button.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/button.c	Thu Dec 18 17:50:15 2025 +0100
@@ -787,24 +787,24 @@
 static char* create_linkbutton_jsonvalue(const char *label, const char *uri, gboolean include_null, gboolean visited, gboolean set_visited) {
     CxJsonValue *obj = cxJsonCreateObj(NULL);
     if(label) {
-        cxJsonObjPutString(obj, CX_STR("label"), label);
+        cxJsonObjPutString(obj, cx_str("label"), label);
     } else if(include_null) {
-        cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL);
+        cxJsonObjPutLiteral(obj, cx_str("label"), CX_JSON_NULL);
     }
     
     if(uri) {
-        cxJsonObjPutString(obj, CX_STR("uri"), uri);
+        cxJsonObjPutString(obj, cx_str("uri"), uri);
     } else if(include_null) {
-        cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL);
+        cxJsonObjPutLiteral(obj, cx_str("uri"), CX_JSON_NULL);
     }
     
     if(set_visited) {
-        cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE);
+        cxJsonObjPutLiteral(obj, cx_str("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE);
     }
     
     CxJsonWriter writer = cxJsonWriterCompact();
     CxBuffer buf;
-    cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND);
+    cxBufferInit(&buf, NULL, NULL, 128, CX_BUFFER_AUTO_EXTEND);
     cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer);
     cxJsonValueFree(obj);
     cxBufferTerminate(&buf);
--- a/ui/gtk/container.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/container.c	Thu Dec 18 17:50:15 2025 +0100
@@ -1162,7 +1162,7 @@
     ct->orientation = orientation;
     ct->max = max;
     ct->initial_position = init;
-    ct->children = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
+    ct->children = cxArrayListCreate(NULL, CX_STORE_POINTERS, 4);
     return ct;
 }
 
@@ -1246,7 +1246,7 @@
 static void update_itemlist(UiList *list, int c) {
     UiGtkItemListContainer *ct = list->obj;
     
-    CxMap *new_items = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    CxMap *new_items = cxHashMapCreate(NULL, CX_STORE_POINTERS, 32);
     new_items->collection.advanced_destructor = remove_item;
     new_items->collection.destructor_data = ct;
     
@@ -1331,7 +1331,7 @@
     container->create_ui = args->create_ui;
     container->userdata = args->userdata;
     container->subcontainer = args->subcontainer;
-    container->current_items = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    container->current_items = cxHashMapCreate(NULL, CX_STORE_POINTERS, 32);
     container->current_items->collection.advanced_destructor = remove_item;
     container->current_items->collection.destructor_data = container;
     container->margin = args->sub_margin;
--- a/ui/gtk/dnd.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/dnd.c	Thu Dec 18 17:50:15 2025 +0100
@@ -184,7 +184,7 @@
 UiDnD* ui_create_dnd(void) {
     UiDnD *dnd = malloc(sizeof(UiDnD));
     memset(dnd, 0, sizeof(UiDnD));
-    dnd->providers = cxArrayListCreateSimple(sizeof(void*), 16);
+    dnd->providers = cxArrayListCreate(NULL, sizeof(void*), 16);
     dnd->selected_action = 0;
     dnd->delete = FALSE;
     return dnd;
--- a/ui/gtk/icon.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/icon.c	Thu Dec 18 17:50:15 2025 +0100
@@ -46,7 +46,7 @@
 #endif
 
 void ui_image_init(void) {
-    image_map = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    image_map = cxHashMapCreate(NULL, CX_STORE_POINTERS, 16);
     
     icon_theme = ICONTHEME_GET_DEFAULT();
 }
--- a/ui/gtk/list.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/list.c	Thu Dec 18 17:50:15 2025 +0100
@@ -822,20 +822,19 @@
     view->selection.count = 0;
     view->selection.rows = NULL;
     
-    CX_ARRAY_DECLARE(int, newselection);
-    cx_array_initialize(newselection, 8);
-     
     size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore));
+    int *newselection = calloc(nitems, sizeof(int));
+    int selection_size = 0;
     
     for(size_t i=0;i<nitems;i++) {
         if(gtk_selection_model_is_selected(view->selectionmodel, i)) {
             int s = (int)i;
-            cx_array_simple_add(newselection, s);
+            newselection[selection_size++] = s;
         }
     }
     
-    if(newselection_size > 0) {
-        view->selection.count = newselection_size;
+    if(selection_size > 0) {
+        view->selection.count = selection_size;
         view->selection.rows = newselection;
     } else {
         free(newselection);
@@ -2234,7 +2233,7 @@
     uisublist.numitems = 0;
     uisublist.header = sublist->header ? strdup(sublist->header) : NULL;
     uisublist.separator = sublist->separator;
-    uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    uisublist.widgets = cxLinkedListCreate(NULL, CX_STORE_POINTERS);
     uisublist.listbox = uilistbox;
     uisublist.userdata = sublist->userdata;
     uisublist.index = cxListSize(sublists);
@@ -2285,7 +2284,7 @@
     uilistbox->onactivatedata = args->onactivatedata;
     uilistbox->onbuttonclick = args->onbuttonclick;
     uilistbox->onbuttonclickdata = args->onbuttonclickdata;
-    uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4);
+    uilistbox->sublists = cxArrayListCreate(NULL, sizeof(UiListBoxSubList), 4);
     uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy;
     uilistbox->sublists->collection.destructor_data = obj;
     uilistbox->first_row = NULL;
@@ -2373,7 +2372,7 @@
     }
     
     cxListFree(uilistbox->sublists);
-    CxList *new_sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), list->count(list));
+    CxList *new_sublists = cxArrayListCreate(NULL, sizeof(UiListBoxSubList), list->count(list));
     uilistbox->sublists = new_sublists;
     
     UiSubList *sublist = list->first(list);
--- a/ui/gtk/menu.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/menu.c	Thu Dec 18 17:50:15 2025 +0100
@@ -130,7 +130,7 @@
     gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
     
     if(i->states) {
-        CxList *states = cxArrayListCreateSimple(sizeof(int), i->nstates);
+        CxList *states = cxArrayListCreate(NULL, sizeof(int), i->nstates);
         cxListAddArray(states, i->states, i->nstates);
         uic_add_state_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, states);
         cxListFree(states);
@@ -220,7 +220,7 @@
         // binding object is a list of all connected UiActiveMenuItemList.
         CxList *bindings = list->obj;
         if(!bindings) {
-            bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS);
+            bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, CX_STORE_POINTERS);
             list->obj = bindings;
         }
         cxListAdd(bindings, ls);
@@ -463,7 +463,7 @@
     g_object_unref(action);
     
     if(i->states) {
-        CxList *groups = cxArrayListCreateSimple(sizeof(int), i->nstates);
+        CxList *groups = cxArrayListCreate(NULL, sizeof(int), i->nstates);
         cxListAddArray(groups, i->states, i->nstates);
         uic_add_state_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups);
         cxListFree(groups);
@@ -569,7 +569,7 @@
 
 static UiMenuRadioGroup* create_radio_group(UiObject *obj, UiMenuRadioItem *item, GSimpleAction *action) {
     UiMenuRadioGroup *group = cxZalloc(obj->ctx->allocator, sizeof(UiMenuRadioGroup));
-    group->callbacks = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(UiCallbackData), 8);
+    group->callbacks = cxArrayListCreate(obj->ctx->allocator, sizeof(UiCallbackData), 8);
     group->var = uic_create_var(ui_global_context(), item->varname, UI_VAR_INTEGER);
     group->obj = obj;
     group->action = action;
@@ -577,7 +577,7 @@
         UiInteger *i = group->var->value;
         CxList *bindings = i->obj;
         if(!bindings) {
-            bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS);
+            bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, CX_STORE_POINTERS);
             i->obj = bindings;
             i->set = ui_action_set_state;
             i->get = ui_action_get_state;
@@ -711,7 +711,7 @@
         // binding object is a list of all connected UiActiveMenuItemList.
         CxList *bindings = list->obj;
         if(!bindings) {
-            bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS);
+            bindings = cxLinkedListCreate(var->from_ctx->mp->allocator, CX_STORE_POINTERS);
             list->obj = bindings;
         }
         cxListAdd(bindings, ls);
--- a/ui/gtk/text.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/text.c	Thu Dec 18 17:50:15 2025 +0100
@@ -756,7 +756,7 @@
 // TODO: move to common
 static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
     cxstring *pathelms;
-    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
+    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), cx_str("/"), 4096, &pathelms);
 
     if (nelm == 0) {
         *ret_nelm = 0;
@@ -1042,7 +1042,7 @@
         
         gtk_box_append(GTK_BOX(pathtf->hbox), button);
         
-        if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), CX_STR("/"))) {
+        if(i+1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len), cx_str("/"))) {
             GtkWidget *path_separator = gtk_label_new("/");
             gtk_widget_add_css_class(path_separator, "pathbar-button-inactive");
             gtk_box_append(GTK_BOX(pathtf->hbox), path_separator);
--- a/ui/gtk/toolkit.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/gtk/toolkit.c	Thu Dec 18 17:50:15 2025 +0100
@@ -489,7 +489,7 @@
     }
     if(style_classes) {
         cxstring *cls = NULL;
-        size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), CX_STR(" "), 128, &cls);
+        size_t numClasses = cx_strsplit_a(cxDefaultAllocator, cx_str(style_classes), cx_str(" "), 128, &cls);
         for(int i=0;i<numClasses;i++) {
             cxmutstr m = cx_strdup(cls[i]);
 #if GTK_MAJOR_VERSION >= 4
--- a/ui/motif/button.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/motif/button.c	Thu Dec 18 17:50:15 2025 +0100
@@ -304,7 +304,7 @@
         if(!rb) {
             // first button in the radiobutton group
             // create a list for all buttons and use the list as value obj
-            rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
+            rb = cxArrayListCreate(NULL, CX_STORE_POINTERS, 4);
             value->obj = rb;
             value->get = ui_radiobutton_get;
             value->set = ui_radiobutton_set;
@@ -374,7 +374,7 @@
         if(!rb) {
             // first button in the radiobutton group
             // create a list for all buttons and use the list as value obj
-            rb = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
+            rb = cxArrayListCreate(NULL, CX_STORE_POINTERS, 4);
             value->obj = rb;
             value->get = ui_radiobutton_get;
             value->set = ui_radiobutton_set;
--- a/ui/motif/container.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/motif/container.c	Thu Dec 18 17:50:15 2025 +0100
@@ -448,7 +448,7 @@
     tabview->select = ui_motif_tabview_select;
     tabview->add = ui_motif_tabview_add_tab;
     tabview->remove = ui_motif_tabview_remove;
-    tabview->tabs = cxArrayListCreate(obj->ctx->allocator, cx_cmp_ptr, sizeof(UiTab), 8);
+    tabview->tabs = cxArrayListCreate(obj->ctx->allocator, sizeof(UiTab), 8);
     tabview->current_index = -1;
     
     UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer));
--- a/ui/motif/menu.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/motif/menu.c	Thu Dec 18 17:50:15 2025 +0100
@@ -244,7 +244,7 @@
         // binding object is a list of all connected UiActiveMenuItemList.
         CxList *bindings = list->obj;
         if(!bindings) {
-            bindings = cxLinkedListCreate(ls->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS);
+            bindings = cxLinkedListCreate(ls->var->from_ctx->mp->allocator, CX_STORE_POINTERS);
             list->obj = bindings;
         }
         cxListAdd(bindings, ls);
--- a/ui/motif/pathbar.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/motif/pathbar.c	Thu Dec 18 17:50:15 2025 +0100
@@ -170,7 +170,7 @@
 
 static cxmutstr concat_path_s(cxstring base, cxstring path) {
     if(!path.ptr) {
-        path = CX_STR("");
+        path = cx_str("");
     }
     
     int add_separator = 0;
@@ -186,7 +186,7 @@
     
     cxmutstr url;
     if(add_separator) {
-        url = cx_strcat(3, base, CX_STR("/"), path);
+        url = cx_strcat(3, base, cx_str("/"), path);
     } else {
         url = cx_strcat(2, base, path);
     }
--- a/ui/server/button.c	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/server/button.c	Thu Dec 18 17:50:15 2025 +0100
@@ -74,7 +74,7 @@
 UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) {
     UiCallbackWidget *widget = cxZalloc(obj->ctx->allocator, sizeof(UiCallbackWidget));
     widget->widget.obj = obj->widget->obj;
-    widget->widget.type = CX_STR("button");
+    widget->widget.type = cx_str("button");
     widget->widget.args = ui_button_args_to_string(obj->ctx, args);
     widget->widget.serialize = (ui_serialize_func)ui_button_serialize;
     widget->callback = args->onclick;
--- a/ui/winui/text.cpp	Wed Dec 17 18:31:20 2025 +0100
+++ b/ui/winui/text.cpp	Thu Dec 18 17:50:15 2025 +0100
@@ -327,7 +327,7 @@
 
 static UiPathElm* default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) {
     cxstring *pathelms;
-    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms);
+    size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), cx_str("/"), 4096, &pathelms);
 
     if (nelm == 0) {
         *ret_nelm = 0;

mercurial