Commit b51d7408 authored by Nathan Bronson's avatar Nathan Bronson Committed by Facebook Github Bot

heterogeneous mutation for F14 maps and sets

Summary:
This diff extends heterogeneous key handling (when the hasher and
key equality functor are transparent) to mutating operations.  If a key
of type K was previously eligible for heterogeneous find and key_type can
be constructed from the key, then that construction can be deferred until
after it is verified that the key is not previously in the table when
using any of the operations that may insert a new element.  If the key was
already present then this avoids the construction of key_type entirely.
Heterogeneous erase is also added in the obvious fashion.

Construction of key_type from K can be accomplished without key_type's
cooperation by the use of an explicit conversion operator, such as the
explicit operator std::string recently added to StringPiece.  That means
that a F14 map or set with std::string key and transparent StringPiece
hash and equality can now use StringPiece natively as a key.

Destructuring is applied whenever possible, so the heterogeneous key
type K may be found as the first element of a pair (for map insert or
emplace) or even inside the first tuple when using map emplace with
std::piecewise_construct.

This diff adds heterogeneous key support to

    Map::insert
    Map::insert_or_assign
    Map::emplace
    Map::try_emplace
    Map::operator[]
    Set::insert
    Set::emplace

Reviewed By: yfeldblum

Differential Revision: D8301887

