From 14193a14344232509028faf8cc6a80e050fde9de Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 31 Jan 2023 07:42:16 +0700 Subject: [PATCH] constexpr hashed_string --- develop/singles/headers/meta.hpp/meta_all.hpp | 58 +++++++---- .../untests/meta_base/hashed_string_tests.cpp | 95 +++++++++++++++++++ headers/meta.hpp/meta_base/hashed_string.hpp | 58 +++++++---- 3 files changed, 177 insertions(+), 34 deletions(-) create mode 100644 develop/untests/meta_base/hashed_string_tests.cpp diff --git a/develop/singles/headers/meta.hpp/meta_all.hpp b/develop/singles/headers/meta.hpp/meta_all.hpp index 720734e..b7ef0f0 100644 --- a/develop/singles/headers/meta.hpp/meta_all.hpp +++ b/develop/singles/headers/meta.hpp/meta_all.hpp @@ -531,6 +531,35 @@ namespace meta_hpp::detail }; } +namespace meta_hpp::detail +{ + template < std::size_t SizeBytes = sizeof(std::size_t) > + struct fnv1a_hash_traits; + + template <> + struct fnv1a_hash_traits<4> { // NOLINT(*-magic-numbers) + using underlying_type = std::uint32_t; + static inline constexpr underlying_type prime{16777619U}; + static inline constexpr underlying_type offset{2166136261U}; + }; + + template <> + struct fnv1a_hash_traits<8> { // NOLINT(*-magic-numbers) + using underlying_type = std::uint64_t; + static inline constexpr underlying_type prime{1099511628211U}; + static inline constexpr underlying_type offset{14695981039346656037U}; + }; + + constexpr std::size_t fnv1a_hash(std::string_view str) noexcept { + std::size_t hash{fnv1a_hash_traits<>::offset}; + for ( char ch : str ) { + hash ^= static_cast(ch); + hash *= fnv1a_hash_traits<>::prime; + } + return hash; + } +} + namespace meta_hpp::detail { class hashed_string final { @@ -544,40 +573,35 @@ namespace meta_hpp::detail hashed_string& operator=(hashed_string&&) = default; hashed_string& operator=(const hashed_string&) = default; - hashed_string(const char* str) noexcept - : hash_{std::hash{}(str)} {} + constexpr hashed_string(const char* str) noexcept : hash_{fnv1a_hash(str)} {} + constexpr hashed_string(std::string_view str) noexcept : hash_{fnv1a_hash(str)} {} + constexpr hashed_string(const std::string& str) noexcept : hash_{fnv1a_hash(str)} {} - hashed_string(std::string_view str) noexcept - : hash_{std::hash{}(str)} {} - - hashed_string(const std::string& str) noexcept - : hash_{std::hash{}(str)} {} - - void swap(hashed_string& other) noexcept { + constexpr void swap(hashed_string& other) noexcept { std::swap(hash_, other.hash_); } - [[nodiscard]] std::size_t get_hash() const noexcept { + [[nodiscard]] constexpr std::size_t get_hash() const noexcept { return hash_; } private: - std::size_t hash_{}; + std::size_t hash_{fnv1a_hash("")}; }; - inline void swap(hashed_string& l, hashed_string& r) noexcept { + constexpr void swap(hashed_string& l, hashed_string& r) noexcept { l.swap(r); } - [[nodiscard]] inline bool operator<(hashed_string l, hashed_string r) noexcept { + [[nodiscard]] constexpr bool operator<(hashed_string l, hashed_string r) noexcept { return l.get_hash() < r.get_hash(); } - [[nodiscard]] inline bool operator==(hashed_string l, hashed_string r) noexcept { + [[nodiscard]] constexpr bool operator==(hashed_string l, hashed_string r) noexcept { return l.get_hash() == r.get_hash(); } - [[nodiscard]] inline bool operator!=(hashed_string l, hashed_string r) noexcept { - return l.get_hash() == r.get_hash(); + [[nodiscard]] constexpr bool operator!=(hashed_string l, hashed_string r) noexcept { + return l.get_hash() != r.get_hash(); } } @@ -585,7 +609,7 @@ namespace std { template <> struct hash { - size_t operator()(meta_hpp::detail::hashed_string hs) const noexcept { + constexpr size_t operator()(meta_hpp::detail::hashed_string hs) const noexcept { return hs.get_hash(); } }; diff --git a/develop/untests/meta_base/hashed_string_tests.cpp b/develop/untests/meta_base/hashed_string_tests.cpp new file mode 100644 index 0000000..f300cd2 --- /dev/null +++ b/develop/untests/meta_base/hashed_string_tests.cpp @@ -0,0 +1,95 @@ +/******************************************************************************* + * This file is part of the "https://github.com/blackmatov/meta.hpp" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2021-2023, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#include +#include + +TEST_CASE("meta/meta_base/hashed_string") { + namespace meta = meta_hpp; + using meta::detail::hashed_string; + + SUBCASE("ctor/0") { + constexpr hashed_string hs{}; + static_assert(hs == hashed_string{""}); + static_assert(hs.get_hash() == hashed_string{""}.get_hash()); + + CHECK(hs == hashed_string{std::string{}}); + CHECK(hs == hashed_string{std::string_view{}}); + } + + SUBCASE("ctor/1") { + constexpr hashed_string hs{"hello"}; + static_assert(hs.get_hash() == meta::detail::fnv1a_hash("hello")); + + CHECK(hs == hashed_string{std::string{"hello"}}); + CHECK(hs == hashed_string{std::string_view{"hello"}}); + } + + SUBCASE("copy_ctor") { + constexpr hashed_string hs{"hello"}; + constexpr hashed_string hs2{hs}; + static_assert(hs == hs2); + static_assert(hs.get_hash() == hs2.get_hash()); + } + + SUBCASE("move_ctor") { + constexpr hashed_string hs{"hello"}; + constexpr hashed_string hs2{std::move(hs)}; + static_assert(hs == hs2); + static_assert(hs.get_hash() == hs2.get_hash()); + } + + SUBCASE("operator=/copy") { + constexpr hashed_string hs{"hello"}; + constexpr hashed_string hs2 = [&hs](){ + hashed_string r; + r = hs; + return r; + }(); + static_assert(hs == hs2); + static_assert(hs.get_hash() == hs2.get_hash()); + } + + SUBCASE("operator=/move") { + constexpr hashed_string hs{"hello"}; + constexpr hashed_string hs2 = [&hs](){ + hashed_string r; + r = std::move(hs); + return r; + }(); + static_assert(hs == hs2); + static_assert(hs.get_hash() == hs2.get_hash()); + } + + SUBCASE("get_hash") { + constexpr hashed_string hs1{"hello"}; + constexpr hashed_string hs2{"world"}; + static_assert(hs1.get_hash() == hashed_string{"hello"}.get_hash()); + static_assert(hs1.get_hash() != hs2.get_hash()); + CHECK(std::hash{}(hs1) == hs1.get_hash()); + } + + SUBCASE("operator<") { + constexpr hashed_string hs1{"hello"}; + constexpr hashed_string hs2{"hello"}; + static_assert(!(hs1 < hs2) && !(hs2 < hs1)); + static_assert(hs1 < hashed_string{"world"} || hashed_string{"world"} < hs1); + } + + SUBCASE("operator==") { + constexpr hashed_string hs1{"hello"}; + static_assert(hs1 == hashed_string{"hello"}); + static_assert(hs1 != hashed_string{"world"}); + } + + SUBCASE("swap") { + hashed_string hs1{"hello"}; + hashed_string hs2{"world"}; + swap(hs1, hs2); + CHECK(hs1 == hashed_string{"world"}); + CHECK(hs2 == hashed_string{"hello"}); + } +} diff --git a/headers/meta.hpp/meta_base/hashed_string.hpp b/headers/meta.hpp/meta_base/hashed_string.hpp index 900a71a..4fb2b23 100644 --- a/headers/meta.hpp/meta_base/hashed_string.hpp +++ b/headers/meta.hpp/meta_base/hashed_string.hpp @@ -8,6 +8,35 @@ #include "base.hpp" +namespace meta_hpp::detail +{ + template < std::size_t SizeBytes = sizeof(std::size_t) > + struct fnv1a_hash_traits; + + template <> + struct fnv1a_hash_traits<4> { // NOLINT(*-magic-numbers) + using underlying_type = std::uint32_t; + static inline constexpr underlying_type prime{16777619U}; + static inline constexpr underlying_type offset{2166136261U}; + }; + + template <> + struct fnv1a_hash_traits<8> { // NOLINT(*-magic-numbers) + using underlying_type = std::uint64_t; + static inline constexpr underlying_type prime{1099511628211U}; + static inline constexpr underlying_type offset{14695981039346656037U}; + }; + + constexpr std::size_t fnv1a_hash(std::string_view str) noexcept { + std::size_t hash{fnv1a_hash_traits<>::offset}; + for ( char ch : str ) { + hash ^= static_cast(ch); + hash *= fnv1a_hash_traits<>::prime; + } + return hash; + } +} + namespace meta_hpp::detail { class hashed_string final { @@ -21,40 +50,35 @@ namespace meta_hpp::detail hashed_string& operator=(hashed_string&&) = default; hashed_string& operator=(const hashed_string&) = default; - hashed_string(const char* str) noexcept - : hash_{std::hash{}(str)} {} + constexpr hashed_string(const char* str) noexcept : hash_{fnv1a_hash(str)} {} + constexpr hashed_string(std::string_view str) noexcept : hash_{fnv1a_hash(str)} {} + constexpr hashed_string(const std::string& str) noexcept : hash_{fnv1a_hash(str)} {} - hashed_string(std::string_view str) noexcept - : hash_{std::hash{}(str)} {} - - hashed_string(const std::string& str) noexcept - : hash_{std::hash{}(str)} {} - - void swap(hashed_string& other) noexcept { + constexpr void swap(hashed_string& other) noexcept { std::swap(hash_, other.hash_); } - [[nodiscard]] std::size_t get_hash() const noexcept { + [[nodiscard]] constexpr std::size_t get_hash() const noexcept { return hash_; } private: - std::size_t hash_{}; + std::size_t hash_{fnv1a_hash("")}; }; - inline void swap(hashed_string& l, hashed_string& r) noexcept { + constexpr void swap(hashed_string& l, hashed_string& r) noexcept { l.swap(r); } - [[nodiscard]] inline bool operator<(hashed_string l, hashed_string r) noexcept { + [[nodiscard]] constexpr bool operator<(hashed_string l, hashed_string r) noexcept { return l.get_hash() < r.get_hash(); } - [[nodiscard]] inline bool operator==(hashed_string l, hashed_string r) noexcept { + [[nodiscard]] constexpr bool operator==(hashed_string l, hashed_string r) noexcept { return l.get_hash() == r.get_hash(); } - [[nodiscard]] inline bool operator!=(hashed_string l, hashed_string r) noexcept { - return l.get_hash() == r.get_hash(); + [[nodiscard]] constexpr bool operator!=(hashed_string l, hashed_string r) noexcept { + return l.get_hash() != r.get_hash(); } } @@ -62,7 +86,7 @@ namespace std { template <> struct hash { - size_t operator()(meta_hpp::detail::hashed_string hs) const noexcept { + constexpr size_t operator()(meta_hpp::detail::hashed_string hs) const noexcept { return hs.get_hash(); } };