Sun, 17 Aug 2025 23:05:16 +0200
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);