Introduce til::hasher for sucessive hashing of structs (#11887)

This commit serves two purposes:
* Simplify construction of hashes for non-trivial structs
  This is especially helpful for ActionArgs
* Improve hash quality by not needlessly throwing away entropy

`til::hasher` is modeled after Rust's `std:#️⃣:Hasher` and works similar.
The idea is simple: A stateful hash function can hash multiple unrelated fields,
without loosing entropy by running a finalizer after hashing each interim field.
This is especially useful for modern hash functions, which often have a wider
internal state than the output width. Additionally this improves performance
for hash functions with complex finalizers.

Most of this is of course a bit moot right now, considering that `til::hasher`
is still based on STL's FNV1a algorithm, which offers a very poor hash quality.
But counterintuitively, FNV1a actually benefits most from this PR: Since it
lacks a finalizer entirely, this commit greatly improves hash quality as
it encodes more data into FNV's state and thus improves randomness.

## PR Checklist
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed
* No unusual behavior 
This commit is contained in:
Leonard Hecker 2021-12-07 20:47:23 +01:00 committed by GitHub
parent 91ad17d439
commit 00fe2b02bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 316 additions and 178 deletions

View File

@ -189,7 +189,6 @@ cacafire
callee
capslock
CARETBLINKINGENABLED
carlos
CARRIAGERETURN
cascadia
cassert
@ -635,7 +634,7 @@ doskey
dotnet
doubleclick
downlevel
dpg
DPG
dpi
DPIAPI
DPICHANGE
@ -793,6 +792,7 @@ FIXEDCONVERTED
FIXEDFILEINFO
Flg
flyout
fmix
fmodern
fmtarg
fmtid
@ -1471,7 +1471,6 @@ msix
msrc
msvcrt
MSVCRTD
MSVS
msys
msysgit
MTSM
@ -2174,7 +2173,6 @@ SHOWNOACTIVATE
SHOWNORMAL
SHOWWINDOW
SHRT
sid
sidebyside
SIF
SIGDN
@ -2205,7 +2203,6 @@ somefile
SOURCEBRANCH
sourced
SOURCESDIRECTORY
SPACEBAR
spammy
spand
sprintf
@ -2510,7 +2507,6 @@ unmark
UNORM
unparseable
unpause
Unregister
unregistering
untests
untextured
@ -2523,7 +2519,6 @@ upvote
uri
url
urlencoded
Urxvt
USASCII
usebackq
USECALLBACK
@ -2568,7 +2563,6 @@ Vcount
vcpkg
vcprintf
vcproj
vcvarsall
vcxitems
vcxproj
vec
@ -2825,7 +2819,6 @@ xes
xff
XFile
XFORM
xIcon
XManifest
XMath
XMFLOAT
@ -2858,7 +2851,6 @@ YCast
YCENTER
YCount
YDPI
yIcon
yml
YOffset
YPosition
@ -2866,7 +2858,6 @@ YSize
YSubstantial
YVIRTUALSCREEN
YWalk
zamora
ZCmd
ZCtrl
zsh

View File

@ -14,9 +14,11 @@ Author(s):
#pragma once
#include <vector>
#include <unordered_map>
#include <climits>
#include <vector>
#include <til/bit.h>
#include <til/hash.h>
// std::unordered_map needs help to know how to hash a COORD
namespace std
@ -33,10 +35,7 @@ namespace std
// - the hashed coord
constexpr size_t operator()(const COORD& coord) const noexcept
{
size_t retVal = coord.Y;
const size_t xCoord = coord.X;
retVal |= xCoord << (sizeof(coord.Y) * CHAR_BIT);
return retVal;
return til::hash(til::bit_cast<uint32_t>(coord));
}
};
}

View File

