/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MOZILLA_AUTO_MAPPABLE_H #define MOZILLA_AUTO_MAPPABLE_H // Here be dragons. #include namespace mozilla::gfx { template size_t Hash(const T&); template struct StaticStdHasher { static auto HashImpl(const T& v) { return std::hash()(v); } }; template struct StaticHasher { static auto HashImpl(const T& v) { return v.hash(); } }; template struct StaticHasher> { static size_t HashImpl(const std::optional& v) { if (!v) return 0; return Hash(*v); } }; template <> struct StaticHasher : public StaticStdHasher {}; template <> struct StaticHasher : public StaticStdHasher {}; template <> struct StaticHasher : public StaticStdHasher {}; template size_t Hash(const T& v) { return StaticHasher::HashImpl(v); } //- // From Boost: // https://www.boost.org/doc/libs/1_37_0/doc/html/hash/reference.html#boost.hash_combine inline size_t HashCombine(size_t seed, const size_t hash) { seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); return seed; } // - // See // https://codereview.stackexchange.com/questions/136770/hashing-a-tuple-in-c17 template size_t HashTupleN(const std::tuple& tup, const std::index_sequence&) { size_t seed = 0; for (const auto& hash : {Hash(std::get(tup))...}) { seed = HashCombine(seed, hash); } return seed; } template size_t HashTuple(const std::tuple& tup) { return HashTupleN(tup, std::make_index_sequence()); } // - template auto MembersEq(const T& a, const T& b) { const auto atup = a.Members(); const auto btup = b.Members(); return atup == btup; } template auto MembersLt(const T& a, const T& b) { const auto atup = a.Members(); const auto btup = b.Members(); return atup == btup; } template auto MembersHash(const T& a) { const auto atup = a.Members(); return HashTuple(atup); } template struct MembersHasher final { auto operator()(const T& v) const { return v.hash(); } }; /** E.g.: struct Foo { int i; bool b; auto Members() const { return std::tie(i, b); } INLINE_AUTO_MAPPABLE(Foo) }; std::unordered_set easy; **/ #define INLINE_DERIVE_MEMBERS_EQ(T) \ friend bool operator==(const T& a, const T& b) { \ return mozilla::gfx::MembersEq(a, b); \ } \ friend bool operator!=(const T& a, const T& b) { return !operator==(a, b); } #define INLINE_AUTO_MAPPABLE(T) \ friend bool operator<(const T& a, const T& b) { \ return mozilla::gfx::MembersLt(a, b); \ } \ INLINE_DERIVE_MEMBERS_EQ(T) \ size_t hash() const { \ return mozilla::gfx::MembersHash(*reinterpret_cast(this)); \ } \ using Hasher = mozilla::gfx::MembersHasher; // - /** E.g.: ``` struct Foo : public AutoMappable { int i; bool b; auto Members() const { return std::tie(i, b); } }; std::unordered_set easy; ``` `easy.insert({{}, 2, true});` The initial {} is needed for aggregate initialization of AutoMappable. Use INLINE_AUTO_MAPPABLE if this is too annoying. **/ template struct AutoMappable { INLINE_AUTO_MAPPABLE(T) }; } // namespace mozilla::gfx #endif // MOZILLA_AUTO_MAPPABLE_H