add cxMapEmplace()

Sun, 17 Aug 2025 23:05:16 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Aug 2025 23:05:16 +0200
changeset 1341
dc88d2ece7e4
parent 1340
31c61b6dcaa5
child 1342
fe3ac6b1cf57

add cxMapEmplace()

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/map.h.md file | annotate | diff | comparison | revisions
src/cx/list.h file | annotate | diff | comparison | revisions
src/cx/map.h file | annotate | diff | comparison | revisions
src/hash_map.c file | annotate | diff | comparison | revisions
tests/test_hash_map.c file | annotate | diff | comparison | revisions
--- a/CHANGELOG	Fri Aug 15 17:46:47 2025 +0200
+++ b/CHANGELOG	Sun Aug 17 23:05:16 2025 +0200
@@ -8,7 +8,7 @@
  * adds cxListFirst() and cxListLast()
  * adds cxListRemoveAndGetFirst() and cxListRemoveAndGetLast(),
    and corresponding macro aliases cxListPopFront() and cxListPop()
- * adds cxListEmplace() and cxListEmplaceAt()
+ * adds cxListEmplace(), cxListEmplaceAt(), and cxMapEmplace()
  * adds cxBufferShrink()
  * adds cxTreeSize()
  * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings
--- a/docs/Writerside/topics/map.h.md	Fri Aug 15 17:46:47 2025 +0200
+++ b/docs/Writerside/topics/map.h.md	Sun Aug 17 23:05:16 2025 +0200
@@ -193,21 +193,28 @@
 #include <cx/map.h>
 
 int cxMapPut(CxMap *map, KeyType key, void *value);
+
+void *cxMapEmplace(CxMap *map, KeyType key);
 ```
 
-The function `cxMapPut()` stores the specified `key` / `value` pair.
+The function `cxMapPut()` stores the specified `key` / `value` pair,
+and returns zero if the element was successfully put into the map and non-zero otherwise.
 
 The key is always copied.
 The behavior for the value is dependent on whether the map is storing pointers.
 If it is storing pointers, the `value` pointer is directly stored in the map.
 Otherwise, the memory pointed to by `value` is copied, using the element size of the collection.
 
-If an element is already associated with the specified key, it is replaced.
+The function `cxMapEmplace()` is similar to `cxMapPut()`, but instead of adding an existing value to the map,
+it returns a pointer to newly allocated memory for the value.
+The memory is allocated using the allocator of the map.
+If the map is storing pointers, the allocated memory will only be that for a pointer.
+Otherwise, the memory is allocated using the known element size.
+
+Both functions, if an element is already associated with the specified key, replace the existing element.
 If [destructor functions](collection.h.md#destructor-functions) are registered,
 they are invoked for the old element before it is replaced.
 
-This function returns zero if the element was successfully put into the map and non-zero otherwise.
-
 ## Access
 
 ```C
--- a/src/cx/list.h	Fri Aug 15 17:46:47 2025 +0200
+++ b/src/cx/list.h	Sun Aug 17 23:05:16 2025 +0200
@@ -81,7 +81,7 @@
     /**
      * Member function for inserting a single element.
      * The data pointer may be @c NULL in which case the function shall only allocate memory.
-     * Returns a pointer to the data of the inserted element.
+     * Returns a pointer to the allocated memory or @c NULL if allocation fails.
      */
     void *(*insert_element)(
             struct cx_list_s *list,
--- a/src/cx/map.h	Fri Aug 15 17:46:47 2025 +0200
+++ b/src/cx/map.h	Sun Aug 17 23:05:16 2025 +0200
@@ -185,8 +185,11 @@
 
     /**
      * 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.
      */
-    int (*put)(
+    void *(*put)(
             CxMap *map,
             CxHashKey key,
             void *value
@@ -387,7 +390,7 @@
         CxHashKey const &key,
         void *value
 ) {
-    return map->cl->put(map, key, value);
+    return map->cl->put(map, key, value) == NULL;
 }
 
 cx_attr_nonnull
@@ -396,7 +399,7 @@
         cxstring const &key,
         void *value
 ) {
-    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+    return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL;
 }
 
 cx_attr_nonnull
@@ -405,7 +408,7 @@
         cxmutstr const &key,
         void *value
 ) {
-    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+    return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL;
 }
 
 cx_attr_nonnull
