Skip to content
/ zmap.h Public

A type-safe, header-only generic map library for C/C++ using X-Macros.

License

Notifications You must be signed in to change notification settings

z-libs/zmap.h

Repository files navigation

zmap.h

Type-Safe, Zero-Overhead, Robin Hood Hash Map for C/C++.

zmap.h is a single-header library that brings the ergonomics of high-level languages to C without sacrificing performance. It uses Robin Hood Hashing with Backward Shift Deletion to maintain high load factors (up to 0.9) while keeping probe lengths short and cache-friendly.

It features a unified API via C11 _Generic, strict type safety (no void* casting), and a zero-cost C++ wrapper that provides STL-compatible iterators.

Features

  • Robin Hood Hashing: Minimizes probe variance, enabling high load factors without performance degradation.
  • Fibonacci Indexing: Mathematically optimal distribution ($hash \times \phi$) protects against poor hash functions.
  • Two Storage Modes:
    1. Standard: Keys/Values stored inline (contiguous memory) for maximum cache locality.
    2. Stable: Values stored on the heap. Pointers to values remain valid after resizes.
  • Type Safety: Compiler errors on type mismatches. No void* overhead.
  • WyHash Support: Automatically uses the ultra-fast WyHash algorithm if zhash.h is present.
  • C++ Interop: Zero-cost z_map::map<K,V> wrapper with RAII and STL-compatible iterators.
  • Safe API: Optional zerror.h integration for stack traces and error handling.

Installation

Manual

zmap.h works best when you use the provided scanner script to manage type registrations, though it can be used manually.

  1. Copy zmap.h (and zcommon.h if separated) to your project's include folder.
  2. (Recommended) Copy zhash.h to the same folder for improved string hashing performance.
  3. Add the z-core tools (optional but recommended):
git submodule add https://github.com/z-libs/z-core.git z-core

Clib

If you use the clib package manager, run:

clib install z-libs/zdk

ZDK (Recommended)

If you use the Zen Development Kit, it is included automatically by including <zdk/zmap.h> (or <zdk/zworld.h>).

Benchmarks

View Live Benchmark Results.

Comparison vs uthash (standard C hash map) managing 1,000,000 string keys:

Operation zmap (WyHash) uthash (Jenkin's) Speedup
Insert 0.1637s 0.3269s 2.00x Faster
Lookup (Hit) 0.1124s 0.2582s 2.30x Faster
Lookup (Miss) 0.1092s 0.3449s 3.16x Faster
Delete All 0.0024s 0.1062s 44.29x Faster

Benchmark

Note: Benchmarks run on GitHub Actions (Ubuntu/Azure). Local native performance is often higher.

Usage: C

For C projects, you must register the map types you need. This can be done via the scanner script (which detects usage) or manually via the Registry Header.

Manual Registration

Create a header (for example, types.h) to define your maps using X-Macros.

#ifndef TYPES_H
#define TYPES_H

// Syntax: X(KeyType, ValueType, ShortName)
#define REGISTER_ZMAP_TYPES(X)      \
    X(const char*, int, StrInt)     \
    X(uint64_t, float, IdF32)

#include "zmap.h"

#endif

Basic Operations

#include "types.h"

// Helper: Hashing and Comparison.
uint32_t hash_str(const char* k, uint32_t seed) { return ZMAP_HASH_STR(k, seed); }
int cmp_str(const char* a, const char* b) { return strcmp(a, b); }

int main(void) 
{
    // Initialize (Name, HashFunc, CmpFunc).
    zmap_StrInt scores = zmap_init(StrInt, hash_str, cmp_str);

    // Insert (Returns Z_OK or Z_ENOMEM).
    zmap_put(&scores, "Alice", 100);
    zmap_put(&scores, "Bob", 200);

    // Retrieve (Returns pointer to value, or NULL).
    int *val = zmap_get(&scores, "Alice");
    if (val) 
    {
        printf("Alice: %d\n", *val);
    }

    // Cleanup.
    zmap_free(&scores);
    return 0;
}

Short Names

If you prefer a cleaner API and don't worry about namespace collisions, define ZMAP_SHORT_NAMES before including the header.

#define ZMAP_SHORT_NAMES
#include "zmap.h"

// Now you can use:
map(StrInt) m = map_init(StrInt, ...);
map_put(&m, k, v);
map_get(&m, k);

Usage: C++

The C++ wrapper z_map::map is a zero-overhead abstraction. It uses the same generated C code under the hood but adds RAII and iterators.

#include <iostream>

// Register types (visible to C++ compiler).
#define REGISTER_ZMAP_TYPES(X) \
    X(int, float, IntFloat)

#include "zmap.h"

int main() 
{
    // Constructor handles init and seed.
    z_map::map<int, float> data(
        [](int k, uint32_t s) { return zhash_uint32(k) ^ s; }, // Hash.
        [](int a, int b) { return a - b; }                     // Compare.
    );

    // STL-style insertion.
    data.put(42, 3.14f);
        
    // STL-compatible Iterators.
    for (auto& bucket : data) 
    {
        std::cout << bucket.key << ": " << bucket.value << "\n";
    }
        
    // Automatic cleanup (Destructor calls zmap_free).
    return 0;
}

Advanced Features

Stable Maps (Pointer Stability)

Standard zmap stores values inline for speed. If you need pointers to values to remain valid after resizes (e.g., for graph nodes), use a Stable Map.

// In your registry:
#define REGISTER_STABLE_MAPS(X) \
    X(const char*, MyStruct, MyStable)

// Usage:
zmap_stable_MyStable m = zmap_init_stable(MyStable, hash_fn, cmp_fn);
MyStruct* ptr = zmap_get(&m, "key"); 
// 'ptr' is now valid until the key is removed, even if map resizes.

High-Performance Hashing

zmap.h automatically detects zhash.h.

  • Without zhash.h: Uses FNV-1a (Simple, inline fallback).
  • With zhash.h: Uses WyHash (SIMD-optimized, high quality).

Recommendation: Always include zhash.h when using string keys.

Safe API (zerror Integration)

If zerror.h is present, zmap generates "Safe" versions of critical functions. These functions return zres (Result) types containing error information and stack traces on failure.

Operation Standard API Safe API Return Type
Put zmap_put zmap_put_safe zres (Void Result)
Get zmap_get zmap_get_safe Result<T*>

API Reference (C)

The library uses _Generic dispatch, so the same macro names work for all registered map types.

Macro Description
zmap_init(Name, h, c) Initialize a standard map.
zmap_init_stable(Name, h, c) Initialize a stable map.
zmap_put(m, k, v) Insert key/value. Returns Z_OK or Z_ENOMEM.
zmap_get(m, k) Return pointer to value, or NULL.
zmap_remove(m, k) Remove key from map.
zmap_free(m) Free all memory.
zmap_clear(m) Clear count but keep capacity.
zmap_size(m) Return number of items.
zmap_iter_init(Name, m) Create an iterator.
zmap_iter_next(it, k, v) Advance iterator. Returns bool.
zmap_autofree(Name) (GCC/Clang) RAII-style auto-cleanup at end of scope.

API Reference (C++)

The C++ wrapper lives in the z_map namespace. It strictly adheres to RAII principles.

class z_map::map<K, V>

Constructors & Management

Method Description
map(hash_fn, cmp_fn) Constructs map with specific helpers.
~map() Destructor. Automatically calls zmap_free.
operator= Move assignment operator.
size() Returns current number of elements.
empty() Returns true if size is 0.
clear() Clears items but keeps capacity.

Access & Modification

Method Description
put(k, v) Inserts or updates key-value pair. Throws std::bad_alloc on failure.
insert_or_assign(k, v) Alias for put.
get(k) Returns V* or const V*. Returns nullptr if not found.
contains(k) Returns true if key exists.
erase(k) Removes the key if present.

Iterators

Method Description
begin(), end() Forward iterators. Returns reference to bucket ({key, value}).
cbegin(), cend() Const iterators for read-only access.

Configuration

Memory Management

You can override the memory allocators globally or specifically for maps.

// Global Override
#define ZMAP_MALLOC  my_malloc
#define ZMAP_FREE    my_free
#define ZMAP_REALLOC my_realloc
#define ZMAP_CALLOC  my_calloc

About

A type-safe, header-only generic map library for C/C++ using X-Macros.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Packages

No packages published