@ -2447,7 +2447,7 @@ uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id)
// assign _currentHyperlinkId if the custom id does not already exist
std::wstring newId{ id };
// hash the URL and add it to the custom ID - GH#7698
newId += L"%" + std::to_wstring(std::hash<std::wstring_view>{}(uri));
newId += L"%" + std::to_wstring(til::hash(uri));
const auto result = _hyperlinkCustomIdMap.emplace(newId, _currentHyperlinkId);
if (result.second)
{

View File

@ -62,27 +62,6 @@ private:
// * ActionEventArgs holds a single IActionArgs. For events that don't need
// additional args, this can be nullptr.
template<>
constexpr size_t Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(const winrt::Microsoft::Terminal::Settings::Model::IActionArgs& args)
{
return gsl::narrow_cast<size_t>(args.Hash());
}
template<>
constexpr size_t Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& args)
{
return gsl::narrow_cast<size_t>(args.Hash());
}
// Retrieves the hash value for an empty-constructed object.
template<typename T>
static size_t EmptyHash()
{
// cache the value of the empty hash
static const size_t cachedHash = winrt::make_self<T>()->Hash();
return cachedHash;
}
////////////////////////////////////////////////////////////////////////////////
// BEGIN X-MACRO MADNESS
////////////////////////////////////////////////////////////////////////////////
@ -324,9 +303,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_ColorScheme = _ColorScheme;
return *copy;
}
size_t Hash() const
size_t Hash(uint64_t hasherState) const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(Commandline(), StartingDirectory(), TabTitle(), TabColor(), ProfileIndex(), Profile(), SuppressApplicationTitle(), ColorScheme());
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
h.write(Commandline());
h.write(StartingDirectory());
h.write(TabTitle());
h.write(TabColor());
h.write(ProfileIndex());
h.write(Profile());
h.write(SuppressApplicationTitle());
h.write(ColorScheme());
return h.finalize();
}
};
@ -375,9 +363,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_TerminalArgs = _TerminalArgs.Copy();
return *copy;
}
size_t Hash() const
size_t Hash(uint64_t hasherState) const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(TerminalArgs());
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
h.write(TerminalArgs());
return h.finalize();
}
};
@ -459,9 +449,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_SplitSize = _SplitSize;
return *copy;
}
size_t Hash() const
size_t Hash(uint64_t hasherState) const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(SplitDirection(), TerminalArgs(), SplitMode(), SplitSize());
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
h.write(SplitDirection());
h.write(TerminalArgs());
h.write(SplitMode());
h.write(SplitSize());
return h.finalize();
}
};
@ -506,9 +501,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_TerminalArgs = _TerminalArgs.Copy();
return *copy;
}
size_t Hash() const
size_t Hash(uint64_t hasherState) const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(TerminalArgs());
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
h.write(TerminalArgs());
return h.finalize();
}
};
@ -625,9 +622,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_Actions = _Actions;
return *copy;
}
size_t Hash() const
size_t Hash(uint64_t hasherState) const
{
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Actions);
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
h.write(winrt::get_abi(_Actions));
return h.finalize();
}
};
}

View File

@ -10,7 +10,7 @@ namespace Microsoft.Terminal.Settings.Model
Boolean Equals(IActionArgs other);
String GenerateName();
IActionArgs Copy();
UInt64 Hash();
UInt64 Hash(UInt64 hasher);
};
interface IActionEventArgs
@ -128,7 +128,7 @@ namespace Microsoft.Terminal.Settings.Model
Boolean Equals(NewTerminalArgs other);
String GenerateName();
String ToCommandline();
UInt64 Hash();
UInt64 Hash(UInt64 hasher);
};
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs

View File

