]> PatchyGit - patchyircd.git/commitdiff
map: use a flat_map (except it's not std:: for now)
authorEgor <egor@patchysicp.xyz>
Tue, 3 Feb 2026 00:36:51 +0000 (02:36 +0200)
committerEgor <egor@patchysicp.xyz>
Tue, 3 Feb 2026 00:36:51 +0000 (02:36 +0200)
Makefile.am
src/common.hpp
src/map.cpp [deleted file]
src/map.hpp

index 479a35c..f62c282 100644 (file)
@@ -12,7 +12,7 @@ ircd_SOURCES = \
                               src/ircdata.hpp        \
        src/ircfmt.cpp         src/ircfmt.hpp         \
        src/job.cpp            src/job.hpp            \
-       src/map.cpp            src/map.hpp            \
+                              src/map.hpp            \
        src/net.cpp            src/net.hpp            \
        src/netpoll.cpp        src/netpoll.hpp        \
        src/passwd.cpp         src/passwd.hpp         \
index 2a88544..f7ac225 100644 (file)
@@ -30,6 +30,7 @@
 #include <algorithm>
 #include <charconv>
 #include <compare>
+#include <concepts>
 #include <deque>
 #include <functional>
 #include <iostream>
@@ -37,6 +38,7 @@
 #include <memory>
 #include <string>
 #include <string_view>
+#include <type_traits>
 #include <utility>
 #include <vector>
 using namespace std::string_view_literals;
diff --git a/src/map.cpp b/src/map.cpp
deleted file mode 100644 (file)
index 18cfb9c..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) 2023, 2025-2026 Egor
-// SPDX-License-Identifier: GPL-2.0-or-later
-#include "common.hpp"
-#include "map.hpp"
-#include "ircdata.hpp"
-
-template<typename A, typename B>
-class Comparisons {
-       friend constexpr bool operator==(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; }
-       friend constexpr bool operator!=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; }
-       friend constexpr bool operator< (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <  0; }
-       friend constexpr bool operator<=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; }
-       friend constexpr bool operator> (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >  0; }
-       friend constexpr bool operator>=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; }
-
-       friend constexpr bool operator==(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; }
-       friend constexpr bool operator!=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; }
-       friend constexpr bool operator< (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <  0; }
-       friend constexpr bool operator<=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; }
-       friend constexpr bool operator> (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >  0; }
-       friend constexpr bool operator>=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; }
-};
-
-class LowercasingView : Comparisons<std::string_view, LowercasingView> {
-       std::string_view sv;
-public:
-       explicit constexpr LowercasingView(std::string_view sv) :sv(sv) {}
-       friend constexpr std::strong_ordering operator<=>(std::string_view lhs, LowercasingView rhs) noexcept {
-               size_t len = std::min(lhs.size(), rhs.sv.size());
-               const char *a = lhs.data();
-               const char *b = rhs.sv.data();
-               unsigned char cha, chb;
-               while (len--) {
-                       cha = *a++;
-                       chb = *b++;
-                       if (chb >= 'A' && chb <= 'Z')
-                               chb |= 0x20;
-                       if (auto diff = cha <=> chb; diff != 0)
-                               return diff;
-               }
-               return lhs.size() <=> rhs.sv.size();
-       }
-       friend constexpr std::strong_ordering operator<=>(LowercasingView lhs, std::string_view rhs) noexcept {
-               return rhs <=> lhs;
-       }
-};
-
-static size_t ent_find(map_base *root, auto key, int *found)
-{
-       auto it = std::partition_point(root->ptr, root->ptr + root->len,
-                       [key](const auto &p){ return std::string_view(p.key) < key; });
-       *found = it != root->ptr + root->len && std::string_view(it->key) == key;
-       return it - root->ptr;
-}
-void *map_base::_get(const char *key)
-{
-       int found;
-       size_t i = ent_find(this, std::string_view(key), &found);
-       return found ? ptr[i].value : 0;
-}
-void *map_base::_iget(const char *key)
-{
-       int found;
-       size_t i = ent_find(this, LowercasingView(key), &found);
-       return found ? ptr[i].value : 0;
-}
-void map_base::_set(const char *key, const void *value, void **oldvalue)
-{
-       int found;
-       size_t i = ent_find(this, std::string_view(key), &found);
-       if (found) {
-               if (oldvalue)
-                       *oldvalue = ptr[i].value;
-               if (!value) {
-                       delete[] ptr[i].key;
-                       std::copy(ptr+i+1, ptr+len, ptr+i);
-                       len--;
-               } else {
-                       ptr[i].value = const_cast<void *>(value);
-               }
-       } else {
-               if (oldvalue)
-                       *oldvalue = 0;
-               if (value) {
-                       if (len == cap) {
-                               size_t ncap = cap ? cap*2 : 2;
-                               map_value *nptr = new map_value[ncap];
-                               memcpy(nptr, ptr, cap*sizeof(map_value));
-                               ptr = nptr;
-                               cap = ncap;
-                       }
-                       size_t slen = strlen(key)+1;
-                       char *nkey = new char[slen];
-                       memcpy(nkey, key, slen);
-                       std::copy_backward(ptr+i, ptr+len, ptr+len+1);
-                       len++;
-                       ptr[i].key = nkey;
-                       ptr[i].value = const_cast<void *>(value);
-               }
-       }
-}
-void map_base::clear() {
-       while (len) {
-               delete[] ptr[--len].key;
-       }
-       delete[] ptr;
-       ptr = 0;
-       len = cap = 0;
-}
-map_base::~map_base() {
-       clear();
-}
index 77021c5..df7b68d 100644 (file)
@@ -4,6 +4,133 @@
 #define MAP_H_
 #include "util.hpp"
 