@@ -415,7 +418,40 @@
         const char *key,
         void *value
 ) {
-    return map->cl->put(map, cx_hash_key_str(key), value);
+    return map->cl->put(map, cx_hash_key_str(key), value) == NULL;
+}
+
+cx_attr_nonnull
+static inline void *cxMapEmplace(
+        CxMap *map,
+        CxHashKey const &key
+) {
+    return map->cl->put(map, key, NULL);
+}
+
+cx_attr_nonnull
+static inline void *cxMapEmplace(
+        CxMap *map,
+        cxstring const &key
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), NULL);
+}
+
+cx_attr_nonnull
+static inline void *cxMapEmplace(
+        CxMap *map,
+        cxmutstr const &key
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), NULL);
+}
+
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline void *cxMapEmplace(
+        CxMap *map,
+        const char *key
+) {
+    return map->cl->put(map, cx_hash_key_str(key), NULL);
 }
 
 cx_attr_nonnull
@@ -540,7 +576,7 @@
         CxHashKey key,
         void *value
 ) {
-    return map->cl->put(map, key, value);
+    return map->cl->put(map, key, value) == NULL;
 }
 
 /**
@@ -552,7 +588,7 @@
         cxstring key,
         void *value
 ) {
-    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+    return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL;
 }
 
 /**
@@ -564,7 +600,7 @@
         cxmutstr key,
         void *value
 ) {
-    return map->cl->put(map, cx_hash_key_cxstr(key), value);
+    return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL;
 }
 
 /**
@@ -577,7 +613,7 @@
         const char *key,
         void *value
 ) {
-    return map->cl->put(map, cx_hash_key_str(key), value);
+    return map->cl->put(map, cx_hash_key_str(key), value) == NULL;
 }
 
 /**
@@ -608,6 +644,77 @@
     (map, key, value)
 
 /**
+ * @copydoc cxMapEmplace()
+ */
+cx_attr_nonnull
+static inline void *cx_map_emplace(
+        CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->put(map, key, NULL);
+}
+
+/**
+ * @copydoc cxMapEmplace()
+ */
+cx_attr_nonnull
+static inline void *cx_map_emplace_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), NULL);
+}
+
+/**
+ * @copydoc cxMapEmplace()
+ */
+cx_attr_nonnull
+static inline void *cx_map_emplace_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->put(map, cx_hash_key_cxstr(key), NULL);
+}
+
+/**
+ * @copydoc cxMapEmplace()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline void *cx_map_emplace_str(
+        CxMap *map,
+        const char *key
+) {
+    return map->cl->put(map, cx_hash_key_str(key), NULL);
+}
+
+/**
+ * Allocates memory for a value in the map associated with the specified key.
+ *
+ * A possible existing value will be overwritten.
+ * If destructor functions are specified, they are called for
+ * the overwritten element.
+ *
+ * If the map is storing pointers, this function returns a @c void** pointer,
+ * meaning a pointer to that pointer.
+ *
+ * The @p key is always copied.
+ *
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) 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
+ */
+#define cxMapEmplace(map, key) _Generic((key), \
+    CxHashKey: cx_map_emplace,                 \
+    cxstring: cx_map_emplace_cxstr,            \
+    cxmutstr: cx_map_emplace_mustr,            \
+    char*: cx_map_emplace_str,                 \
+    const char*: cx_map_emplace_str)           \
+    (map, key)
+
+/**
  * @copydoc cxMapGet()
  */
 cx_attr_nonnull
--- a/src/hash_map.c	Fri Aug 15 17:46:47 2025 +0200
+++ b/src/hash_map.c	Sun Aug 17 23:05:16 2025 +0200
@@ -78,7 +78,7 @@
     cxFree(map->collection.allocator, map);
 }
 