@ -17,6 +17,8 @@ Author(s):
--*/
#pragma once
// MACRO HACKS
//
// We want to have code that looks like:
@ -96,7 +98,7 @@ struct InitListPlaceholder
// definition of Hash() below, that's to deal with trailing commas (or in this
// case, leading.)
#define HASH_ARGS(type, name, jsonKey, required, ...) \
, name()
h.write(name());
// Use ACTION_ARGS_STRUCT when you've got no other customizing to do.
#define ACTION_ARGS_STRUCT(className, argsMacro) \
@ -109,52 +111,53 @@ struct InitListPlaceholder
// * NewTerminalArgs has a ToCommandline method it needs to additionally declare.
// * GlobalSummonArgs has the QuakeModeFromJson helper
#define ACTION_ARG_BODY(className, argsMacro) \
className() = default; \
className( \
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
argsMacro(CTOR_INIT) _placeholder{} {}; \
argsMacro(DECLARE_ARGS); \
\
private: \
InitListPlaceholder _placeholder; \
\
public: \
hstring GenerateName() const; \
bool Equals(const IActionArgs& other) \
{ \
auto otherAsUs = other.try_as<className>(); \
if (otherAsUs) \
{ \
return true argsMacro(EQUALS_ARGS); \
} \
return false; \
}; \
static FromJsonResult FromJson(const Json::Value& json) \
{ \
auto args = winrt::make_self<className>(); \
argsMacro(FROM_JSON_ARGS); \
return { *args, {} }; \
} \
static Json::Value ToJson(const IActionArgs& val) \
{ \
if (!val) \
{ \
return {}; \
} \
Json::Value json{ Json::ValueType::objectValue }; \
const auto args{ get_self<className>(val) }; \
argsMacro(TO_JSON_ARGS); \
return json; \
} \
IActionArgs Copy() const \
{ \
auto copy{ winrt::make_self<className>() }; \
argsMacro(COPY_ARGS); \
return *copy; \
} \
size_t Hash() const \
{ \
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty( \
0 argsMacro(HASH_ARGS)); \
#define ACTION_ARG_BODY(className, argsMacro) \
className() = default; \
className( \
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
argsMacro(CTOR_INIT) _placeholder{} {}; \
argsMacro(DECLARE_ARGS); \
\
private: \
InitListPlaceholder _placeholder; \
\
public: \
hstring GenerateName() const; \
bool Equals(const IActionArgs& other) \
{ \
auto otherAsUs = other.try_as<className>(); \
if (otherAsUs) \
{ \
return true argsMacro(EQUALS_ARGS); \
} \
return false; \
}; \
static FromJsonResult FromJson(const Json::Value& json) \
{ \
auto args = winrt::make_self<className>(); \
argsMacro(FROM_JSON_ARGS); \
return { *args, {} }; \
} \
static Json::Value ToJson(const IActionArgs& val) \
{ \
if (!val) \
{ \
return {}; \
} \
Json::Value json{ Json::ValueType::objectValue }; \
const auto args{ get_self<className>(val) }; \
argsMacro(TO_JSON_ARGS); \
return json; \
} \
IActionArgs Copy() const \
{ \
auto copy{ winrt::make_self<className>() }; \
argsMacro(COPY_ARGS); \
return *copy; \
} \
size_t Hash(uint64_t hasherState) const \
{ \
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) }; \
argsMacro(HASH_ARGS); \
return h.finalize(); \
}

View File

@ -15,36 +15,42 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
static InternalActionID Hash(const Model::ActionAndArgs& actionAndArgs)
{
size_t hashedAction{ HashUtils::HashProperty(actionAndArgs.Action()) };
static constexpr auto initialHash = til::hasher{}.finalize();
const auto action = actionAndArgs.Action();
til::hasher hasher;
size_t hashedArgs{};
if (const auto& args{ actionAndArgs.Args() })
if (const auto args = actionAndArgs.Args())
{
// Args are defined, so hash them
hashedArgs = gsl::narrow_cast<size_t>(args.Hash());
hasher = til::hasher{ gsl::narrow_cast<size_t>(args.Hash(initialHash)) };
}
else
{
size_t hash = 0;
// Args are not defined.
// Check if the ShortcutAction supports args.
switch (actionAndArgs.Action())
{
#define ON_ALL_ACTIONS_WITH_ARGS(action) \
case ShortcutAction::action: \
/* If it does, hash the default values for the args.*/ \
hashedArgs = EmptyHash<implementation::action##Args>(); \
break;
#define ON_ALL_ACTIONS_WITH_ARGS(action) \
case ShortcutAction::action: \
{ \
/* If it does, hash the default values for the args. */ \
static const auto cachedHash = gsl::narrow_cast<size_t>( \
winrt::make_self<implementation::action##Args>()->Hash(initialHash)); \
hash = cachedHash; \
break; \
}
ALL_SHORTCUT_ACTIONS_WITH_ARGS
#undef ON_ALL_ACTIONS_WITH_ARGS
default:
{
// Otherwise, hash nullptr.
std::hash<IActionArgs> argsHash;
hashedArgs = argsHash(nullptr);
}
break;
}
hasher = til::hasher{ hash };
}
return hashedAction ^ hashedArgs;
hasher.write(action);
return hasher.finalize();
}
// Method Description:

View File

@ -17,54 +17,37 @@ Revision History:
#pragma once
namespace Microsoft::Terminal::Settings::Model::HashUtils
#include <til/hash.h>
namespace til
{
// This is a helper template function for hashing multiple variables in conjunction to each other.
template<typename T>
constexpr size_t HashProperty(const T& val)
struct hash_trait<winrt::Windows::Foundation::IReference<T>>
{
std::hash<T> hashFunc;
return hashFunc(val);
}
template<typename T, typename... Args>
constexpr size_t HashProperty(const T& val, Args&&... more)
{
// Inspired by boost::hash_combine, which causes this effect...
// seed ^= hash_value(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
// Source: https://www.boost.org/doc/libs/1_35_0/doc/html/boost/hash_combine_id241013.html
const auto seed{ HashProperty(val) };
return seed ^ (0x9e3779b9 + (seed << 6) + (seed >> 2) + HashProperty(std::forward<Args>(more)...));
}
constexpr void operator()(hasher& h, const winrt::Windows::Foundation::IReference<T>& v) const noexcept
{
if (v)
{
h.write(v.Value());
}
}
};
template<>
constexpr size_t HashProperty(const til::color& val)
struct hash_trait<winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs>
{
return HashProperty(val.a, val.r, val.g, val.b);
}
void operator()(hasher& h, const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& value) const noexcept
{
h.write(winrt::get_abi(value));
}
};
#ifdef WINRT_Windows_Foundation_H
template<typename T>
constexpr size_t HashProperty(const winrt::Windows::Foundation::IReference<T>& val)
{
return val ? HashProperty(val.Value()) : 0;
}
#endif
#ifdef WINRT_Windows_UI_H
template<>
constexpr size_t HashProperty(const winrt::Windows::UI::Color& val)
struct hash_trait<winrt::hstring>
{
return HashProperty(til::color{ val });
}
#endif
#ifdef WINRT_Microsoft_Terminal_Core_H
template<>
constexpr size_t HashProperty(const winrt::Microsoft::Terminal::Core::Color& val)
{
return HashProperty(til::color{ val });
}
#endif
};
void operator()(hasher& h, const winrt::hstring& value) const noexcept
{
h.write(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(wchar_t));
}
};
}

