metadata 0.2.0
Modern C++23 header-only metadata container (JSON-like)
Loading...
Searching...
No Matches
hash.hpp
1#pragma once
2
3#include <md/object.hpp>
4#include <md/value.hpp>
5
6#include <cstddef>
7#include <cstdint>
8#include <functional>
9#include <string>
10#include <string_view>
11#include <variant>
12
13namespace md::detail {
14
15inline std::size_t hash_combine(const std::size_t seed, const std::size_t v) noexcept {
16 // Boost-style mix; documented as stable within a process only.
17 return seed ^ (v + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2));
18}
19
20// Per-alternative salt — keeps Value{0} (int), Value{0u} (uint), Value{0.0}
21// (double), and Value{false} from colliding.
22constexpr std::size_t alt_salt(const std::size_t idx) noexcept {
23 constexpr std::size_t base = 0xcbf29ce484222325ULL;
24 return base + (idx * 0x100000001b3ULL);
25}
26
27std::size_t hash_value(const Value& v) noexcept;
28
29inline std::size_t hash_array(const Array& a) noexcept {
30 std::size_t h = alt_salt(7);
31 for (const auto& el : a) {
32 h = hash_combine(h, hash_value(el));
33 }
34 return h;
35}
36
37// Order-independent so two Objects that compare equal hash equal regardless
38// of insertion order.
39inline std::size_t hash_object(const Object& o) noexcept {
40 std::size_t acc = 0;
41 for (const auto& [k, val] : o) {
42 std::size_t entry = std::hash<std::string_view>{}(std::string_view{k});
43 entry = hash_combine(entry, hash_value(val));
44 acc ^= entry;
45 }
46 // Mix in a non-zero salt so an empty object hashes distinctly from "all
47 // entry-hashes XORed to zero by accident".
48 return acc ^ alt_salt(8);
49}
50
51inline std::size_t hash_value(const Value& v) noexcept {
52 return std::visit(
53 [&](const auto& x) noexcept -> std::size_t {
54 using T = std::decay_t<decltype(x)>;
55 if constexpr (std::is_same_v<T, std::nullptr_t>) {
56 return alt_salt(0);
57 } else if constexpr (std::is_same_v<T, bool>) {
58 return hash_combine(alt_salt(1), std::hash<bool>{}(x));
59 } else if constexpr (std::is_same_v<T, std::int64_t>) {
60 return hash_combine(alt_salt(2), std::hash<std::int64_t>{}(x));
61 } else if constexpr (std::is_same_v<T, std::uint64_t>) {
62 return hash_combine(alt_salt(3), std::hash<std::uint64_t>{}(x));
63 } else if constexpr (std::is_same_v<T, float>) {
64 return hash_combine(alt_salt(4), std::hash<float>{}(x));
65 } else if constexpr (std::is_same_v<T, double>) {
66 return hash_combine(alt_salt(5), std::hash<double>{}(x));
67 } else if constexpr (std::is_same_v<T, std::string>) {
68 return hash_combine(alt_salt(6),
69 std::hash<std::string_view>{}(std::string_view{x}));
70 } else if constexpr (std::is_same_v<T, Array>) {
71 return hash_array(x);
72 } else if constexpr (std::is_same_v<T, std::unique_ptr<Object>>) {
73 return hash_object(*x);
74 } else {
75 return 0;
76 }
77 },
78 v.raw());
79}
80
81} // namespace md::detail
82
84template <>
85struct std::hash<md::Value> {
87 std::size_t operator()(const md::Value& v) const noexcept {
88 return md::detail::hash_value(v);
89 }
90};
91
94template <>
95struct std::hash<md::Object> {
97 std::size_t operator()(const md::Object& o) const noexcept {
98 return md::detail::hash_object(o);
99 }
100};
101
103template <>
104struct std::hash<md::Array> {
106 std::size_t operator()(const md::Array& a) const noexcept {
107 return md::detail::hash_array(a);
108 }
109};
Ordered-by-insertion-time-ish string-keyed map of Values, with transparent string-view lookup and JSO...
Definition object.hpp:21
Discriminated union holding one of the JSON-like alternatives (null, bool, signed/unsigned integer,...
Definition value.hpp:67
std::size_t operator()(const md::Array &a) const noexcept
Hash an md::Array.
Definition hash.hpp:106
std::size_t operator()(const md::Object &o) const noexcept
Hash an md::Object.
Definition hash.hpp:97
std::size_t operator()(const md::Value &v) const noexcept
Hash an md::Value.
Definition hash.hpp:87