-static int cx_hash_map_put(
+static void *cx_hash_map_put(
         CxMap *map,
         CxHashKey key,
         void *value
@@ -105,7 +105,9 @@
         memcmp(elm->key.data, key.data, key.len) == 0) {
         // overwrite existing element, but call destructors first
         cx_invoke_destructor(map, elm->data);
-        if (map->collection.store_pointer) {
+        if (value == NULL) {
+            memset(elm->data, 0, map->collection.elem_size);
+        } else if (map->collection.store_pointer) {
             memcpy(elm->data, &value, sizeof(void *));
         } else {
             memcpy(elm->data, value, map->collection.elem_size);
@@ -116,10 +118,12 @@
                 allocator,
                 sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
         );
-        if (e == NULL) return -1;
+        if (e == NULL) return NULL;
 
         // write the value
-        if (map->collection.store_pointer) {
+        if (value == NULL) {
+            memset(e->data, 0, map->collection.elem_size);
+        } else if (map->collection.store_pointer) {
             memcpy(e->data, &value, sizeof(void *));
         } else {
             memcpy(e->data, value, map->collection.elem_size);
@@ -127,7 +131,10 @@
 
         // copy the key
         void *kd = cxMalloc(allocator, key.len);
-        if (kd == NULL) return -1;
+        if (kd == NULL) {
+            cxFree(allocator, e);
+            return NULL;
+        }
         memcpy(kd, key.data, key.len);
         e->key.data = kd;
         e->key.len = key.len;
@@ -140,12 +147,14 @@
             prev->next = e;
         }
         e->next = elm;
+        elm = e;
 
         // increase the size
         map->collection.size++;
     }
 
-    return 0;
+    // return pointer to the element
+    return elm->data;
 }
 
 static void cx_hash_map_unlink(
--- a/tests/test_hash_map.c	Fri Aug 15 17:46:47 2025 +0200
+++ b/tests/test_hash_map.c	Sun Aug 17 23:05:16 2025 +0200
@@ -79,6 +79,70 @@
     cx_testing_allocator_destroy(&talloc);
 }
 
+CX_TEST(test_hash_map_emplace) {
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *allocator = &talloc.base;
+    CX_TEST_DO {
+        CxMap *map = cxHashMapCreate(allocator, 20, 4);
+
+        char *v = cxMapEmplace(map, "key 1");
+        CX_TEST_ASSERT(v != NULL);
+        strcpy(v, "val 1");
+
+        char *tv = cxMapGet(map, "key 1");
+        CX_TEST_ASSERT(tv != NULL);
+        CX_TEST_ASSERT(0 == strcmp(tv, "val 1"));
+
+        v = cxMapEmplace(map, "key 1");
+        CX_TEST_ASSERT(v != NULL);
+        CX_TEST_ASSERT(0 != strcmp(v, "val 1"));
+
+        tv = cxMapGet(map, "key 1");
+        CX_TEST_ASSERT(tv != NULL);
+        CX_TEST_ASSERT(0 != strcmp(tv, "val 1"));
+
+        cxMapFree(map);
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
+CX_TEST(test_hash_map_emplace_pointers) {
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    CxAllocator *allocator = &talloc.base;
+    CX_TEST_DO {
+        CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 4);
+        cxDefineAdvancedDestructor(map, cxFree, allocator);
+
+        char *val1 = cxMalloc(allocator, 8);
+        strcpy(val1, "val 1");
+        char *val2 = cxMalloc(allocator, 8);
+        strcpy(val2, "val 2");
+
+        char **v1 = cxMapEmplace(map, "key 1");
+        CX_TEST_ASSERT(v1 != NULL);
+        *v1 = val1;
+
+        char *tv = cxMapGet(map, "key 1");
+        CX_TEST_ASSERT(tv != NULL);
+        CX_TEST_ASSERT(0 == strcmp(tv, "val 1"));
+
+        // this will call the destructor of former v1
+        char **v2 = cxMapEmplace(map, "key 1");
+        CX_TEST_ASSERT(v2 != NULL);
+        *v2 = val2;
+        tv = cxMapGet(map, "key 1");
+        CX_TEST_ASSERT(tv != NULL);
+        CX_TEST_ASSERT(0 == strcmp(tv, "val 2"));
+
+        cxMapFree(map);
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+    }
+    cx_testing_allocator_destroy(&talloc);
+}
+
 CX_TEST(test_hash_map_rehash) {
     CxTestingAllocator talloc;
     cx_testing_allocator_init(&talloc);
@@ -731,6 +795,8 @@
     cx_test_register(suite, test_hash_map_create);
     cx_test_register(suite, test_hash_map_create_store_pointers);
     cx_test_register(suite, test_hash_map_basic_operations);
+    cx_test_register(suite, test_hash_map_emplace);
+    cx_test_register(suite, test_hash_map_emplace_pointers);
     cx_test_register(suite, test_hash_map_rehash);
     cx_test_register(suite, test_hash_map_rehash_not_required);
     cx_test_register(suite, test_hash_map_clear);

mercurial