fbshipit-source-id: 1a45e13739c550f094afe09123839a3c9cd892ff
parent 54b202c0
......@@ -61,7 +61,7 @@ large entries. F14NodeMap is the only F14 variant that doesn't move
its elements, so in the rare case that you need reference stability you
should use it.
## TRANSPARENT (HETEROGENEOUS) HASH AND EQUALITY
## HETEROGENEOUS KEY TYPE WITH TRANSPARENT HASH AND EQUALITY
In some cases it makes sense to define hash and key equality across
types. For example, StringPiece's hash and equality are capable of
......@@ -71,12 +71,18 @@ as _transparent_, then F14 will allow you to search the table directly
using any of the accepted key types without converting the key.
For example, using H =
folly::transparent<folly::hasher<folly::StringPiece>> and E
= folly::transparent<std::equal_to<folly::StringPiece>>, an
F14FastSet<std::string, H, E> will allow you to find or count using
a StringPiece key (as well as std::string key). Note that this is
possible even though there is no implicit conversion from StringPiece
to std::string.
folly::transparent<folly::hasher<folly::StringPiece>> and
E = folly::transparent<std::equal_to<folly::StringPiece>>, an
F14FastSet<std::string, H, E> will allow you to use a StringPiece key
without the need to construct a std::string.
Heterogeneous lookup and erase works for any key types that can be passed
to operator() on the hasher and key_equal functors. For operations
such as operator[] that might insert there is an additional constraint,
which is that the passed-in key must be explicitly convertible to the
table's key_type. F14 maps understand all possible forms that can be
used to construct the underlying std::pair<key_type const, value_type),
so heterogeneous keys can be used even with insert and emplace.
## WHY CHUNKS?
......
This diff is collapsed.
......@@ -113,12 +113,33 @@ namespace detail {
template <typename Policy>
class F14BasicSet {
template <
typename K,
typename T,
typename H = typename Policy::Hasher,
typename E = typename Policy::KeyEqual>
using IfIsTransparent = folly::_t<EnableIfIsTransparent<void, H, E, K, T>>;
template <typename K, typename T>
using EnableHeterogeneousFind = std::enable_if_t<
EligibleForHeterogeneousFind<
typename Policy::Value,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value,
T>;
template <typename K, typename T>
using EnableHeterogeneousInsert = std::enable_if_t<
EligibleForHeterogeneousInsert<
typename Policy::Value,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value,
T>;
template <typename K, typename T>
using EnableHeterogeneousErase = std::enable_if_t<
EligibleForHeterogeneousFind<
typename Policy::Value,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value &&
!std::is_same<typename Policy::Iter, folly::remove_cvref_t<K>>::value,
T>;
public:
//// PUBLIC - Member types
......@@ -306,6 +327,11 @@ class F14BasicSet {
return insert(std::move(value)).first;
}
template <typename K>
EnableHeterogeneousInsert<K, std::pair<iterator, bool>> insert(K&& value) {
return emplace(std::forward<K>(value));
}
private:
template <class InputIt>
FOLLY_ALWAYS_INLINE void
......@@ -354,36 +380,18 @@ class F14BasicSet {
insert(ilist.begin(), ilist.end());
}
private:
std::pair<ItemIter, bool> emplaceItem() {
// rare but valid
return table_.tryEmplaceValue(key_type{});
}
std::pair<ItemIter, bool> emplaceItem(key_type&& key) {
// best case
return table_.tryEmplaceValue(key, std::move(key));
}
template <class... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
using K = KeyTypeForEmplace<key_type, hasher, key_equal, Args...>;
std::pair<ItemIter, bool> emplaceItem(key_type const& key) {
// okay case, no construction unless we will actually insert
return table_.tryEmplaceValue(key, key);
}
// If args is a single arg that can be emplaced directly (either
// key_type or a heterogeneous find + conversion to key_type) key will
// just be a reference to that arg, otherwise this will construct an
// intermediate key.
K key(std::forward<Args>(args)...);
template <typename Arg0, typename... Args>
std::enable_if_t<
sizeof...(Args) != 0 ||
!std::is_same<folly::remove_cvref_t<Arg0>, key_type>::value,
std::pair<ItemIter, bool>>
emplaceItem(Arg0&& arg0, Args&&... args) {
key_type key(std::forward<Arg0>(arg0), std::forward<Args>(args)...);
return table_.tryEmplaceValue(key, std::move(key));
}
auto rv = table_.tryEmplaceValue(key, std::forward<K>(key));
public:
template <class... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
auto rv = emplaceItem(std::forward<Args>(args)...);
return std::make_pair(table_.makeIter(rv.first), rv.second);
}
......@@ -404,6 +412,11 @@ class F14BasicSet {
return eraseInto(key, [](value_type&&) {});
}
template <typename K>
EnableHeterogeneousErase<K, size_type> erase(K const& key) {
return eraseInto(key, [](value_type&&) {});
}
// eraseInto contains the same overloads as erase but provides
// an additional callback argument which is called with an rvalue
// reference to the item directly before it is destroyed. This can be
......@@ -411,7 +424,7 @@ class F14BasicSet {
template <typename BeforeDestroy>
FOLLY_ALWAYS_INLINE iterator
eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
table_.eraseInto(table_.unwrapIter(pos), beforeDestroy);
table_.eraseIterInto(table_.unwrapIter(pos), beforeDestroy);
// If we are inlined then gcc and clang can optimize away all of the
// work of ++pos if the caller discards it.
......@@ -431,7 +444,14 @@ class F14BasicSet {
template <typename BeforeDestroy>
size_type eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
return table_.eraseInto(key, beforeDestroy);
return table_.eraseKeyInto(key, beforeDestroy);
}
template <typename K, typename BeforeDestroy>
EnableHeterogeneousErase<K, size_type> eraseInto(
K const& key,
BeforeDestroy&& beforeDestroy) {
return table_.eraseKeyInto(key, beforeDestroy);
}
//// PUBLIC - Lookup
......@@ -441,7 +461,8 @@ class F14BasicSet {
}
template <typename K>
FOLLY_ALWAYS_INLINE IfIsTransparent<K, size_type> count(K const& key) const {
FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, size_type> count(
K const& key) const {
return table_.find(key).atEnd() ? 0 : 1;
}
......@@ -464,7 +485,7 @@ class F14BasicSet {
}
template <typename K>
IfIsTransparent<K, F14HashToken> prehash(K const& key) const {
EnableHeterogeneousFind<K, F14HashToken> prehash(K const& key) const {
return table_.prehash(key);
}
......@@ -487,25 +508,25 @@ class F14BasicSet {
}
template <typename K>
FOLLY_ALWAYS_INLINE IfIsTransparent<K, iterator> find(K const& key) {
FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, iterator> find(K const& key) {
return const_cast<F14BasicSet const*>(this)->find(key);
}
template <typename K>
FOLLY_ALWAYS_INLINE IfIsTransparent<K, const_iterator> find(
FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, const_iterator> find(
K const& key) const {
return table_.makeIter(table_.find(key));
}
template <typename K>
FOLLY_ALWAYS_INLINE IfIsTransparent<K, iterator> find(
FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, iterator> find(
F14HashToken const& token,
K const& key) {
return const_cast<F14BasicSet const*>(this)->find(token, key);
}
template <typename K>
FOLLY_ALWAYS_INLINE IfIsTransparent<K, const_iterator> find(
FOLLY_ALWAYS_INLINE EnableHeterogeneousFind<K, const_iterator> find(
F14HashToken const& token,
K const& key) const {
return table_.makeIter(table_.find(token, key));
......@@ -521,13 +542,14 @@ class F14BasicSet {
}
template <typename K>
IfIsTransparent<K, std::pair<iterator, iterator>> equal_range(K const& key) {
EnableHeterogeneousFind<K, std::pair<iterator, iterator>> equal_range(
K const& key) {
return equal_range(*this, key);
}
template <typename K>
IfIsTransparent<K, std::pair<const_iterator, const_iterator>> equal_range(
K const& key) const {
EnableHeterogeneousFind<K, std::pair<const_iterator, const_iterator>>
equal_range(K const& key) const {
return equal_range(*this, key);
}
......@@ -736,13 +758,27 @@ class F14VectorSet
Alloc>;
using Super = f14::detail::F14BasicSet<Policy>;
template <typename K, typename T>
using EnableHeterogeneousVectorErase = std::enable_if_t<
f14::detail::EligibleForHeterogeneousFind<
typename Policy::Value,
typename Policy::Hasher,
typename Policy::KeyEqual,
K>::value &&
!std::is_same<typename Policy::Iter, folly::remove_cvref_t<K>>::
value &&
!std::is_same<
typename Policy::ReverseIter,
folly::remove_cvref_t<K>>::value,
T>;
public:
using typename Super::const_iterator;
using typename Super::iterator;
using typename Super::key_type;
using typename Super::value_type;
using reverse_iterator = typename Policy::ReverseIter;
using const_reverse_iterator = typename Policy::ConstReverseIter;
using const_reverse_iterator = reverse_iterator;
F14VectorSet() noexcept(Policy::kDefaultConstructIsNoexcept) : Super{} {}
......@@ -839,7 +875,7 @@ class F14VectorSet
// destroy the value and remove the ptr from the base table
auto index = underlying.item();
this->table_.eraseInto(underlying, beforeDestroy);
this->table_.eraseIterInto(underlying, beforeDestroy);
Policy::AllocTraits::destroy(a, std::addressof(values[index]));
// move the last element in values_ down and fix up the inbound index
......@@ -854,6 +890,17 @@ class F14VectorSet
}
}
template <typename K, typename BeforeDestroy>
std::size_t eraseUnderlyingKey(K const& key, BeforeDestroy&& beforeDestroy) {
auto underlying = this->table_.find(key);
if (underlying.atEnd()) {
return 0;
} else {
eraseUnderlying(underlying, beforeDestroy);
return 1;
}
}
public:
FOLLY_ALWAYS_INLINE iterator erase(const_iterator pos) {
return eraseInto(pos, [](value_type&&) {});
......@@ -863,10 +910,19 @@ class F14VectorSet
return eraseInto(first, last, [](value_type&&) {});
}
// No erase is provided for reverse_iterator (AKA const_reverse_iterator)
// to make it harder to shoot yourself in the foot by erasing while
// reverse-iterating. You can write that as set.erase(set.iter(riter)).
std::size_t erase(key_type const& key) {
return eraseInto(key, [](value_type&&) {});
}
template <typename K>
EnableHeterogeneousVectorErase<K, std::size_t> erase(K const& key) {
return eraseInto(key, [](value_type&&) {});
}
template <typename BeforeDestroy>
FOLLY_ALWAYS_INLINE iterator
eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
......@@ -889,13 +945,14 @@ class F14VectorSet
template <typename BeforeDestroy>
std::size_t eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
auto underlying = this->table_.find(key);
if (underlying.atEnd()) {
return 0;
} else {
eraseUnderlying(underlying, beforeDestroy);
return 1;
return eraseUnderlyingKey(key, beforeDestroy);
}
template <typename K, typename BeforeDestroy>
EnableHeterogeneousVectorErase<K, std::size_t> eraseInto(
K const& key,
BeforeDestroy&& beforeDestroy) {
return eraseUnderlyingKey(key, beforeDestroy);
}
};
......
......@@ -183,24 +183,64 @@ using Defaulted =
typename std::conditional_t<std::is_same<Arg, void>::value, Default, Arg>;
template <
typename Void,
typename TableKey,
typename Hasher,
typename KeyEqual,
typename Key,
typename T>
struct EnableIfIsTransparent {};
typename ArgKey,
typename Void = void>
struct EligibleForHeterogeneousFind : std::false_type {};
template <typename Hasher, typename KeyEqual, typename Key, typename T>
struct EnableIfIsTransparent<
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename ArgKey>
struct EligibleForHeterogeneousFind<
TableKey,
Hasher,
KeyEqual,
ArgKey,
folly::void_t<
typename Hasher::is_transparent,
typename KeyEqual::is_transparent>,
typename KeyEqual::is_transparent>> : std::true_type {};
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename ArgKey>
using EligibleForHeterogeneousInsert = Conjunction<
EligibleForHeterogeneousFind<TableKey, Hasher, KeyEqual, ArgKey>,
std::is_constructible<TableKey, ArgKey>>;
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename KeyArg0OrBool,
typename... KeyArgs>
using KeyTypeForEmplaceHelper = std::conditional_t<
sizeof...(KeyArgs) == 1 &&
(std::is_same<folly::remove_cvref_t<KeyArg0OrBool>, TableKey>::value ||
EligibleForHeterogeneousFind<
TableKey,
Hasher,
KeyEqual,
Key,
T> {
using type = T;
};
KeyArg0OrBool>::value),
KeyArg0OrBool&&,
TableKey>;
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename... KeyArgs>
using KeyTypeForEmplace = KeyTypeForEmplaceHelper<
TableKey,
Hasher,
KeyEqual,
std::tuple_element_t<0, std::tuple<KeyArgs..., bool>>,
KeyArgs...>;
////////////////
......@@ -1827,16 +1867,16 @@ class F14Table : public Policy {
public:
// The item needs to still be hashable during this call. If you want
// to intercept the value before it is destroyed (to extract it, for
// example), use eraseInto(pos, beforeDestroy).
void erase(ItemIter pos) {
eraseInto(pos, [](value_type&&) {});
// example), use eraseIterInto(pos, beforeDestroy).
void eraseIter(ItemIter pos) {
eraseIterInto(pos, [](value_type&&) {});
}
// The item needs to still be hashable during this call. If you want
// to intercept the value before it is destroyed (to extract it, for
// example), do so in the beforeDestroy callback.
template <typename BeforeDestroy>
void eraseInto(ItemIter pos, BeforeDestroy&& beforeDestroy) {
void eraseIterInto(ItemIter pos, BeforeDestroy&& beforeDestroy) {
HashPair hp{};
if (pos.chunk()->hostedOverflowCount() != 0) {
hp = splitHash(this->computeItemHash(pos.citem()));
......@@ -1846,12 +1886,12 @@ class F14Table : public Policy {
}
template <typename K>
std::size_t erase(K const& key) {
return eraseInto(key, [](value_type&&) {});
std::size_t eraseKey(K const& key) {
return eraseKeyInto(key, [](value_type&&) {});
}
template <typename K, typename BeforeDestroy>
std::size_t eraseInto(K const& key, BeforeDestroy&& beforeDestroy) {
std::size_t eraseKeyInto(K const& key, BeforeDestroy&& beforeDestroy) {
if (UNLIKELY(size() == 0)) {
return 0;
}
......
......@@ -764,7 +764,7 @@ TEST(Tracked, baseline) {
// and a pair const& or pair&& and cause it to be inserted
template <typename M, typename F>
void runInsertCases(
std::string const& /* name */,
std::string const& name,
F const& insertFunc,
uint64_t expectedDist = 0) {
static_assert(std::is_same<typename M::key_type, Tracked<0>>::value, "");
......@@ -779,7 +779,9 @@ void runInsertCases(
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{1, 0, 0, 0}),
expectedDist);
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
}
{
typename M::value_type p{0, 0};
......@@ -791,7 +793,9 @@ void runInsertCases(
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 1, 0, 0}),
expectedDist);
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
......@@ -803,7 +807,9 @@ void runInsertCases(
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{1, 0, 0, 0}),
expectedDist);
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
......@@ -815,7 +821,9 @@ void runInsertCases(
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 1, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 1, 0, 0}),
expectedDist);
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
}
{
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
......@@ -1244,6 +1252,85 @@ TEST(F14FastMap, statefulFunctors) {
GenericAlloc<std::pair<int const, int>>>>();
}
template <typename M>
void runHeterogeneousInsertTest() {
M map;
resetTracking();
EXPECT_EQ(map.count(10), 0);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
resetTracking();
map[10] = 20;
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 1}), 0)
<< Tracked<1>::counts;
resetTracking();
std::pair<int, int> p(10, 30);
std::vector<std::pair<int, int>> v({p});
map[10] = 30;
map.insert(std::pair<int, int>(10, 30));
map.insert(std::pair<int const, int>(10, 30));
map.insert(p);
map.insert(v.begin(), v.end());
map.insert(
std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
map.insert_or_assign(10, 40);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
resetTracking();
map.emplace(10, 30);
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(10),
std::forward_as_tuple(30));
map.emplace(p);
map.try_emplace(10, 30);
map.try_emplace(10);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
resetTracking();
map.erase(10);
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
}
TEST(F14ValueMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14ValueMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
TEST(F14NodeMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14NodeMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
TEST(F14VectorMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14VectorMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
TEST(F14FastMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14FastMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
///////////////////////////////////
#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
......@@ -888,6 +888,80 @@ TEST(F14FastSet, statefulFunctors) {
GenericAlloc<int>>>();
}
template <typename S>
void runHeterogeneousInsertTest() {
S set;
resetTracking();
EXPECT_EQ(set.count(10), 0);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
resetTracking();
set.insert(10);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 1}), 0)
<< Tracked<1>::counts;
resetTracking();
int k = 10;
std::vector<int> v({10});
set.insert(10);
set.insert(k);
set.insert(v.begin(), v.end());
set.insert(
std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
set.emplace(10);
set.emplace(k);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
resetTracking();
set.erase(20);
EXPECT_EQ(set.size(), 1);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
resetTracking();
set.erase(10);
EXPECT_EQ(set.size(), 0);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
set.insert(10);
resetTracking();
set.eraseInto(10, [](auto&&) {});
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
}
TEST(F14ValueSet, heterogeneousInsert) {
runHeterogeneousInsertTest<F14ValueSet<
Tracked<1>,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
TEST(F14NodeSet, heterogeneousInsert) {
runHeterogeneousInsertTest<F14NodeSet<
Tracked<1>,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
TEST(F14VectorSet, heterogeneousInsert) {
runHeterogeneousInsertTest<F14VectorSet<
Tracked<1>,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
TEST(F14FastSet, heterogeneousInsert) {
runHeterogeneousInsertTest<F14FastSet<
Tracked<1>,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
}
///////////////////////////////////
#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
......@@ -277,6 +277,35 @@ struct Tracked {
}
};
template <int Tag>
struct TransparentTrackedHash {
using is_transparent = std::true_type;
size_t operator()(Tracked<Tag> const& tracked) const {
return tracked.val_ ^ Tag;
}
size_t operator()(uint64_t v) const {
return v ^ Tag;
}
};
template <int Tag>
struct TransparentTrackedEqual {
using is_transparent = std::true_type;
uint64_t unwrap(Tracked<Tag> const& v) const {
return v.val_;
}
uint64_t unwrap(uint64_t v) const {
return v;
}
template <typename A, typename B>
bool operator()(A const& lhs, B const& rhs) const {
return unwrap(lhs) == unwrap(rhs);
}
};
template <>
thread_local Counts Tracked<0>::counts{};
template <>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment