ucx/list.c

changeset 888
af685cc9d623
parent 854
1c8401ece69e
--- a/ucx/list.c	Sun Aug 31 14:39:13 2025 +0200
+++ b/ucx/list.c	Sat Nov 08 23:06:11 2025 +0100
@@ -29,6 +29,7 @@
 #include "cx/list.h"
 
 #include <string.h>
+#include <assert.h>
 
 // <editor-fold desc="Store Pointers Functionality">
 
@@ -38,10 +39,18 @@
         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 == NULL ? NULL : *lptr;
-    const void *right = rptr == NULL ? NULL : *rptr;
+    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;
+    }
     return cx_pl_cmpfunc_impl(left, right);
 }
 
@@ -62,7 +71,7 @@
     list->climpl->deallocate(list);
 }
 
-static int cx_pl_insert_element(
+static void *cx_pl_insert_element(
         struct cx_list_s *list,
         size_t index,
         const void *element
@@ -90,12 +99,23 @@
     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.m;
+    struct cx_list_s *list = iter->src_handle;
     return list->climpl->insert_iter(iter, &elem, prepend);
 }
 
@@ -181,6 +201,7 @@
         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,
@@ -225,7 +246,7 @@
         cx_attr_unused bool backwards
 ) {
     CxIterator iter = {0};
-    iter.src_handle.c = list;
+    iter.src_handle = (void*) list;
     iter.index = index;
     iter.base.valid = cx_emptyl_iter_valid;
     return iter;
@@ -238,6 +259,7 @@
         NULL,
         NULL,
         NULL,
+        NULL,
         cx_emptyl_noop,
         NULL,
         cx_emptyl_at,
@@ -278,21 +300,26 @@
         const void *data,
         size_t n
 ) {
-    size_t elem_size = list->collection.elem_size;
     const char *src = data;
     size_t i = 0;
     for (; i < n; i++) {
-        if (0 != invoke_list_func(
-            insert_element, list, index + i,
-            src + (i * elem_size))) return i;
+        if (NULL == invoke_list_func(
+            insert_element, list, index + i, src)
+        ) {
+            return i; // LCOV_EXCL_LINE
+        }
+        if (src != NULL) {
+            src += list->collection.elem_size;
+        }
     }
     return i;
 }
 
-size_t cx_list_default_insert_sorted(
+static size_t cx_list_default_insert_sorted_impl(
         struct cx_list_s *list,
         const void *sorted_data,
-        size_t n
+        size_t n,
+        bool allow_duplicates
 ) {
     // corner case
     if (n == 0) return 0;
@@ -302,22 +329,54 @@
     const char *src = sorted_data;
 
     // track indices and number of inserted items
-    size_t di = 0, si = 0, inserted = 0;
+    size_t di = 0, si = 0, processed = 0;
 
     // search the list for insertion points
-    for (; di < list->collection.size; di++) {
+    while (di < list->collection.size) {
         const void *list_elm = invoke_list_func(at, list, di);
 
-        // compare current list element with first source element
-        // if less or equal, skip
-        if (cmp(list_elm, src) <= 0) {
-            continue;
+        // 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);
+            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) {
+                        src += elem_size;
+                        si++;
+                        processed++;
+                    }
+                    if (processed == n) {
+                        return processed;
+                    }
+                }
+                di++;
+                continue;
+            }
         }
 
-        // determine number of consecutive elements that can be inserted
-        size_t ins = 1;
+        // determine the number of consecutive elements that can be inserted
+        size_t ins = 1, skip = 0;
         const char *next = src;
         while (++si < n) {
+            if (!allow_duplicates) {
+                // skip duplicates within the source
+                if (cmp(next, next + elem_size) == 0) {
+                    next += elem_size;
+                    skip++;
+                    continue;
+                } else {
+                    if (skip > 0) {
+                        // if we had to skip something, we must wait for the next run
+                        next += elem_size;
+                        break;
+                    }
+                }
+            }
             next += elem_size;
             // once we become larger than the list elem, break
             if (cmp(list_elm, next) <= 0) {
@@ -329,33 +388,70 @@
 
         // insert the elements at location si
         if (ins == 1) {
-            if (0 != invoke_list_func(
-                insert_element, list, di, src)) return inserted;
+            if (NULL == invoke_list_func(insert_element, list, di, src)) {
+                return processed; // LCOV_EXCL_LINE
+            }
         } else {
             size_t r = invoke_list_func(insert_array, list, di, src, ins);
-            if (r < ins) return inserted + r;
+            if (r < ins) {
+                return processed + r;  // LCOV_EXCL_LINE
+            }
         }
-        inserted += ins;
+        processed += ins + skip;
         di += ins;
 
         // everything inserted?
-        if (inserted == n) return inserted;
+        if (processed == n) {
+            return processed;
+        }
         src = next;
     }
 
     // insert remaining items
     if (si < n) {
-        inserted += invoke_list_func(insert_array, list, di, src, n - si);
+        if (allow_duplicates) {
+            processed += invoke_list_func(insert_array, list, di, src, n - si);
+        } else {
+            const void *last = di == 0 ? NULL : invoke_list_func(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)) {
+                        return processed; // LCOV_EXCL_LINE
+                    }
+                    last = src;
+                    di++;
+                }
+                processed++;
+                src += elem_size;
+            }
+        }
     }
 
-    return inserted;
+    return processed;
+}
+
+size_t cx_list_default_insert_sorted(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+) {
+    return cx_list_default_insert_sorted_impl(list, sorted_data, n, true);
+}
+
+size_t cx_list_default_insert_unique(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n
+) {
+    return cx_list_default_insert_sorted_impl(list, sorted_data, n, false);
 }
 
 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;
-    void *tmp = malloc(elem_size * list_size);
-    if (tmp == NULL) abort();
+    void *tmp = cxMallocDefault(elem_size * list_size);
+    if (tmp == NULL) abort(); // LCOV_EXCL_LINE
 
     // copy elements from source array
     char *loc = tmp;
@@ -377,7 +473,7 @@
         loc += elem_size;
     }
 
-    free(tmp);
+    cxFreeDefault(tmp);
 }
 
 int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) {
@@ -387,8 +483,8 @@
 
     size_t elem_size = list->collection.elem_size;
 
-    void *tmp = malloc(elem_size);
-    if (tmp == NULL) return 1;
+    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);
@@ -397,7 +493,7 @@
     memcpy(ip, jp, elem_size);
     memcpy(jp, tmp, elem_size);
 
-    free(tmp);
+    cxFreeDefault(tmp);
 
     return 0;
 }
@@ -472,25 +568,513 @@
     }
 }
 
-CxIterator cxListMutIteratorAt(
-        CxList *list,
-        size_t index
-) {
-    CxIterator it = list->cl->iterator(list, index, false);
-    it.base.mutating = true;
-    return it;
+size_t cxListSize(const CxList *list) {
+    return list->collection.size;
+}
+
+int cxListAdd(CxList *list, const void *elem) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, list->collection.size, elem) == NULL;
+}
+
+size_t cxListAddArray(CxList *list, const void *array, size_t n) {
+    list->collection.sorted = false;
+    return list->cl->insert_array(list, list->collection.size, array, n);
+}
+
+int cxListInsert(CxList *list, size_t index, const void *elem) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, index, elem) == NULL;
+}
+
+void *cxListEmplaceAt(CxList *list, size_t index) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, index, NULL);
+}
+
+void *cxListEmplace(CxList *list) {
+    list->collection.sorted = false;
+    return list->cl->insert_element(list, list->collection.size, NULL);
+}
+
+static bool cx_list_emplace_iterator_valid(const void *it) {
+    const CxIterator *iter = it;
+    return iter->index < iter->elem_count;
+}
+
+CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n) {
+    list->collection.sorted = false;
+    size_t c = list->cl->insert_array(list, index, NULL, n);
+    CxIterator iter = list->cl->iterator(list, index, false);
+    // tweak the fields of this iterator
+    iter.elem_count = c;
+    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;
+}
+
+CxIterator cxListEmplaceArray(CxList *list, size_t n) {
+    return cxListEmplaceArrayAt(list, list->collection.size, n);
+}
+
+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;
+}
+
+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;
+    } else {
+        if (cxListContains(list, elem)) {
+            return 0;
+        } else {
+            return cxListAdd(list, elem);
+        }
+    }
+}
+
+size_t cxListInsertArray(CxList *list, size_t index, const void *array, size_t n) {
+    list->collection.sorted = false;
+    return list->cl->insert_array(list, index, array, n);
+}
+
+size_t cxListInsertSortedArray(CxList *list, const void *array, size_t n) {
+    assert(cxCollectionSorted(list));
+    list->collection.sorted = true;
+    return list->cl->insert_sorted(list, array, n);
+}
+
+size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n) {
+    if (cxCollectionSorted(list)) {
+        list->collection.sorted = true;
+        return list->cl->insert_unique(list, array, n);
+    } else {
+        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;
+            if (!cxListContains(list, data)) {
+                if (cxListAdd(list, data)) {
+                    return i; // LCOV_EXCL_LINE
+                }
+            }
+            source += list->collection.elem_size;
+        }
+        return n;
+    }
+}
+
+int cxListInsertAfter(CxIterator *iter, const void *elem) {
+    CxList* list = (CxList*)iter->src_handle;
+    list->collection.sorted = false;
+    return list->cl->insert_iter(iter, elem, 0);
 }
 
