Skip to content

<xstd/misc/functional/hash.hpp>

xstd::hash

// (1)
template<class T, hash_algorithm H64 = boost::hash2::xxhash_64, hash_algorithm H32 = H64,
         class Flavor = boost::hash2::default_flavor>
  requires (sizeof(typename H32::result_type) <= sizeof(typename H64::result_type))
class hash;

// (2)
template<class CharT, class Traits, class Allocator, hash_algorithm H64, hash_algorithm H32, class Flavor>
class hash<basic_string<CharT, Traits, Allocator>, H64, H32, Flavor>;

// (3)
template<class T, hash_algorithm H64, hash_algorithm H32, class Flavor>
class hash<optional<T>, H64, H32, Flavor>;

// (4)
template<class T, class Allocator, hash_algorithm H64, hash_algorithm H32, class Flavor>
class hash<indirect<T, Allocator>, H64, H32, Flavor>;

An unordered container's hash for keys of type T, from a seeded Boost.Hash2 algorithm. The class holds a prototype of hash_type, seeded once; each call copies the prototype, appends the key and folds the result to size_t.

  1. The primary template.
  2. The string specialization, transparent over basic_string_view<CharT, Traits>. A string, its view and a C string hash the same, and a set of strings finds a key by its view, or by a literal, without building a string. Only a string key is transparent: equal values of other types need not append the same bytes.
  3. The optional specialization. An engaged optional hashes as hash<remove_const_t<T>, H64, H32, Flavor> hashes its value, as [optional.hash] requires, and an empty one as no bytes at all, which no key appends. It is seeded as that hash is, and is_avalanching is that hash's.
  4. The indirect specialization, where the standard library declares indirect. An indirect hashes as hash<T, H64, H32, Flavor> hashes its value, as [indirect.hash] requires, and a valueless one as no bytes at all.

A key's own hash is its Boost.Hash2 tag_invoke, which every key containing it follows at any depth. A specialization of hash for a key is followed only by the optional and indirect specializations.

Keys

Every key Boost.Hash2's hash_append hashes, and every key the standard specializes std::hash for. Hash2 1.92 hashes the arithmetic, enumeration and pointer types, nullptr_t, strings and their views, ranges, tuple-likes, optional, variant and monostate. For the other standard keys, the header appends the state each one's operator== reads:

Key Appends
a floating-point type of neither 4 nor 8 bytes, such as x87's long double, float16_t or float128_t its value bits, with -0 as +0 and without padding
__int128, unsigned __int128, where the compiler has them and the key is not inside another its low and high 64 bits
bitset<N> its bits, 64 to a word
error_code, error_condition the address of its category, then its value
unique_ptr<T, D>, shared_ptr<T> get()
type_index name()
thread::id its object representation
coroutine_handle<P> address()
filesystem::path its root name, whether it has a root directory, then each element of its relative path
stacktrace_entry native_handle()
text_encoding mib(), then for id::other its name, lowercased, without non-alphanumerics or 0s
indirect<T, Allocator> whether it is valueless, then its value
chrono::duration, chrono::time_point count(), time_since_epoch()
chrono::day, month, year, weekday and the calendar types built from them each field operator== compares
chrono::zoned_time, chrono::leap_second its time zone and its time, its date

Equal keys therefore hash equal, and unequal keys append different bytes. A key whose state is an address, such as a smart pointer or an error's category, hashes differently from one run to the next.

Template parameters

Parameter Meaning
T The key type. An argument converts to T before it is appended, so 42 hashes as the int64_t a table holds.
H64 The algorithm where size_t is wider than 32 bits. Constrained to model hash_algorithm, so a second key type in its place is rejected. Defaults to xxhash_64.
H32 The algorithm where size_t is at most 32 bits, constrained as H64 is. Defaults to H64, so one algorithm named alone runs on every target.
Flavor The flavor Hash2 appends with. Defaults to default_flavor, which appends in the target's own byte order.

Remarks: A pair whose 32-bit algorithm is named first, such as xxhash_32, xxhash_64, is rejected by the requires-clause.

Member types

Type Definition
hash_type conditional_t<sizeof(size_t) <= sizeof(uint32_t), H32, H64>, the algorithm that runs
is_avalanching bool_constant<hash_algorithm_is_avalanching_v<hash_type>>, which boost::hash_is_avalanching reads
is_transparent (2) only

Member functions

Function Purpose
(constructor) value-initializes or seeds the prototype
operator() hashes a key

(constructor)

hash() = default;                                       // (1)
constexpr explicit hash(uint64_t seed);                 // (2)
constexpr hash(const unsigned char* p, size_t n);       // (3)
  1. Value-initializes the prototype.
  2. Initializes it from seed.
  3. Initializes it from the n bytes at p.

Remarks: Each is a constant expression.

operator()

constexpr auto operator()(const T& v) const -> size_t;

