Commit ce0007aa authored by Nathan Bronson's avatar Nathan Bronson Committed by Facebook GitHub Bot

heterogeneous lookup for F14 map fallback

Summary:
This diff implements efficient heterogeneous lookup, insert,
and erase for the fallback (non-SIMD) mode of F14.  It relies on internal
implementation details of the underlying std::unordered_map implementation
that happen to be true for libstdc++, libc++, and MSVC.

Extending this technique to F14 sets would be straightforward, but has
not been done here.

(Note: this ignores all push blocking failures!)

Differential Revision: D21408628

fbshipit-source-id: 72e9ddd82a50c02ec762d4b64dc3303848bd0218
parent 5d517eb7
......@@ -44,6 +44,14 @@
#define FOLLY_F14_CRC_INTRINSIC_AVAILABLE 0
#endif
// The F14 extension eraseInto is only available in fallback mode for
// c++17 or later, because it relies on unordered_map::extract.
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE || __cplusplus >= 201703L
#define FOLLY_F14_ERASE_INTO_AVAILABLE 1
#else
#define FOLLY_F14_ERASE_INTO_AVAILABLE 0
#endif
namespace folly {
namespace f14 {
namespace detail {
......
......@@ -24,6 +24,11 @@
#include <unordered_map>
#include <algorithm>
#include <folly/Optional.h>
#include <folly/lang/Assume.h>
namespace folly {
namespace f14 {
......@@ -32,17 +37,471 @@ template <typename K, typename M, typename H, typename E, typename A>
class F14BasicMap : public std::unordered_map<K, M, H, E, A> {
using Super = std::unordered_map<K, M, H, E, A>;
template <typename K2, typename T>
using EnableHeterogeneousFind =
std::enable_if_t<EligibleForHeterogeneousFind<K, H, E, K2>::value, T>;
template <typename K2, typename T>
using EnableHeterogeneousInsert =
std::enable_if_t<EligibleForHeterogeneousInsert<K, H, E, K2>::value, T>;
template <typename K2>
using IsIter = Disjunction<
std::is_same<typename Super::iterator, remove_cvref_t<K2>>,
std::is_same<typename Super::const_iterator, remove_cvref_t<K2>>>;
template <typename K2, typename T>
using EnableHeterogeneousErase = std::enable_if_t<
EligibleForHeterogeneousFind<
K,
H,
E,
std::conditional_t<IsIter<K2>::value, K, K2>>::value &&
!IsIter<K2>::value,
T>;
public:
using typename Super::const_iterator;
using typename Super::hasher;
using typename Super::iterator;
using typename Super::key_equal;
using typename Super::key_type;
using typename Super::mapped_type;
using typename Super::pointer;
using typename Super::size_type;
using typename Super::value_type;
F14BasicMap() = default;
using Super::Super;
//// PUBLIC - Modifiers
std::pair<iterator, bool> insert(value_type const& value) {
return emplace(value);
}
template <typename P>
std::enable_if_t<
std::is_constructible<value_type, P&&>::value,
std::pair<iterator, bool>>
insert(P&& value) {
return emplace(std::forward<P>(value));
}
// TODO(T31574848): Work around libstdc++ versions (e.g., GCC < 6) with no
// implementation of N4387 ("perfect initialization" for pairs and tuples).
template <typename U1, typename U2>
std::enable_if_t<
std::is_constructible<key_type, U1 const&>::value &&
std::is_constructible<mapped_type, U2 const&>::value,
std::pair<iterator, bool>>
insert(std::pair<U1, U2> const& value) {
return emplace(value);
}
// TODO(T31574848)
template <typename U1, typename U2>
std::enable_if_t<
std::is_constructible<key_type, U1&&>::value &&
std::is_constructible<mapped_type, U2&&>::value,
std::pair<iterator, bool>>
insert(std::pair<U1, U2>&& value) {
return emplace(std::move(value));
}
std::pair<iterator, bool> insert(value_type&& value) {
return emplace(std::move(value));
}
iterator insert(const_iterator /*hint*/, value_type const& value) {
return insert(value).first;
}
template <typename P>
std::enable_if_t<std::is_constructible<value_type, P&&>::value, iterator>
insert(const_iterator /*hint*/, P&& value) {
return insert(std::forward<P>(value)).first;
}
iterator insert(const_iterator /*hint*/, value_type&& value) {
return insert(std::move(value)).first;
}
template <class... Args>
iterator emplace_hint(const_iterator /*hint*/, Args&&... args) {
return emplace(std::forward<Args>(args)...).first;
}
template <class InputIt>
void insert(InputIt first, InputIt last) {
while (first != last) {
insert(*first);
++first;
}
}
void insert(std::initializer_list<value_type> ilist) {
insert(ilist.begin(), ilist.end());
}
template <typename M2>
std::pair<iterator, bool> insert_or_assign(key_type const& key, M2&& obj) {
auto rv = try_emplace(key, std::forward<M2>(obj));
if (!rv.second) {
rv.first->second = std::forward<M>(obj);
}
return rv;
}
template <typename M2>
std::pair<iterator, bool> insert_or_assign(key_type&& key, M2&& obj) {
auto rv = try_emplace(std::move(key), std::forward<M2>(obj));
if (!rv.second) {
rv.first->second = std::forward<M>(obj);
}
return rv;
}
template <typename M2>
iterator
insert_or_assign(const_iterator /*hint*/, key_type const& key, M2&& obj) {
return insert_or_assign(key, std::forward<M2>(obj)).first;
}
template <typename M2>
iterator insert_or_assign(const_iterator /*hint*/, key_type&& key, M2&& obj) {
return insert_or_assign(std::move(key), std::forward<M2>(obj)).first;
}
template <typename K2, typename M2>
EnableHeterogeneousInsert<K2, std::pair<iterator, bool>> insert_or_assign(
K2&& key,
M2&& obj) {
auto rv = try_emplace(std::forward<K2>(key), std::forward<M2>(obj));
if (!rv.second) {
rv.first->second = std::forward<M2>(obj);
}
return rv;
}
private:
template <typename Arg>
using UsableAsKey =
EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
public:
template <typename... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
auto alloc = this->get_allocator();
return folly::detail::callWithExtractedKey<
key_type,
mapped_type,
UsableAsKey>(
alloc,
[&](auto& key, auto&&... inner) {
if (!std::is_same<key_type, remove_cvref_t<decltype(key)>>::value) {
// this is a heterogeneous lookup
auto it = find(key);
if (it != this->end()) {
return std::make_pair(it, false);
}
auto rv = Super::emplace(std::forward<decltype(inner)>(inner)...);
FOLLY_SAFE_CHECK(
rv.second, "post-find emplace should always insert");
return rv;
} else {
// callWithExtractedKey will use 2 inner args if possible, which
// maximizes the changes for the STL emplace to search for an
// existing key before constructing a value_type
return Super::emplace(std::forward<decltype(inner)>(inner)...);
}
},
std::forward<Args>(args)...);
}
template <typename... Args>
std::pair<iterator, bool> try_emplace(key_type const& key, Args&&... args) {
return emplace(
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
return emplace(
std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
iterator
try_emplace(const_iterator /*hint*/, key_type const& key, Args&&... args) {
return emplace(
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(std::forward<Args>(args)...))
.first;
}
template <typename... Args>
iterator
try_emplace(const_iterator /*hint*/, key_type&& key, Args&&... args) {
return emplace(
std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
std::forward_as_tuple(std::forward<Args>(args)...))
.first;
}
template <typename K2, typename... Args>
EnableHeterogeneousInsert<K2, std::pair<iterator, bool>> try_emplace(
K2&& key,
Args&&... args) {
return emplace(
std::piecewise_construct,
std::forward_as_tuple(std::forward<K2>(key)),
std::forward_as_tuple(std::forward<Args>(args)...));
}
using Super::erase;
template <typename K2>
EnableHeterogeneousErase<K2, size_type> erase(K2 const& key) {
auto it = find(key);
if (it != this->end()) {
erase(it);
return 1;
} else {
return 0;
}
}
//// PUBLIC - Lookup
private:
template <typename K2>
struct BottomKeyEqual {
[[noreturn]] bool operator()(K2 const&, K2 const&) const {
FOLLY_SAFE_CHECK(false, "bucket should not invoke key equality");
assume_unreachable();
}
};
template <typename Self, typename K2>
static auto findLocal(Self& self, K2 const& key)
-> folly::Optional<decltype(self.begin(0))> {
if (self.empty()) {
return none;
}
using A2 = typename std::allocator_traits<A>::template rebind_alloc<
std::pair<K2 const, M>>;
using E2 = BottomKeyEqual<K2>;
// this is exceedingly wicked!
auto slot =
reinterpret_cast<std::unordered_map<K2, M, H, E2, A2> const&>(self)
.bucket(key);
auto b = self.begin(slot);
auto e = self.end(slot);
while (b != e) {
if (self.key_eq()(key, b->first)) {
return b;
}
++b;
}
FOLLY_SAFE_DCHECK(
self.size() > 3 ||
std::none_of(
self.begin(),
self.end(),
[&](auto const& kv) { return self.key_eq()(key, kv.first); }),
"");
return none;
}
template <typename Self, typename K2>
static auto& atImpl(Self& self, K2 const& key) {
auto it = findLocal(self, key);
if (!it) {
throw_exception<std::out_of_range>("at() did not find key");
}
return (*it)->second;
}
public:
mapped_type& at(key_type const& key) {
return Super::at(key);
}
mapped_type const& at(key_type const& key) const {
return Super::at(key);
}
template <typename K2>
EnableHeterogeneousFind<K2, mapped_type&> at(K2 const& key) {
return atImpl(*this, key);
}
template <typename K2>
EnableHeterogeneousFind<K2, mapped_type const&> at(K2 const& key) const {
return atImpl(*this, key);
}
using Super::operator[];
template <typename K2>
EnableHeterogeneousInsert<K2, mapped_type&> operator[](K2&& key) {
return try_emplace(std::forward<K2>(key)).first->second;
}
size_type count(key_type const& key) const {
return Super::count(key);
}
template <typename K2>
EnableHeterogeneousFind<K2, size_type> count(K2 const& key) const {
return !findLocal(*this, key) ? 0 : 1;
}
bool contains(key_type const& key) const {
return count(key) != 0;
}
template <typename K2>
EnableHeterogeneousFind<K2, bool> contains(K2 const& key) const {
return count(key) != 0;
}
private:
template <typename Iter, typename Self, typename K2>
static Iter findImpl(Self& self, K2 const& key) {
auto optLocalIt = findLocal(self, key);
if (!optLocalIt) {
return self.end();
} else {
Iter it;
static_assert(sizeof(it) <= sizeof(*optLocalIt), "");
std::memcpy(&it, &*optLocalIt, sizeof(it));
FOLLY_SAFE_CHECK(
std::addressof(**optLocalIt) == std::addressof(*it),
"ABI-assuming local_iterator to iterator conversion failed");
return it;
}
}
public:
iterator find(key_type const& key) {
return Super::find(key);
}
const_iterator find(key_type const& key) const {
return Super::find(key);
}
template <typename K2>
EnableHeterogeneousFind<K2, iterator> find(K2 const& key) {
return findImpl<iterator>(*this, key);
}
template <typename K2>
EnableHeterogeneousFind<K2, const_iterator> find(K2 const& key) const {
return findImpl<const_iterator>(*this, key);
}
private:
template <typename Self, typename K2>
static auto equalRangeImpl(Self& self, K2 const& key) {
auto first = self.find(key);
auto last = first;
if (last != self.end()) {
++last;
}
return std::make_pair(first, last);
}
public:
using Super::equal_range;
template <typename K2>
EnableHeterogeneousFind<K2, std::pair<iterator, iterator>> equal_range(
K2 const& key) {
return equalRangeImpl(*this, key);
}
template <typename K2>
EnableHeterogeneousFind<K2, std::pair<const_iterator, const_iterator>>
equal_range(K2 const& key) const {
return equalRangeImpl(*this, key);
}
//// PUBLIC - F14 Extensions
#if FOLLY_F14_ERASE_INTO_AVAILABLE
// emulation of eraseInto requires unordered_map::extract
template <typename BeforeDestroy>
iterator eraseInto(const_iterator pos, BeforeDestroy&& beforeDestroy) {
iterator it = erase(pos, pos);
FOLLY_SAFE_CHECK(std::addressof(*it) == std::addressof(*pos), "");
return eraseInto(it, beforeDestroy);
}
template <typename BeforeDestroy>
iterator eraseInto(iterator pos, BeforeDestroy&& beforeDestroy) {
const_iterator prev{pos};
++pos;
auto nh = this->extract(prev);
FOLLY_SAFE_CHECK(!nh.empty(), "");
beforeDestroy(std::move(nh.key()), std::move(nh.mapped()));
return pos;
}
template <typename BeforeDestroy>
iterator eraseInto(
const_iterator first,
const_iterator last,
BeforeDestroy&& beforeDestroy) {
iterator pos = erase(first, first);
FOLLY_SAFE_CHECK(std::addressof(*pos) == std::addressof(*first), "");
while (pos != last) {
pos = eraseInto(pos, beforeDestroy);
}
return pos;
}
private:
template <typename K2, typename BeforeDestroy>
size_type eraseIntoImpl(K2 const& key, BeforeDestroy& beforeDestroy) {
auto it = find(key);
if (it != this->end()) {
eraseInto(it, beforeDestroy);
return 1;
} else {
return 0;
}
}
public:
template <typename BeforeDestroy>
size_type eraseInto(key_type const& key, BeforeDestroy&& beforeDestroy) {
return eraseIntoImpl(key, beforeDestroy);
}
template <typename K2, typename BeforeDestroy>
EnableHeterogeneousErase<K2, size_type> eraseInto(
K2 const& key,
BeforeDestroy&& beforeDestroy) {
return eraseIntoImpl(key, beforeDestroy);
}
#endif
bool containsEqualValue(value_type const& value) const {
// bucket isn't valid if bucket_count is zero
if (this->empty()) {
return false;
}
auto slot = this->bucket(value.first);
auto e = this->end(slot);
for (auto b = this->begin(slot); b != e; ++b) {
......@@ -141,6 +600,8 @@ class F14VectorMap
using Super = f14::detail::F14BasicMap<Key, Mapped, Hasher, KeyEqual, Alloc>;
public:
using typename Super::const_iterator;
using typename Super::iterator;
using typename Super::value_type;
F14VectorMap() = default;
......
......@@ -43,6 +43,10 @@ class F14BasicSet : public std::unordered_set<K, H, E, A> {
//// PUBLIC - F14 Extensions
bool containsEqualValue(value_type const& value) const {
// bucket is only valid if bucket_count is non-zero
if (this->empty()) {
return false;
}
auto slot = this->bucket(value);
auto e = this->end(slot);
for (auto b = this->begin(slot); b != e; ++b) {
......
......@@ -198,6 +198,27 @@ void erase_if_impl(Container& c, Predicate& predicate) {
}
}
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename ArgKey>
struct EligibleForHeterogeneousFind
: Conjunction<
is_transparent<Hasher>,
is_transparent<KeyEqual>,
is_invocable<Hasher, ArgKey const&>,
is_invocable<KeyEqual, ArgKey const&, TableKey const&>> {};
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename ArgKey>
using EligibleForHeterogeneousInsert = Conjunction<
EligibleForHeterogeneousFind<TableKey, Hasher, KeyEqual, ArgKey>,
std::is_constructible<TableKey, ArgKey>>;
} // namespace detail
} // namespace f14
......@@ -239,27 +260,6 @@ template <typename Arg, typename Default>
using Defaulted =
std::conditional_t<std::is_same<Arg, void>::value, Default, Arg>;
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename ArgKey>
struct EligibleForHeterogeneousFind
: Conjunction<
is_transparent<Hasher>,
is_transparent<KeyEqual>,
is_invocable<Hasher, ArgKey const&>,
is_invocable<KeyEqual, ArgKey const&, TableKey const&>> {};
template <
typename TableKey,
typename Hasher,
typename KeyEqual,
typename ArgKey>
using EligibleForHeterogeneousInsert = Conjunction<
EligibleForHeterogeneousFind<TableKey, Hasher, KeyEqual, ArgKey>,
std::is_constructible<TableKey, ArgKey>>;
////////////////
template <typename T>
......
......@@ -28,6 +28,11 @@
#include <folly/container/test/TrackingTypes.h>
#include <folly/portability/GTest.h>
using namespace folly;
using namespace folly::f14;
using namespace folly::string_piece_literals;
using namespace folly::test;
template <template <typename, typename, typename, typename, typename>
class TMap>
void testCustomSwap() {
......@@ -292,6 +297,232 @@ TEST(F14Map, equalityRefinement) {
testEqualityRefinement<folly::F14FastMap>();
}
template <typename M>
void runHeterogeneousInsertTest() {
M map;
resetTracking();
EXPECT_EQ(map.count(10), 0);
EXPECT_FALSE(map.contains(10));
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();
#if FOLLY_F14_ERASE_INTO_AVAILABLE
map.emplace(10, 40);
resetTracking();
map.eraseInto(10, [](auto&&, auto&&) {});
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts();
#endif
}
template <typename M>
void runHeterogeneousInsertStringTest() {
using P = std::pair<StringPiece, std::string>;
using CP = std::pair<const StringPiece, std::string>;
M map;
P p{"foo", "hello"};
std::vector<P> v{p};
StringPiece foo{"foo"};
map.insert(P("foo", "hello"));
// TODO(T31574848): the list-initialization below does not work on libstdc++
// versions (e.g., GCC < 6) with no implementation of N4387 ("perfect
// initialization" for pairs and tuples).
// StringPiece sp{"foo"};
// map.insert({sp, "hello"});
map.insert({"foo", "hello"});
map.insert(CP("foo", "hello"));
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("foo", "hello");
map.insert_or_assign(StringPiece{"foo"}, "hello");
EXPECT_EQ(map["foo"], "hello");
map.emplace(StringPiece{"foo"}, "hello");
map.emplace("foo", "hello");
map.emplace(p);
map.emplace();
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(StringPiece{"foo"}),
std::forward_as_tuple(/* count */ 20, 'x'));
map.try_emplace(StringPiece{"foo"}, "hello");
map.try_emplace(foo, "hello");
map.try_emplace(foo);
map.try_emplace("foo");
map.try_emplace("foo", "hello");
map.try_emplace("foo", /* count */ 20, 'x');
map.erase(StringPiece{"foo"});
map.erase(foo);
map.erase("");
EXPECT_TRUE(map.empty());
}
TEST(F14ValueMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14ValueMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14ValueMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14ValueMap<std::string, std::string>>();
}
TEST(F14NodeMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14NodeMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14NodeMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14NodeMap<std::string, std::string>>();
}
TEST(F14VectorMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14VectorMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14VectorMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14VectorMap<std::string, std::string>>();
}
TEST(F14FastMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14FastMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14FastMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14FastMap<std::string, std::string>>();
}
namespace {
// std::is_convertible is not transitive :( Problem scenario: B<T> is
// implicitly convertible to A, so hasher that takes A can be used as a
// transparent hasher for a map with key of type B<T>. C is implicitly
// convertible to any B<T>, but we have to disable heterogeneous find
// for C. There is no way to infer the T of the intermediate type so C
// can't be used to explicitly construct A.
struct A {
int value;
bool operator==(A const& rhs) const {
return value == rhs.value;
}
bool operator!=(A const& rhs) const {
return !(*this == rhs);
}
};
struct AHasher {
std::size_t operator()(A const& v) const {
return v.value;
}
};
template <typename T>
struct B {
int value;
explicit B(int v) : value(v) {}
/* implicit */ B(A const& v) : value(v.value) {}
/* implicit */ operator A() const {
return A{value};
}
};
struct C {
int value;
template <typename T>
/* implicit */ operator B<T>() const {
return B<T>{value};
}
};
} // namespace
TEST(F14FastMap, disabledDoubleTransparent) {
static_assert(std::is_convertible<B<char>, A>::value, "");
static_assert(std::is_convertible<C, B<char>>::value, "");
static_assert(!std::is_convertible<C, A>::value, "");
F14FastMap<
B<char>,
int,
folly::transparent<AHasher>,
folly::transparent<std::equal_to<A>>>
map;
map[A{10}] = 10;
EXPECT_TRUE(map.find(C{10}) != map.end());
EXPECT_TRUE(map.find(C{20}) == map.end());
}
///////////////////////////////////
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
......@@ -306,8 +537,6 @@ TEST(F14Map, equalityRefinement) {
using namespace folly;
using namespace folly::f14;
using namespace folly::string_piece_literals;
using namespace folly::test;
namespace {
std::string s(char const* p) {
......@@ -1075,8 +1304,8 @@ void runInsertCases(
Tracked<0>::counts().dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{1, 0, 0, 0}),
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
<< name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts();
}
{
typename M::value_type p{0, 0};
......@@ -1089,8 +1318,8 @@ void runInsertCases(
Tracked<0>::counts().dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 1, 0, 0}),
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
<< name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts();
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
......@@ -1103,8 +1332,8 @@ void runInsertCases(
Tracked<0>::counts().dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{1, 0, 0, 0}),
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
<< name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts();
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
......@@ -1117,8 +1346,8 @@ void runInsertCases(
Tracked<0>::counts().dist(Counts{0, 1, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 1, 0, 0}),
expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> "
<< Tracked<1>::counts;
<< name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts();
}
{
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
......@@ -1448,6 +1677,7 @@ TEST(F14FastMap, moveOnly) {
F14FastMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
}
#if FOLLY_F14_ERASE_INTO_AVAILABLE
template <typename M>
void runEraseIntoTest() {
M t0;
......@@ -1513,6 +1743,7 @@ TEST(F14FastMap, eraseInto) {
runEraseIntoTest<
F14FastMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
}
#endif
template <typename M>
void runPermissiveConstructorTest() {
......@@ -1528,12 +1759,14 @@ void runPermissiveConstructorTest() {
EXPECT_EQ(t.size(), 8);
t.erase(ct.find(30));
EXPECT_EQ(t.size(), 7);
#if FOLLY_F14_ERASE_INTO_AVAILABLE
t.eraseInto(40, [](auto&&, auto&&) {});
EXPECT_EQ(t.size(), 6);
t.eraseInto(t.find(50), [](auto&&, auto&&) {});
EXPECT_EQ(t.size(), 5);
t.eraseInto(ct.find(60), [](auto&&, auto&&) {});
EXPECT_EQ(t.size(), 4);
#endif
}
TEST(F14ValueMap, permissiveConstructor) {
......@@ -1685,230 +1918,6 @@ TEST(F14FastMap, statefulFunctors) {
GenericAlloc<std::pair<int const, int>>>>();
}
template <typename M>
void runHeterogeneousInsertTest() {
M map;
resetTracking();
EXPECT_EQ(map.count(10), 0);
EXPECT_FALSE(map.contains(10));
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;
map.emplace(10, 40);
resetTracking();
map.eraseInto(10, [](auto&&, auto&&) {});
EXPECT_EQ(map.size(), 0);
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
}
template <typename M>
void runHeterogeneousInsertStringTest() {
using P = std::pair<StringPiece, std::string>;
using CP = std::pair<const StringPiece, std::string>;
M map;
P p{"foo", "hello"};
std::vector<P> v{p};
StringPiece foo{"foo"};
map.insert(P("foo", "hello"));
// TODO(T31574848): the list-initialization below does not work on libstdc++
// versions (e.g., GCC < 6) with no implementation of N4387 ("perfect
// initialization" for pairs and tuples).
// StringPiece sp{"foo"};
// map.insert({sp, "hello"});
map.insert({"foo", "hello"});
map.insert(CP("foo", "hello"));
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("foo", "hello");
map.insert_or_assign(StringPiece{"foo"}, "hello");
EXPECT_EQ(map["foo"], "hello");
map.emplace(StringPiece{"foo"}, "hello");
map.emplace("foo", "hello");
map.emplace(p);
map.emplace();
map.emplace(
std::piecewise_construct,
std::forward_as_tuple(StringPiece{"foo"}),
std::forward_as_tuple(/* count */ 20, 'x'));
map.try_emplace(StringPiece{"foo"}, "hello");
map.try_emplace(foo, "hello");
map.try_emplace(foo);
map.try_emplace("foo");
map.try_emplace("foo", "hello");
map.try_emplace("foo", /* count */ 20, 'x');
map.erase(StringPiece{"foo"});
map.erase(foo);
map.erase("");
EXPECT_TRUE(map.empty());
}
TEST(F14ValueMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14ValueMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14ValueMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14ValueMap<std::string, std::string>>();
}
TEST(F14NodeMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14NodeMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14NodeMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14NodeMap<std::string, std::string>>();
}
TEST(F14VectorMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14VectorMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14VectorMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14VectorMap<std::string, std::string>>();
}
TEST(F14FastMap, heterogeneousInsert) {
runHeterogeneousInsertTest<F14FastMap<
Tracked<1>,
int,
TransparentTrackedHash<1>,
TransparentTrackedEqual<1>>>();
runHeterogeneousInsertStringTest<F14FastMap<
std::string,
std::string,
transparent<hasher<StringPiece>>,
transparent<DefaultKeyEqual<StringPiece>>>>();
runHeterogeneousInsertStringTest<F14FastMap<std::string, std::string>>();
}
namespace {
// std::is_convertible is not transitive :( Problem scenario: B<T> is
// implicitly convertible to A, so hasher that takes A can be used as a
// transparent hasher for a map with key of type B<T>. C is implicitly
// convertible to any B<T>, but we have to disable heterogeneous find
// for C. There is no way to infer the T of the intermediate type so C
// can't be used to explicitly construct A.
struct A {
int value;
bool operator==(A const& rhs) const {
return value == rhs.value;
}
bool operator!=(A const& rhs) const {
return !(*this == rhs);
}
};
struct AHasher {
std::size_t operator()(A const& v) const {
return v.value;
}
};
template <typename T>
struct B {
int value;
explicit B(int v) : value(v) {}
/* implicit */ B(A const& v) : value(v.value) {}
/* implicit */ operator A() const {
return A{value};
}
};
struct C {
int value;
template <typename T>
/* implicit */ operator B<T>() const {
return B<T>{value};
}
};
} // namespace
TEST(F14FastMap, disabledDoubleTransparent) {
static_assert(std::is_convertible<B<char>, A>::value, "");
static_assert(std::is_convertible<C, B<char>>::value, "");
static_assert(!std::is_convertible<C, A>::value, "");
F14FastMap<
B<char>,
int,
folly::transparent<AHasher>,
folly::transparent<std::equal_to<A>>>
map;
map[A{10}] = 10;
EXPECT_TRUE(map.find(C{10}) != map.end());
EXPECT_TRUE(map.find(C{20}) == map.end());
}
template <typename M>
void runRandomInsertOrderTest() {
if (FOLLY_F14_PERTURB_INSERTION_ORDER) {
......
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