-CxIterator cxListMutBackwardsIteratorAt(
-        CxList *list,
-        size_t index
-) {
-    CxIterator it = list->cl->iterator(list, index, true);
-    it.base.mutating = true;
-    return it;
+int cxListInsertBefore(CxIterator *iter, const void *elem) {
+    CxList* list = (CxList*)iter->src_handle;
+    list->collection.sorted = false;
+    return list->cl->insert_iter(iter, elem, 1);
+}
+
+int cxListRemove(CxList *list, size_t index) {
+    return list->cl->remove(list, index, 1, NULL) == 0;
+}
+
+int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf) {
+    return list->cl->remove(list, index, 1, targetbuf) == 0;
+}
+
+int cxListRemoveAndGetFirst(CxList *list, void *targetbuf) {
+    return list->cl->remove(list, 0, 1, targetbuf) == 0;
+}
+
+int cxListRemoveAndGetLast(CxList *list, void *targetbuf) {
+    // note: index may wrap - member function will catch that
+    return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0;
+}
+
+size_t cxListRemoveArray(CxList *list, size_t index, size_t num) {
+    return list->cl->remove(list, index, num, NULL);
+}
+
+size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf) {
+    return list->cl->remove(list, index, num, targetbuf);
+}
+
+void cxListClear(CxList *list) {
+    list->cl->clear(list);
+    list->collection.sorted = true; // empty lists are always sorted
+}
+
+int cxListSwap(CxList *list, size_t i, size_t j) {
+    list->collection.sorted = false;
+    return list->cl->swap(list, i, j);
+}
+
+void *cxListAt(const CxList *list, size_t index) {
+    return list->cl->at(list, index);
+}
+
+void *cxListFirst(const CxList *list) {
+    return list->cl->at(list, 0);
+}
+
+void *cxListLast(const CxList *list) {
+    return list->cl->at(list, list->collection.size - 1);
+}
+
+int cxListSet(CxList *list, size_t index, const void *elem) {
+    if (index >= list->collection.size) {
+        return 1;
+    }
+
+    if (list->collection.store_pointer) {
+        // For pointer collections, always use climpl
+        void **target = list->climpl->at(list, index);
+        *target = (void *)elem;
+    } else {
+        void *target = list->cl->at(list, index);
+        memcpy(target, elem, list->collection.elem_size);
+    }
+
+    return 0;
+}
+
+CxIterator cxListIteratorAt(const CxList *list, size_t index) {
+    if (list == NULL) list = cxEmptyList;
+    return 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);
+}
+
+CxIterator cxListIterator(const CxList *list) {
+    if (list == NULL) list = cxEmptyList;
+    return 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);
+}
+
+size_t cxListFind(const CxList *list, const void *elem) {
+    return list->cl->find_remove((CxList*)list, elem, false);
+}
+
+bool cxListContains(const CxList* list, const void* elem) {
+    return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size;
+}
+
+bool cxListIndexValid(const CxList *list, size_t index) {
+    return index < list->collection.size;
+}
+
+size_t cxListFindRemove(CxList *list, const void *elem) {
+    return list->cl->find_remove(list, elem, true);
+}
+
+void cxListSort(CxList *list) {
+    if (list->collection.sorted) return;
+    list->cl->sort(list);
+    list->collection.sorted = true;
+}
+
+void cxListReverse(CxList *list) {
+    // still sorted, but not according to the cmp_func
+    list->collection.sorted = false;
+    list->cl->reverse(list);
 }
 
 void cxListFree(CxList *list) {
     if (list == NULL) return;
     list->cl->deallocate(list);
 }
+
+static void cx_list_pop_uninitialized_elements(CxList *list, size_t n) {
+    cx_destructor_func destr_bak = list->collection.simple_destructor;
+    cx_destructor_func2 destr2_bak = list->collection.advanced_destructor;
+    list->collection.simple_destructor = NULL;
+    list->collection.advanced_destructor = NULL;
+    if (n == 1) {
+        cxListRemove(list, list->collection.size - 1);
+    } else {
+        cxListRemoveArray(list,list->collection.size - n, n);
+    }
+    list->collection.simple_destructor = destr_bak;
+    list->collection.advanced_destructor = destr2_bak;
+}
+
+int cxListClone(CxList *dst, const CxList *src, cx_clone_func clone_func,
+        const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // remember the original size
+    size_t orig_size = dst->collection.size;
+
+    // first, try to allocate the memory in the new list
+    CxIterator empl_iter = cxListEmplaceArray(dst, src->collection.size);
+
+    // get an iterator over the source elements
+    CxIterator src_iter = cxListIterator(src);
+
+    // now clone the elements
+    size_t cloned = empl_iter.elem_count;
+    for (size_t i = 0 ; i < empl_iter.elem_count; i++) {
+        void *src_elem = cxIteratorCurrent(src_iter);
+        void **dest_memory = cxIteratorCurrent(empl_iter);
+        void *target = cxCollectionStoresPointers(dst) ? NULL : dest_memory;
+        void *dest_ptr = clone_func(target, src_elem, clone_allocator, data);
+        if (dest_ptr == NULL) {
+            cloned = i;
+            break;
+        }
+        if (cxCollectionStoresPointers(dst)) {
+            *dest_memory = dest_ptr;
+        }
+        cxIteratorNext(src_iter);
+        cxIteratorNext(empl_iter);
+    }
+
+    // if we could not clone everything, free the allocated memory
+    // (disable the destructors!)
+    if (cloned < src->collection.size) {
+        cx_list_pop_uninitialized_elements(dst,
+            dst->collection.size - cloned - orig_size);
+        return 1;
+    }
+
+    return 0;
+}
+
+int cxListDifference(CxList *dst,
+        const CxList *minuend, const CxList *subtrahend,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // optimize for sorted collections
+    if (cxCollectionSorted(minuend) && cxCollectionSorted(subtrahend)) {
+        bool dst_was_empty = cxCollectionSize(dst) == 0;
+
+        CxIterator min_iter = cxListIterator(minuend);
+        CxIterator sub_iter = cxListIterator(subtrahend);
+        while (cxIteratorValid(min_iter)) {
+            void *min_elem = cxIteratorCurrent(min_iter);
+            void *sub_elem;
+            int d;
+            if (cxIteratorValid(sub_iter)) {
+                sub_elem = cxIteratorCurrent(sub_iter);
+                cx_compare_func cmp = subtrahend->collection.cmpfunc;
+                d = cmp(sub_elem, min_elem);
+            } else {
+                // no more elements in the subtrahend,
+                // i.e., the min_elem is larger than any elem of the subtrahend
+                d = 1;
+            }
+            if (d == 0) {
+                // is contained, so skip it
+                cxIteratorNext(min_iter);
+            } else if (d < 0) {
+                // subtrahend is smaller than minuend,
+                // check the next element
+                cxIteratorNext(sub_iter);
+            } else {
+                // subtrahend is larger than the dst element,
+                // clone the minuend and advance
+                void **dst_mem = cxListEmplace(dst);
+                void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+                void* dst_ptr = clone_func(target, min_elem, clone_allocator, data);
+                if (dst_ptr == NULL) {
+                    cx_list_pop_uninitialized_elements(dst, 1);
+                    return 1;
+                }
+                if (cxCollectionStoresPointers(dst)) {
+                    *dst_mem = dst_ptr;
+                }
+                cxIteratorNext(min_iter);
+            }
+        }
+
+        // if dst was empty, it is now guaranteed to be sorted
+        dst->collection.sorted = dst_was_empty;
+    } else {
+        CxIterator min_iter = cxListIterator(minuend);
+        cx_foreach(void *, elem, min_iter) {
+            if (cxListContains(subtrahend, elem)) {
+                continue;
+            }
+            void **dst_mem = cxListEmplace(dst);
+            void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+            void* dst_ptr = clone_func(target, elem, clone_allocator, data);
+            if (dst_ptr == NULL) {
+                cx_list_pop_uninitialized_elements(dst, 1);
+                return 1;
+            }
+            if (cxCollectionStoresPointers(dst)) {
+                *dst_mem = dst_ptr;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int cxListIntersection(CxList *dst,
+        const CxList *src, const CxList *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // optimize for sorted collections
+    if (cxCollectionSorted(src) && cxCollectionSorted(other)) {
+        bool dst_was_empty = cxCollectionSize(dst) == 0;
+
+        CxIterator src_iter = cxListIterator(src);
+        CxIterator other_iter = cxListIterator(other);
+        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);
+            if (d == 0) {
+                // is contained, clone it
+                void **dst_mem = cxListEmplace(dst);
+                void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+                void* dst_ptr = clone_func(target, src_elem, clone_allocator, data);
+                if (dst_ptr == NULL) {
+                    cx_list_pop_uninitialized_elements(dst, 1);
+                    return 1;
+                }
+                if (cxCollectionStoresPointers(dst)) {
+                    *dst_mem = dst_ptr;
+                }
+                cxIteratorNext(src_iter);
+            } else if (d < 0) {
+                // the other element is larger, skip the source element
+                cxIteratorNext(src_iter);
+            } else {
+                // the source element is larger, try to find it in the other list
+                cxIteratorNext(other_iter);
+            }
+        }
+
+        // if dst was empty, it is now guaranteed to be sorted
+        dst->collection.sorted = dst_was_empty;
+    } else {
+        CxIterator src_iter = cxListIterator(src);
+        cx_foreach(void *, elem, src_iter) {
+            if (!cxListContains(other, elem)) {
+                continue;
+            }
+            void **dst_mem = cxListEmplace(dst);
+            void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+            void* dst_ptr = clone_func(target, elem, clone_allocator, data);
+            if (dst_ptr == NULL) {
+                cx_list_pop_uninitialized_elements(dst, 1);
+                return 1;
+            }
+            if (cxCollectionStoresPointers(dst)) {
+                *dst_mem = dst_ptr;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int cxListUnion(CxList *dst,
+        const CxList *src, const CxList *other,
+        cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) {
+    if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator;
+
+    // optimize for sorted collections
+    if (cxCollectionSorted(src) && cxCollectionSorted(other)) {
+        bool dst_was_empty = cxCollectionSize(dst) == 0;
+
+        CxIterator src_iter = cxListIterator(src);
+        CxIterator other_iter = cxListIterator(other);
+        while (cxIteratorValid(src_iter) || cxIteratorValid(other_iter)) {
+            void *src_elem, *other_elem;
+            int d;
+            if (!cxIteratorValid(src_iter)) {
+                other_elem = cxIteratorCurrent(other_iter);
+                d = 1;
+            } else if (!cxIteratorValid(other_iter)) {
+                src_elem = cxIteratorCurrent(src_iter);
+                d = -1;
+            } else {
+                src_elem = cxIteratorCurrent(src_iter);
+                other_elem = cxIteratorCurrent(other_iter);
+                d = src->collection.cmpfunc(src_elem, other_elem);
+            }
+            if (d <= 0) {
+                // source element is smaller or equal, clone it
+                void **dst_mem = cxListEmplace(dst);
+                void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+                void* dst_ptr = clone_func(target, src_elem, clone_allocator, data);
+                if (dst_ptr == NULL) {
+                    cx_list_pop_uninitialized_elements(dst, 1);
+                    return 1;
+                }
+                if (cxCollectionStoresPointers(dst)) {
+                    *dst_mem = dst_ptr;
+                }
+                cxIteratorNext(src_iter);
+                // if the other element was equal, skip it
+                if (d == 0) {
+                    cxIteratorNext(other_iter);
+                }
+            } else {
+                // the other element is smaller, clone it
+                void **dst_mem = cxListEmplace(dst);
+                void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+                void* dst_ptr = clone_func(target, other_elem, clone_allocator, data);
+                if (dst_ptr == NULL) {
+                    cx_list_pop_uninitialized_elements(dst, 1);
+                    return 1;
+                }
+                if (cxCollectionStoresPointers(dst)) {
+                    *dst_mem = dst_ptr;
+                }
+                cxIteratorNext(other_iter);
+            }
+        }
+
+        // if dst was empty, it is now guaranteed to be sorted
+        dst->collection.sorted = dst_was_empty;
+    } else {
+        if (cxListClone(dst, src, clone_func, clone_allocator, data)) {
+            return 1;
+        }
+        CxIterator other_iter = cxListIterator(other);
+        cx_foreach(void *, elem, other_iter) {
+            if (cxListContains(src, elem)) {
+                continue;
+            }
+            void **dst_mem = cxListEmplace(dst);
+            void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem;
+            void* dst_ptr = clone_func(target, elem, clone_allocator, data);
+            if (dst_ptr == NULL) {
+                cx_list_pop_uninitialized_elements(dst, 1);
+                return 1;
+            }
+            if (cxCollectionStoresPointers(dst)) {
+                *dst_mem = dst_ptr;
+            }
+        }
+    }
+
+    return 0;
+}

mercurial