View File

@ -93,6 +93,7 @@
<ClInclude Include="VsSetupConfiguration.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="ActionArgsMagic.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="ActionArgs.idl" />

View File

@ -2,19 +2,14 @@
// Licensed under the MIT license.
#include "precomp.h"
#include "alias.h"
#include "_output.h"
#include <til/hash.h>
#include "output.h"
#include "stream.h"
#include "_stream.h"
#include "dbcs.h"
#include "handle.h"
#include "misc.h"
#include "../types/inc/convert.hpp"
#include "srvinit.h"
#include "resource.h"
#include "ApiRoutines.h"
@ -28,10 +23,12 @@ struct case_insensitive_hash
{
std::size_t operator()(const std::wstring& key) const
{
std::wstring lower(key);
std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower);
std::hash<std::wstring> hash;
return hash(lower);
til::hasher h;
for (const auto& ch : key)
{
h.write(::towlower(ch));
}
return h.finalize();
}
};

View File

@ -2583,10 +2583,10 @@ void TextBufferTests::HyperlinkTrim()
const TextAttribute attr{ 0x7f };
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
const auto url = L"test.url";
const auto otherUrl = L"other.url";
const auto customId = L"CustomId";
const auto otherCustomId = L"OtherCustomId";
static constexpr std::wstring_view url{ L"test.url" };
static constexpr std::wstring_view otherUrl{ L"other.url" };
static constexpr std::wstring_view customId{ L"CustomId" };
static constexpr std::wstring_view otherCustomId{ L"OtherCustomId" };
// Set a hyperlink id in the first row and add a hyperlink to our map
const COORD pos{ 70, 0 };
@ -2606,8 +2606,8 @@ void TextBufferTests::HyperlinkTrim()
// Increment the circular buffer
_buffer->IncrementCircularBuffer();
const auto finalCustomId = fmt::format(L"{}%{}", customId, std::hash<std::wstring_view>{}(url));
const auto finalOtherCustomId = fmt::format(L"{}%{}", otherCustomId, std::hash<std::wstring_view>{}(otherUrl));
const auto finalCustomId = fmt::format(L"{}%{}", customId, til::hash(url));
const auto finalOtherCustomId = fmt::format(L"{}%{}", otherCustomId, til::hash(otherUrl));
// The hyperlink reference that was only in the first row should be deleted from the map
VERIFY_ARE_EQUAL(_buffer->_hyperlinkMap.find(id), _buffer->_hyperlinkMap.end());
@ -2629,8 +2629,8 @@ void TextBufferTests::NoHyperlinkTrim()
const TextAttribute attr{ 0x7f };
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
const auto url = L"test.url";
const auto customId = L"CustomId";
static constexpr std::wstring_view url{ L"test.url" };
static constexpr std::wstring_view customId{ L"CustomId" };
// Set a hyperlink id in the first row and add a hyperlink to our map
const COORD pos{ 70, 0 };
@ -2647,7 +2647,7 @@ void TextBufferTests::NoHyperlinkTrim()
// Increment the circular buffer
_buffer->IncrementCircularBuffer();
const auto finalCustomId = fmt::format(L"{}%{}", customId, std::hash<std::wstring_view>{}(url));
const auto finalCustomId = fmt::format(L"{}%{}", customId, til::hash(url));
// The hyperlink reference should not be deleted from the map since it is still present in the buffer
VERIFY_ARE_EQUAL(_buffer->GetHyperlinkUriFromId(id), url);

159
src/inc/til/hash.h Normal file
View File

@ -0,0 +1,159 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace til
{
template<typename T>
struct hash_trait;
struct hasher
{
explicit constexpr hasher(size_t state = FNV_offset_basis) noexcept :
_hash{ state } {}
template<typename T>
constexpr void write(const T& v) noexcept
{
hash_trait<T>{}(*this, v);
}
template<typename T, typename = std::enable_if_t<std::has_unique_object_representations_v<T>>>
constexpr void write(const T* data, size_t count) noexcept
{
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
write(reinterpret_cast<const uint8_t*>(data), sizeof(T) * count);
}
#pragma warning(suppress : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
constexpr void write(const uint8_t* data, size_t count) noexcept
{
for (size_t i = 0; i < count; ++i)
{
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
_hash ^= static_cast<size_t>(data[i]);
_hash *= FNV_prime;
}
}
constexpr size_t finalize() const noexcept
{
return _hash;
}
private:
#if defined(_WIN64)
static constexpr size_t FNV_offset_basis = 14695981039346656037ULL;
static constexpr size_t FNV_prime = 1099511628211ULL;
#else
static constexpr size_t FNV_offset_basis = 2166136261U;
static constexpr size_t FNV_prime = 16777619U;
#endif
size_t _hash = FNV_offset_basis;
};
namespace details
{
template<typename T, bool enable>
struct conditionally_enabled_hash_trait
{
constexpr void operator()(hasher& h, const T& v) const noexcept
{
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
}
};
template<typename T>
struct conditionally_enabled_hash_trait<T, false>
{
conditionally_enabled_hash_trait() = delete;
conditionally_enabled_hash_trait(const conditionally_enabled_hash_trait&) = delete;
conditionally_enabled_hash_trait(conditionally_enabled_hash_trait&&) = delete;
conditionally_enabled_hash_trait& operator=(const conditionally_enabled_hash_trait&) = delete;
conditionally_enabled_hash_trait& operator=(conditionally_enabled_hash_trait&&) = delete;
};
}
template<typename T>
struct hash_trait : details::conditionally_enabled_hash_trait<T, std::has_unique_object_representations_v<T>>
{
};
template<>
struct hash_trait<float>
{
constexpr void operator()(hasher& h, float v) const noexcept
{
v = v == 0.0f ? 0.0f : v; // map -0 to 0
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(v));
}
};
template<>
struct hash_trait<double>
{
constexpr void operator()(hasher& h, double v) const noexcept
{
v = v == 0.0 ? 0.0 : v; // map -0 to 0
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(v));
}
};
template<typename T, typename CharTraits, typename Allocator>
struct hash_trait<std::basic_string<T, CharTraits, Allocator>>
{
constexpr void operator()(hasher& h, const std::basic_string<T, CharTraits, Allocator>& v) const noexcept
{
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
h.write(reinterpret_cast<const uint8_t*>(v.data()), sizeof(T) * v.size());
}
};
template<typename T, typename CharTraits>
struct hash_trait<std::basic_string_view<T, CharTraits>>
{
constexpr void operator()(hasher& h, const std::basic_string_view<T, CharTraits>& v) const noexcept
{
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
h.write(reinterpret_cast<const uint8_t*>(v.data()), sizeof(T) * v.size());
}
};
template<typename T, typename = std::enable_if_t<std::is_integral_v<T> || std::is_enum_v<T>>>
constexpr size_t hash(const T v) noexcept
{
// This runs murmurhash3's finalizer (fmix32/fmix64) on a single integer.
// It's fast, public domain and produces good results.
auto h = static_cast<size_t>(v);
if constexpr (sizeof(size_t) == 4)
{
h ^= h >> 16;
h *= UINT32_C(0x85ebca6b);
h ^= h >> 13;
h *= UINT32_C(0xc2b2ae35);
h ^= h >> 16;
}
else
{
h ^= h >> 33;
h *= UINT64_C(0xff51afd7ed558ccd);
h ^= h >> 33;
h *= UINT64_C(0xc4ceb9fe1a85ec53);
h ^= h >> 33;
}
return h;
}
template<typename T, typename = std::enable_if_t<!(std::is_integral_v<T> || std::is_enum_v<T>)>>
constexpr size_t hash(const T& v) noexcept
{
hasher h;
h.write(v);
return h.finalize();
}
}