The string specialization has two in its place: one taking const basic_string<CharT, Traits, Allocator>&, which accepts whatever converts to a string, as std::hash's parameter does, and one taking any const K& that converts to basic_string_view<CharT, Traits>, such as a view or a C string, which hashes as the view. The optional specialization takes const optional<T>& and the indirect one const indirect<T, Allocator>&.

  • Effects: Copies the prototype to h and calls boost::hash2::hash_append(h, Flavor(), v). The prototype is unchanged.
  • Returns: boost::hash2::get_integral_result<size_t>(h).
  • Throws: Any exception the key's hash_append throws.
  • Remarks: It is noexcept where [unord.hash]/3 makes std::hash<T>'s call noexcept, and hash_type is one of Hash2's own algorithms. That covers the arithmetic, enumeration and pointer types, nullptr_t, strings and their views, and each standard key the Keys table lists but indirect, unique_ptr, duration, time_point and zoned_time. The optional and indirect specializations are noexcept where the hash of their value is.
  • Remarks: It is unconstrained: Hash2 declares hash_append for every type, so a constraint would admit keys it then rejects, such as weak_ptr.
  • Remarks: As of Boost.Hash2 1.92 a call is not a constant expression, since get_integral_result is not constexpr.

Notes

  • Equal values hash equal, and unequal values append different bytes.
  • It serves as the hash of unordered_set and boost::unordered_flat_set, seeded per container.
  • Under FNV-1a, keys that differ only in their high bits share a bucket wherever a table masks the hash, and spread in a boost::unordered_flat_set, which mixes it first.

Example

Adapted from the example of std::hash on cppreference.com, at revision 179088, by cppreference.com contributors. This example is under CC-BY-SA 3.0 and the GFDL, as cppreference.com's content is; the rest of this page is under the Boost Software License.

#include <xstd/misc/functional/hash.hpp> // hash
#include <boost/hash2/hash_append.hpp>   // hash_append_tag
#include <boost/hash2/siphash.hpp>       // siphash_64
#include <cstdint>                       // uint64_t
#include <iomanip>                       // quoted
#include <iostream>                      // boolalpha, cout
#include <string>                        // string
#include <string_view>                   // string_view
#include <unordered_set>                 // unordered_set

struct S
{
        std::string first_name;
        std::string last_name;

        [[nodiscard]] auto operator==(S const&) const -> bool = default;

        // A key says what it hashes, its members in order, and leaves the algorithm to the hasher.
        template<class Provider, class Hash, class Flavor>
        friend constexpr auto tag_invoke(boost::hash2::hash_append_tag const&, Provider const& provider, Hash& h, Flavor const& f, S const* s)
                -> void
        {
                provider.hash_append(h, f, s->first_name);
                provider.hash_append(h, f, s->last_name);
        }
};

// A hasher can name its algorithm: SipHash, seeded, where an adversary chooses the keys.
using siphasher = xstd::hash<S, boost::hash2::siphash_64>;

auto main() -> int
{
        auto const str      = std::string("Meet the new boss...");
        auto const str_hash = xstd::hash<std::string>()(str);
        std::cout << std::boolalpha << "hash(" << std::quoted(str) << ") == hash of its view: "
                  << (str_hash == xstd::hash<std::string>()(std::string_view(str))) << '\n';

        // A fixed seed, for the example; a program draws one at random, per container.
        auto const seed = std::uint64_t{0x9e37'79b9'7f4a'7c15};

        auto const obj  = S{"Hubert", "Farnsworth"};
        auto const copy = obj;
        std::cout << "hash(" << std::quoted(obj.first_name) << ", " << std::quoted(obj.last_name) << ") == hash of a copy: "
                  << (xstd::hash<S>()(obj) == xstd::hash<S>()(copy)) << " (using xxhash_64) and "
                  << (siphasher(seed)(obj) == siphasher(seed)(copy)) << " (using a seeded siphash_64)\n";

        // A key that appends its members can be used in unordered containers, under whichever algorithm they name.
        auto const names = std::unordered_set<S, siphasher>({obj, {"Bender", "Rodriguez"}, {"Turanga", "Leela"}}, 0, siphasher(seed));
        for (auto const& s : {S{"Bender", "Rodriguez"}, S{"Turanga", "Leela"}, obj, S{"Philip", "Fry"}}) {
                std::cout << std::quoted(s.first_name) << ' ' << std::quoted(s.last_name) << " is in the set: " << names.contains(s) << '\n';
        }
}

Output:

hash("Meet the new boss...") == hash of its view: true
hash("Hubert", "Farnsworth") == hash of a copy: true (using xxhash_64) and true (using a seeded siphash_64)
"Bender" "Rodriguez" is in the set: true
"Turanga" "Leela" is in the set: true
"Hubert" "Farnsworth" is in the set: true
"Philip" "Fry" is in the set: false

See also

Entity Relation
digest a key's digest at its algorithm's own width, from the same seeded prototype
hash_algorithm what H64 and H32 model
hash_algorithm_is_avalanching what is_avalanching reads
std::hash the standard's hash, whose role this class generalizes