+template<typename A, typename B>
+class Comparisons {
+       friend constexpr bool operator==(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; }
+       friend constexpr bool operator!=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; }
+       friend constexpr bool operator< (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <  0; }
+       friend constexpr bool operator<=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; }
+       friend constexpr bool operator> (const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >  0; }
+       friend constexpr bool operator>=(const A& lhs, const B& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; }
+
+       friend constexpr bool operator==(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs == 0; }
+       friend constexpr bool operator!=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs != 0; }
+       friend constexpr bool operator< (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <  0; }
+       friend constexpr bool operator<=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs <= 0; }
+       friend constexpr bool operator> (const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >  0; }
+       friend constexpr bool operator>=(const B& lhs, const A& rhs) noexcept(noexcept(lhs <=> rhs)) { return lhs <=> rhs >= 0; }
+};
+
+class LowercasingView : Comparisons<std::string_view, LowercasingView> {
+       std::string_view sv;
+public:
+       explicit constexpr LowercasingView(std::string_view sv) :sv(sv) {}
+       friend constexpr std::strong_ordering operator<=>(std::string_view lhs, LowercasingView rhs) noexcept {
+               size_t len = std::min(lhs.size(), rhs.sv.size());
+               const char *a = lhs.data();
+               const char *b = rhs.sv.data();
+               unsigned char cha, chb;
+               while (len--) {
+                       cha = *a++;
+                       chb = *b++;
+                       if (chb >= 'A' && chb <= 'Z')
+                               chb |= 0x20;
+                       if (auto diff = cha <=> chb; diff != 0)
+                               return diff;
+               }
+               return lhs.size() <=> rhs.sv.size();
+       }
+       friend constexpr std::strong_ordering operator<=>(LowercasingView lhs, std::string_view rhs) noexcept {
+               return 0 <=> (rhs <=> lhs);
+       }
+};
+
+// TODO: use std::flat_set once clang is new enough
+template<typename T>
+concept is_transparent = requires { typename T::is_transparent; };
+template<typename Key, typename Compare=std::less<Key>, typename KeyContainer=std::vector<Key>>
+class FlatSet {
+       KeyContainer m_vec;
+       [[no_unique_address]] Compare cmp;
+public:
+       using iterator = KeyContainer::iterator;
+       auto begin() noexcept { return m_vec.begin(); }
+       auto end() noexcept { return m_vec.end(); }
+
+       auto lower_bound(const Key& key) {
+               return std::partition_point(begin(), end(), [&](const auto &e){ return cmp(e, key); });
+       }
+       template<typename K> requires is_transparent<Compare>
+       auto lower_bound(const K& key) {
+               return std::partition_point(begin(), end(), [&](const auto &e){ return cmp(e, key); });
+       }
+
+       auto find(const Key& key) {
+               auto it = lower_bound(key);
+               if (it != end() && !cmp(key, *it))
+                       return it;
+               else
+                       return end();
+       }
+       template<typename K> requires is_transparent<Compare>
+       auto find(const K& key) {
+               auto it = lower_bound(key);
+               if (it != end() && !cmp(key, *it))
+                       return it;
+               else
+                       return end();
+       }
+
+       auto insert(const Key& key) {
+               auto it = lower_bound(key);
+               if (it != end() && !cmp(key, *it)) {
+                       return std::make_pair(it, false);
+               } else {
+                       return std::make_pair(m_vec.insert(it, key), true);
+               }
+       }
+       auto insert(Key&& key) {
+               auto it = lower_bound(key);
+               if (it != end() && !cmp(key, *it)) {
+                       return std::make_pair(it, false);
+               } else {
+                       return std::make_pair(m_vec.insert(it, std::move(key)), true);
+               }
+       }
+
+       auto erase(typename KeyContainer::iterator it) {
+               return m_vec.erase(it);
+       }
+       size_t erase(const Key& key) {
+               auto it = lower_bound(key);
+               if (it != end() && !cmp(key, *it)) {
+                       erase(it);
+                       return 1;
+               } else {
+                       return 0;
+               }
+       }
+       template<typename K> requires is_transparent<Compare>
+       size_t erase(const K& key) {
+               auto it = lower_bound(key);
+               if (it != end() && !cmp(key, *it)) {
+                       erase(it);
+                       return 1;
+               } else {
+                       return 0;
+               }
+       }
+
+       void clear() noexcept {
+               m_vec.clear();
+       }
+       auto size() noexcept {
+               return m_vec.size();
+       }
+
+       FlatSet& operator=(FlatSet&&) = delete;
+};
+
 /* A map abstract data type with following properties:
  * - keys are lexicographically-sorted unique NUL-terminated strings
  * - values are non-NULL pointers
  * A map is created by allocating and zero-initializing an object of type
  * map_t. All map functions take a pointer to map_t.
  */
+template<typename T>
+struct MapValueLess {
+       using is_transparent = void;
+       constexpr std::string_view to_key(const T &v) {
+               return v.key;
+       }
+       constexpr bool operator()(const T& lhs, const T& rhs) noexcept {
+               return to_key(lhs) < to_key(rhs);
+       }
+       constexpr bool operator()(const T& lhs, std::string_view rhs) noexcept {
+               return to_key(lhs) < rhs;
+       }
+       constexpr bool operator()(std::string_view lhs, const T& rhs) noexcept {
+               return lhs < to_key(rhs);
+       }
+       constexpr bool operator()(const T& lhs, LowercasingView rhs) noexcept {
+               return to_key(lhs) < rhs;
+       }
+       constexpr bool operator()(LowercasingView lhs, const T& rhs) noexcept {
+               return lhs < to_key(rhs);
+       }
+};
 struct map_base {
        struct map_value {
                char *key;
                void *value;
        };
-       map_value *ptr = 0;
-       size_t len = 0;
-       size_t cap = 0;
+       FlatSet<map_value, MapValueLess<map_value>> m;
 
        map_base& operator=(map_base&&) = delete;
-       ~map_base();
+       ~map_base() {
+               clear();
+       }
        /* Return the associated value for a given key.
         * Dynarr complexity: O(log n) */
-       void *_get(const char *key);
+       void *_get(const char *key) {
+               auto it = m.find(std::string_view(key));
+               return it != m.end() ? it->value : nullptr;
+       }
        /* _get() but lowercase the key */
-       void *_iget(const char *key);
+       void *_iget(const char *key) {
+               auto it = m.find(LowercasingView(key));
+               return it != m.end() ? it->value : nullptr;
+       }
        /* Associate a new value with a given key.
         * If oldvalue is not NULL, it receives the old value of key.
         * No allocation failures on value = NULL.
         * Dynarr complexity: O(n) */
-       void _set(const char *key, const void *value, void **oldvalue);
+       void _set(const char *key, const void *value, void **oldvalue) {
+               if (value) {
+                       map_value mv;
+                       mv.key = const_cast<char *>(key);
+                       mv.value = const_cast<void *>(value);
+                       auto [it, inserted] = m.insert(mv);
+                       if (inserted) {
+                               if (oldvalue)
+                                       *oldvalue = nullptr;
+                               it->key = mystrdup(it->key);
+                       } else {
+                               if (oldvalue)
+                                       *oldvalue = it->value;
+                               it->value = mv.value;
+                       }
+               } else {
+                       auto it = m.find(std::string_view(key));
+                       if (oldvalue)
+                               *oldvalue = it != m.end() ? it->value : nullptr;
+                       if (it != m.end()) {
+                               delete[] it->key;
+                               m.erase(it);
+                       }
+               }
+       }
        /* Removes all keys from the map and frees any associated memory.
         * The map_t is reset to zero-initialized state and can be used again.
         * Dynarr complexity: O(n) */
-       void clear();
+       void clear() {
+               for (auto &mv : m)
+                       delete[] mv.key;
+               m.clear();
+       }
        /* Iterate over a map.
         * Do not modify the map while iterating.
         */
@@ -47,8 +230,11 @@ struct map_base {
                size_t index;
                auto next() {
                        map_value kv{0, 0};
-                       if (index < root->len)
-                               kv = root->ptr[index++];
+                       auto it = root->m.begin() + index;
+                       if (it < root->m.end()) {
+                               kv = *it;
+                               index++;
+                       }
                        return std::make_pair(kv.key, kv.value);
                }
        };
@@ -68,12 +254,12 @@ struct map_base2 : map_base {
                iterator &operator--() { --ptr; return *this; }
                iterator operator--(int) { auto it = *this; --ptr; return it; }
                bool operator==(const iterator& other) const = default;
-               reference operator*() const { return T::to_value(ptr); }
-               map_value *ptr;
+               reference operator*() const { return T::to_value(&*ptr); }
+               decltype(m)::iterator ptr;
        };
        static_assert(std::bidirectional_iterator<iterator>);
-       auto begin() { return iterator{ptr}; }
-       auto end() { return iterator{ptr+len}; }
+       auto begin() { return iterator{m.begin()}; }
+       auto end() { return iterator{m.end()}; }
        struct reverse {
                map_base2 &obj;
                auto begin() { return std::make_reverse_iterator(obj.end()); }
@@ -84,7 +270,7 @@ struct map_base2 : map_base {
                return reverse{*this};
        }
        size_t size() {
-               return len;
+               return m.size();
        }
 protected:
        static const char *to_key(V v) {