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

reusable key destructuring logic for implementing emplace

Summary:
This diff extracts the key destructuring logic in F14's emplace
functions into standalone helpers that can be used by other container
types (like sorted_vector_{set,map}).  It also adds better PMR compliance, by
using a stateful allocator to construct a temporary if that is required
to be able to perform the search.

This diff also cleans up some of the testing code in folly/container/test
that had issues if included in multiple places.

Reviewed By: yfeldblum

Differential Revision: D16655131

fbshipit-source-id: 5a6f57ac346d1fd5928e0f737110760b0c518974
parent 120ac01a
......@@ -136,13 +136,15 @@ list(REMOVE_ITEM hfiles
)
# Explicitly include utility library code from inside
# folly/test and folly/io/async/test/
# test subdirs
list(APPEND files
${FOLLY_DIR}/io/async/test/ScopedBoundPort.cpp
${FOLLY_DIR}/io/async/test/SocketPair.cpp
${FOLLY_DIR}/io/async/test/TimeUtil.cpp
)
list(APPEND hfiles
${FOLLY_DIR}/container/test/F14TestUtil.h
${FOLLY_DIR}/container/test/TrackingTypes.h
${FOLLY_DIR}/io/async/test/AsyncSSLSocketTest.h
${FOLLY_DIR}/io/async/test/AsyncSocketTest.h
${FOLLY_DIR}/io/async/test/AsyncSocketTest2.h
......@@ -436,11 +438,9 @@ if (BUILD_TESTS)
${FOLLY_DIR}/test/SingletonTestStructs.cpp
${FOLLY_DIR}/test/SocketAddressTestHelper.cpp
${FOLLY_DIR}/test/SocketAddressTestHelper.h
${FOLLY_DIR}/container/test/F14TestUtil.h
${FOLLY_DIR}/container/test/TrackingTypes.h
${FOLLY_DIR}/experimental/test/CodingTestUtils.cpp
${FOLLY_DIR}/logging/test/ConfigHelpers.cpp
${FOLLY_DIR}/logging/test/ConfigHelpers.h
${FOLLY_DIR}/logging/test/TestLogHandler.cpp
${FOLLY_DIR}/logging/test/TestLogHandler.h
${FOLLY_DIR}/futures/test/TestExecutor.cpp
${FOLLY_DIR}/futures/test/TestExecutor.h
${FOLLY_DIR}/io/async/test/BlockingSocket.h
......@@ -460,6 +460,10 @@ if (BUILD_TESTS)
${FOLLY_DIR}/io/async/test/TimeUtil.h
${FOLLY_DIR}/io/async/test/UndelayedDestruction.h
${FOLLY_DIR}/io/async/test/Util.h
${FOLLY_DIR}/logging/test/ConfigHelpers.cpp
${FOLLY_DIR}/logging/test/ConfigHelpers.h
${FOLLY_DIR}/logging/test/TestLogHandler.cpp
${FOLLY_DIR}/logging/test/TestLogHandler.h
)
target_compile_definitions(folly_test_support
PUBLIC
......@@ -502,6 +506,7 @@ if (BUILD_TESTS)
TEST foreach_test SOURCES ForeachTest.cpp
TEST merge_test SOURCES MergeTest.cpp
TEST sparse_byte_set_test SOURCES SparseByteSetTest.cpp
TEST util_test SOURCES UtilTest.cpp
DIRECTORY concurrency/test/
TEST atomic_shared_ptr_test SOURCES AtomicSharedPtrTest.cpp
......
......@@ -30,13 +30,13 @@
#include <stdexcept>
#include <folly/Traits.h>
#include <folly/functional/ApplyTuple.h>
#include <folly/lang/Exception.h>
#include <folly/lang/SafeAssert.h>
#include <folly/container/F14Map-fwd.h>
#include <folly/container/detail/F14Policy.h>
#include <folly/container/detail/F14Table.h>
#include <folly/container/detail/Util.h>
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
......@@ -404,73 +404,20 @@ class F14BasicMap {
}
private:
std::pair<ItemIter, bool> emplaceItem() {
// rare but valid
return table_.tryEmplaceValue(key_type{});
}
template <typename U1, typename U2>
std::pair<ItemIter, bool> emplaceItem(U1&& x, U2&& y) {
using K = KeyTypeForEmplace<key_type, hasher, key_equal, U1>;
K key(std::forward<U1>(x));
// TODO(T31574848): piecewise_construct is to work around libstdc++ versions
// (e.g., GCC < 6) with no implementation of N4387 ("perfect initialization"
// for pairs and tuples). Otherwise we could just pass key, forwarded key,
// and forwarded y to tryEmplaceValue.
return table_.tryEmplaceValue(
key,
std::piecewise_construct,
std::forward_as_tuple(std::forward<K>(key)),
std::forward_as_tuple(std::forward<U2>(y)));
}
template <typename U1, typename U2>
std::pair<ItemIter, bool> emplaceItem(std::pair<U1, U2> const& p) {
return emplaceItem(p.first, p.second);
}
template <typename U1, typename U2>
std::pair<ItemIter, bool> emplaceItem(std::pair<U1, U2>&& p) {
return emplaceItem(std::move(p.first), std::move(p.second));
}
template <typename Arg1, typename... Args2>
std::pair<ItemIter, bool> emplaceItem(
std::piecewise_construct_t,
std::tuple<Arg1>&& first_args,
std::tuple<Args2...>&& second_args) {
using K = KeyTypeForEmplace<key_type, hasher, key_equal, Arg1>;
K key(std::get<0>(std::move(first_args)));
// Args2&&... holds only references even if the caller gave us a
// tuple that directly contains values.
return table_.tryEmplaceValue(
key,
std::piecewise_construct,
std::forward_as_tuple(std::forward<K>(key)),
std::tuple<Args2&&...>(std::move(second_args)));
}
template <typename... Args1, typename... Args2>
std::enable_if_t<sizeof...(Args1) != 1, std::pair<ItemIter, bool>>
emplaceItem(
std::piecewise_construct_t,
std::tuple<Args1...>&& first_args,
std::tuple<Args2...>&& second_args) {
auto key = make_from_tuple<key_type>(
std::tuple<Args1&&...>(std::move(first_args)));
return table_.tryEmplaceValue(
key,
std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
std::tuple<Args2&&...>(std::move(second_args)));
}
template <typename Arg>
using UsableAsKey =
EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
public:
template <typename... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
auto rv = emplaceItem(std::forward<Args>(args)...);
auto rv = folly::detail::callWithExtractedKey<key_type, UsableAsKey>(
table_.alloc(),
[&](auto&&... inner) {
return table_.tryEmplaceValue(
std::forward<decltype(inner)>(inner)...);
},
std::forward<Args>(args)...);
return std::make_pair(table_.makeIter(rv.first), rv.second);
}
......
......@@ -34,6 +34,7 @@
#include <folly/container/F14Set-fwd.h>
#include <folly/container/detail/F14Policy.h>
#include <folly/container/detail/F14Table.h>
#include <folly/container/detail/Util.h>
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
......@@ -323,18 +324,21 @@ class F14BasicSet {
insert(ilist.begin(), ilist.end());
}
private:
template <typename Arg>
using UsableAsKey =
EligibleForHeterogeneousFind<key_type, hasher, key_equal, Arg>;
public:
template <class... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
using K = KeyTypeForEmplace<key_type, hasher, key_equal, Args...>;
// 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)...);
auto rv = table_.tryEmplaceValue(key, std::forward<K>(key));
auto rv = folly::detail::callWithConstructedKey<key_type, UsableAsKey>(
table_.alloc(),
[&](auto&&... inner) {
return table_.tryEmplaceValue(
std::forward<decltype(inner)>(inner)...);
},
std::forward<Args>(args)...);
return std::make_pair(table_.makeIter(rv.first), rv.second);
}
......
......@@ -39,7 +39,6 @@
#include <folly/Portability.h>
#include <folly/ScopeGuard.h>
#include <folly/Traits.h>
#include <folly/functional/ApplyTuple.h>
#include <folly/functional/Invoke.h>
#include <folly/lang/Align.h>
#include <folly/lang/Assume.h>
......@@ -260,35 +259,6 @@ 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<remove_cvref_t<KeyArg0OrBool>, TableKey>::value ||
EligibleForHeterogeneousFind<
TableKey,
Hasher,
KeyEqual,
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...>;
////////////////
template <typename T>
......
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include <folly/Traits.h>
#include <folly/functional/ApplyTuple.h>
// Utility functions for container implementors
namespace folly {
namespace detail {
template <typename KeyType, typename Alloc>
struct TemporaryEmplaceKey {
TemporaryEmplaceKey(TemporaryEmplaceKey const&) = delete;
TemporaryEmplaceKey(TemporaryEmplaceKey&&) = delete;
template <typename... Args>
TemporaryEmplaceKey(Alloc& a, std::tuple<Args...>&& args) : alloc_(a) {
auto p = &value();
apply(
[&, p](auto&&... inner) {
std::allocator_traits<Alloc>::construct(
alloc_, p, std::forward<decltype(inner)>(inner)...);
},
std::move(args));
}
~TemporaryEmplaceKey() {
std::allocator_traits<Alloc>::destroy(alloc_, &value());
}
KeyType& value() {
return *static_cast<KeyType*>(static_cast<void*>(&raw_));
}
Alloc& alloc_;
std::aligned_storage_t<sizeof(KeyType), alignof(KeyType)> raw_;
};
// A map's emplace(args...) function takes arguments that can be used to
// construct a pair<key_type const, mapped_type>, but that construction
// only needs to take place if the key is not present in the container.
// callWithExtractedKey helps to handle this efficiently by looking for a
// reference to the key within the args list. If the search is successful
// then the search can be performed without constructing any temporaries.
// If the search is not successful then callWithExtractedKey constructs
// a temporary key_type and a new argument list suitable for constructing
// the entire value_type if necessary.
//
// callWithExtractedKey(a, f, args...) will call f(k, args'...), where
// k is the key and args'... is an argument list that can be used to
// construct a pair of key and mapped value. Note that this means f gets
// the key twice.
//
// In some cases a temporary key must be constructed. This is accomplished
// with std::allocator_traits<>::construct, and the temporary will be
// destroyed with std::allocator_traits<>::destroy. Using the allocator's
// construct method reduces unnecessary copies for pmr allocators.
//
// callWithExtractedKey supports heterogeneous lookup with the UsableAsKey
// template parameter. If a single key argument of type K is found in
// args... then it will be passed directly to f if it is either KeyType or
// if UsableAsKey<remove_cvref_t<K>>::value is true. If you don't care
// about heterogeneous lookup you can just pass a single-arg template
// that extends std::false_type.
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func,
typename Arg1,
typename... Args2,
std::enable_if_t<
std::is_same<remove_cvref_t<Arg1>, KeyType>::value ||
UsableAsKey<remove_cvref_t<Arg1>>::value,
int> = 0>
auto callWithExtractedKey(
Alloc&,
Func&& f,
std::piecewise_construct_t,
std::tuple<Arg1>&& first_args,
std::tuple<Args2...>&& second_args) {
// we found a usable key in the args :)
auto const& key = std::get<0>(first_args);
return f(
key,
std::piecewise_construct,
std::tuple<Arg1&&>(std::move(first_args)),
std::tuple<Args2&&...>(std::move(second_args)));
}
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func,
typename... Args1,
typename... Args2>
auto callWithExtractedKey(
Alloc& a,
Func&& f,
std::piecewise_construct_t,
std::tuple<Args1...>&& first_args,
std::tuple<Args2...>&& second_args) {
// we will need to materialize a temporary key :(
TemporaryEmplaceKey<KeyType, Alloc> key(
a, std::tuple<Args1&&...>(std::move(first_args)));
return f(
const_cast<KeyType const&>(key.value()),
std::piecewise_construct,
std::forward_as_tuple(std::move(key.value())),
std::tuple<Args2&&...>(std::move(second_args)));
}
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func>
auto callWithExtractedKey(Alloc& a, Func&& f) {
return callWithExtractedKey<KeyType, UsableAsKey>(
a,
std::forward<Func>(f),
std::piecewise_construct,
std::tuple<>{},
std::tuple<>{});
}
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func,
typename U1,
typename U2>
auto callWithExtractedKey(Alloc& a, Func&& f, U1&& x, U2&& y) {
return callWithExtractedKey<KeyType, UsableAsKey>(
a,
std::forward<Func>(f),
std::piecewise_construct,
std::forward_as_tuple(std::forward<U1>(x)),
std::forward_as_tuple(std::forward<U2>(y)));
}
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func,
typename U1,
typename U2>
auto callWithExtractedKey(Alloc& a, Func&& f, std::pair<U1, U2> const& p) {
return callWithExtractedKey<KeyType, UsableAsKey>(
a,
std::forward<Func>(f),
std::piecewise_construct,
std::forward_as_tuple(p.first),
std::forward_as_tuple(p.second));
}
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func,
typename U1,
typename U2>
auto callWithExtractedKey(Alloc& a, Func&& f, std::pair<U1, U2>&& p) {
return callWithExtractedKey<KeyType, UsableAsKey>(
a,
std::forward<Func>(f),
std::piecewise_construct,
std::forward_as_tuple(std::move(p.first)),
std::forward_as_tuple(std::move(p.second)));
}
// callWithConstructedKey is the set container analogue of
// callWithExtractedKey
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func,
typename Arg,
std::enable_if_t<
std::is_same<remove_cvref_t<Arg>, KeyType>::value ||
UsableAsKey<remove_cvref_t<Arg>>::value,
int> = 0>
auto callWithConstructedKey(Alloc&, Func&& f, Arg&& arg) {
// we found a usable key in the args :)
auto const& key = arg;
return f(key, std::forward<Arg>(arg));
}
template <
typename KeyType,
template <typename> class UsableAsKey,
typename Alloc,
typename Func,
typename... Args>
auto callWithConstructedKey(Alloc& a, Func&& f, Args&&... args) {
// we will need to materialize a temporary key :(
TemporaryEmplaceKey<KeyType, Alloc> key(
a, std::forward_as_tuple(std::forward<Args>(args)...));
return f(const_cast<KeyType const&>(key.value()), std::move(key.value()));
}
} // namespace detail
} // namespace folly
......@@ -29,7 +29,6 @@
#include <folly/Traits.h>
#include <folly/container/F14Map.h>
#include <folly/container/F14Set.h>
#include <folly/container/test/F14TestUtil.h>
#include <folly/portability/GTest.h>
using namespace boost::interprocess;
......
......@@ -24,6 +24,7 @@
#include <folly/Conv.h>
#include <folly/FBString.h>
#include <folly/container/test/F14TestUtil.h>
#include <folly/container/test/TrackingTypes.h>
#include <folly/portability/GTest.h>
template <template <typename, typename, typename, typename, typename>
......@@ -36,13 +37,14 @@ void testCustomSwap() {
int,
folly::f14::DefaultHasher<int>,
folly::f14::DefaultKeyEqual<int>,
folly::f14::SwapTrackingAlloc<std::pair<int const, int>>>
folly::test::SwapTrackingAlloc<std::pair<int const, int>>>
m0, m1;
folly::f14::resetTracking();
folly::test::resetTracking();
swap(m0, m1);
EXPECT_EQ(
0, folly::f14::Tracked<0>::counts.dist(folly::f14::Counts{0, 0, 0, 0}));
0,
folly::test::Tracked<0>::counts().dist(folly::test::Counts{0, 0, 0, 0}));
}
TEST(F14Map, customSwap) {
......@@ -59,6 +61,7 @@ template <
void runAllocatedMemorySizeTest() {
using namespace folly::f14;
using namespace folly::f14::detail;
using namespace folly::test;
using A = SwapTrackingAlloc<std::pair<const K, V>>;
resetTracking();
......@@ -71,17 +74,17 @@ void runAllocatedMemorySizeTest() {
bool preciseAllocInfo = getF14IntrinsicsMode() != F14IntrinsicsMode::None;
if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(testAllocatedMemorySize(), 0);
EXPECT_EQ(m.getAllocatedMemorySize(), 0);
}
auto emptyMapAllocatedMemorySize = testAllocatedMemorySize;
auto emptyMapAllocatedBlockCount = testAllocatedBlockCount;
auto emptyMapAllocatedMemorySize = testAllocatedMemorySize();
auto emptyMapAllocatedBlockCount = testAllocatedBlockCount();
for (size_t i = 0; i < 1000; ++i) {
m.insert(std::make_pair(folly::to<K>(i), V{}));
m.erase(folly::to<K>(i / 10 + 2));
if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, m.getAllocatedMemorySize());
EXPECT_EQ(testAllocatedMemorySize(), m.getAllocatedMemorySize());
}
EXPECT_GE(m.getAllocatedMemorySize(), sizeof(std::pair<K, V>) * m.size());
std::size_t size = 0;
......@@ -92,22 +95,22 @@ void runAllocatedMemorySizeTest() {
count += n;
});
if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, size);
EXPECT_EQ(testAllocatedBlockCount, count);
EXPECT_EQ(testAllocatedMemorySize(), size);
EXPECT_EQ(testAllocatedBlockCount(), count);
}
}
m = decltype(m){};
EXPECT_EQ(testAllocatedMemorySize, emptyMapAllocatedMemorySize);
EXPECT_EQ(testAllocatedBlockCount, emptyMapAllocatedBlockCount);
EXPECT_EQ(testAllocatedMemorySize(), emptyMapAllocatedMemorySize);
EXPECT_EQ(testAllocatedBlockCount(), emptyMapAllocatedBlockCount);
m.reserve(5);
EXPECT_GT(testAllocatedMemorySize, 0);
EXPECT_GT(testAllocatedMemorySize(), 0);
m = {};
EXPECT_GT(testAllocatedMemorySize, 0);
EXPECT_GT(testAllocatedMemorySize(), 0);
}
EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(testAllocatedBlockCount, 0);
EXPECT_EQ(testAllocatedMemorySize(), 0);
EXPECT_EQ(testAllocatedBlockCount(), 0);
}
template <typename K, typename V>
......@@ -242,9 +245,17 @@ void testNestedMapEquality() {
template <template <class...> class TMap>
void testEqualityRefinement() {
TMap<std::pair<int, int>, int, folly::f14::HashFirst, folly::f14::EqualFirst>
TMap<
std::pair<int, int>,
int,
folly::test::HashFirst,
folly::test::EqualFirst>
m1;
TMap<std::pair<int, int>, int, folly::f14::HashFirst, folly::f14::EqualFirst>
TMap<
std::pair<int, int>,
int,
folly::test::HashFirst,
folly::test::EqualFirst>
m2;
m1[std::make_pair(0, 0)] = 0;
m1[std::make_pair(1, 1)] = 1;
......@@ -293,6 +304,7 @@ 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) {
......@@ -513,9 +525,9 @@ void runRandom() {
try {
EXPECT_EQ(t0.empty(), r0.empty());
EXPECT_EQ(t0.size(), r0.size());
EXPECT_EQ(2, Tracked<0>::counts.liveCount());
EXPECT_EQ(t0.size() + t1.size(), Tracked<1>::counts.liveCount());
EXPECT_EQ(r0.size() + r1.size(), Tracked<2>::counts.liveCount());
EXPECT_EQ(2, Tracked<0>::counts().liveCount());
EXPECT_EQ(t0.size() + t1.size(), Tracked<1>::counts().liveCount());
EXPECT_EQ(r0.size() + r1.size(), Tracked<2>::counts().liveCount());
if (pct < 15) {
// insert
auto t = t0.insert(std::make_pair(k, v));
......@@ -735,7 +747,7 @@ void runRandom() {
}
}
EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(testAllocatedMemorySize(), 0);
}
template <typename T>
......@@ -968,61 +980,61 @@ TEST(Tracked, baseline) {
resetTracking();
Tracked<0> b0{a0};
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts, (Counts{1, 0, 0, 0}));
EXPECT_EQ(Tracked<0>::counts, (Counts{1, 0, 0, 0}));
EXPECT_EQ(sumCounts(), (Counts{1, 0, 0, 0}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{1, 0, 0, 0}));
}
{
resetTracking();
Tracked<0> b0{std::move(a0)};
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts, (Counts{0, 1, 0, 0}));
EXPECT_EQ(Tracked<0>::counts, (Counts{0, 1, 0, 0}));
EXPECT_EQ(sumCounts(), (Counts{0, 1, 0, 0}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{0, 1, 0, 0}));
}
{
resetTracking();
Tracked<1> b1{a0};
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts, (Counts{0, 0, 1, 0}));
EXPECT_EQ(Tracked<1>::counts, (Counts{0, 0, 1, 0}));
EXPECT_EQ(sumCounts(), (Counts{0, 0, 1, 0}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 1, 0}));
}
{
resetTracking();
Tracked<1> b1{std::move(a0)};
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts, (Counts{0, 0, 0, 1}));
EXPECT_EQ(Tracked<1>::counts, (Counts{0, 0, 0, 1}));
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 1}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 0, 1}));
}
{
Tracked<0> b0;
resetTracking();
b0 = a0;
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts, (Counts{0, 0, 0, 0, 1, 0}));
EXPECT_EQ(Tracked<0>::counts, (Counts{0, 0, 0, 0, 1, 0}));
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 0, 1, 0}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{0, 0, 0, 0, 1, 0}));
}
{
Tracked<0> b0;
resetTracking();
b0 = std::move(a0);
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts, (Counts{0, 0, 0, 0, 0, 1}));
EXPECT_EQ(Tracked<0>::counts, (Counts{0, 0, 0, 0, 0, 1}));
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 0, 0, 1}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{0, 0, 0, 0, 0, 1}));
}
{
Tracked<1> b1;
resetTracking();
b1 = a0;
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts, (Counts{0, 0, 1, 0, 0, 1, 0, 1}));
EXPECT_EQ(Tracked<1>::counts, (Counts{0, 0, 1, 0, 0, 1, 0, 1}));
EXPECT_EQ(sumCounts(), (Counts{0, 0, 1, 0, 0, 1, 0, 1}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 1, 0, 0, 1, 0, 1}));
}
{
Tracked<1> b1;
resetTracking();
b1 = std::move(a0);
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts, (Counts{0, 0, 0, 1, 0, 1, 0, 1}));
EXPECT_EQ(Tracked<1>::counts, (Counts{0, 0, 0, 1, 0, 1, 0, 1}));
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 1, 0, 1, 0, 1}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 0, 1, 0, 1, 0, 1}));
}
}
......@@ -1043,8 +1055,8 @@ void runInsertCases(
// fresh key, value_type const& ->
// copy is expected
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{1, 0, 0, 0}),
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;
......@@ -1057,8 +1069,8 @@ void runInsertCases(
// fresh key, value_type&& ->
// key copy is unfortunate but required
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 1, 0, 0}),
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;
......@@ -1071,8 +1083,8 @@ void runInsertCases(
// fresh key, pair<key_type,mapped_type> const& ->
// 1 copy is required
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{1, 0, 0, 0}),
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;
......@@ -1085,8 +1097,8 @@ void runInsertCases(
// fresh key, pair<key_type,mapped_type>&& ->
// this is the happy path for insert(make_pair(.., ..))
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 1, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 1, 0, 0}),
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;
......@@ -1120,10 +1132,10 @@ void runInsertCases(
// test, whose test harness copies the original pair and then uses
// move conversion instead of copy conversion.
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 1, 1, 0}) +
Tracked<1>::counts.dist(Counts{0, 0, 1, 0}) +
Tracked<2>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 1, 1, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 1, 0}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist * 3);
}
{
......@@ -1137,10 +1149,10 @@ void runInsertCases(
// key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts;
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 1, 0, 1}) +
Tracked<1>::counts.dist(Counts{0, 0, 0, 1}) +
Tracked<2>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 1, 0, 1}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 1}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
......@@ -1151,8 +1163,8 @@ void runInsertCases(
insertFunc(m, p);
// duplicate key, value_type const&
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
......@@ -1163,8 +1175,8 @@ void runInsertCases(
insertFunc(m, std::move(p));
// duplicate key, value_type&&
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
......@@ -1175,8 +1187,8 @@ void runInsertCases(
insertFunc(m, p);
// duplicate key, pair<key_type,mapped_type> const&
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
......@@ -1187,8 +1199,8 @@ void runInsertCases(
insertFunc(m, std::move(p));
// duplicate key, pair<key_type,mapped_type>&&
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
......@@ -1203,10 +1215,10 @@ void runInsertCases(
// key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts;
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 0, 1, 0}) +
Tracked<1>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<2>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 0, 1, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist * 2);
}
{
......@@ -1221,10 +1233,10 @@ void runInsertCases(
// key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts;
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 0, 0, 1}) +
Tracked<1>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<2>::counts.dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts.dist(Counts{0, 0, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 0, 0, 1}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
}
......@@ -1344,8 +1356,8 @@ TEST(F14VectorMap, destructuringErase) {
<< Tracked<1>::counts;
// deleting p1 will cause p2 to be moved to the front of the values array
EXPECT_EQ(
Tracked<0>::counts.dist(Counts{0, 1, 0, 0}) +
Tracked<1>::counts.dist(Counts{0, 1, 0, 0}),
Tracked<0>::counts().dist(Counts{0, 1, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 1, 0, 0}),
0);
}
......@@ -1388,27 +1400,33 @@ void runMoveOnlyTest() {
}
TEST(F14ValueMap, moveOnly) {
runMoveOnlyTest<F14ValueMap<f14::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14ValueMap<int, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueMap<f14::MoveOnlyTestInt, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14ValueMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueMap<
folly::test::MoveOnlyTestInt,
folly::test::MoveOnlyTestInt>>();
}
TEST(F14NodeMap, moveOnly) {
runMoveOnlyTest<F14NodeMap<f14::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14NodeMap<int, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14NodeMap<f14::MoveOnlyTestInt, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14NodeMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14NodeMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<
F14NodeMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
}
TEST(F14VectorMap, moveOnly) {
runMoveOnlyTest<F14VectorMap<f14::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14VectorMap<int, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorMap<f14::MoveOnlyTestInt, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14VectorMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorMap<
folly::test::MoveOnlyTestInt,
folly::test::MoveOnlyTestInt>>();
}
TEST(F14FastMap, moveOnly) {
runMoveOnlyTest<F14FastMap<f14::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14FastMap<int, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14FastMap<f14::MoveOnlyTestInt, f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14FastMap<folly::test::MoveOnlyTestInt, int>>();
runMoveOnlyTest<F14FastMap<int, folly::test::MoveOnlyTestInt>>();
runMoveOnlyTest<
F14FastMap<folly::test::MoveOnlyTestInt, folly::test::MoveOnlyTestInt>>();
}
TEST(F14ValueMap, heterogeneousLookup) {
......@@ -1543,12 +1561,12 @@ void runHeterogeneousInsertTest() {
resetTracking();
EXPECT_EQ(map.count(10), 0);
EXPECT_FALSE(map.contains(10));
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 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)
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 1}), 0)
<< Tracked<1>::counts;
resetTracking();
......@@ -1562,7 +1580,7 @@ void runHeterogeneousInsertTest() {
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)
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
resetTracking();
......@@ -1574,13 +1592,13 @@ void runHeterogeneousInsertTest() {
map.emplace(p);
map.try_emplace(10, 30);
map.try_emplace(10);
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 0)
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)
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
}
......
......@@ -23,6 +23,7 @@
#include <folly/Conv.h>
#include <folly/FBString.h>
#include <folly/container/test/F14TestUtil.h>
#include <folly/container/test/TrackingTypes.h>
#include <folly/portability/GTest.h>
template <template <typename, typename, typename, typename> class TSet>
......@@ -33,13 +34,14 @@ void testCustomSwap() {
int,
folly::f14::DefaultHasher<int>,
folly::f14::DefaultKeyEqual<int>,
folly::f14::SwapTrackingAlloc<int>>
folly::test::SwapTrackingAlloc<int>>
m0, m1;
folly::f14::resetTracking();
folly::test::resetTracking();
swap(m0, m1);
EXPECT_EQ(
0, folly::f14::Tracked<0>::counts.dist(folly::f14::Counts{0, 0, 0, 0}));
0,
folly::test::Tracked<0>::counts().dist(folly::test::Counts{0, 0, 0, 0}));
}
TEST(F14Set, customSwap) {
......@@ -56,6 +58,7 @@ template <
void runAllocatedMemorySizeTest() {
using namespace folly::f14;
using namespace folly::f14::detail;
using namespace folly::test;
using A = SwapTrackingAlloc<K>;
resetTracking();
......@@ -68,17 +71,17 @@ void runAllocatedMemorySizeTest() {
bool preciseAllocInfo = getF14IntrinsicsMode() != F14IntrinsicsMode::None;
if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(testAllocatedMemorySize(), 0);
EXPECT_EQ(s.getAllocatedMemorySize(), 0);
}
auto emptySetAllocatedMemorySize = testAllocatedMemorySize;
auto emptySetAllocatedBlockCount = testAllocatedBlockCount;
auto emptySetAllocatedMemorySize = testAllocatedMemorySize();
auto emptySetAllocatedBlockCount = testAllocatedBlockCount();
for (size_t i = 0; i < 1000; ++i) {
s.insert(folly::to<K>(i));
s.erase(folly::to<K>(i / 10 + 2));
if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, s.getAllocatedMemorySize());
EXPECT_EQ(testAllocatedMemorySize(), s.getAllocatedMemorySize());
}
EXPECT_GE(s.getAllocatedMemorySize(), sizeof(K) * s.size());
std::size_t size = 0;
......@@ -89,22 +92,22 @@ void runAllocatedMemorySizeTest() {
count += n;
});
if (preciseAllocInfo) {
EXPECT_EQ(testAllocatedMemorySize, size);
EXPECT_EQ(testAllocatedBlockCount, count);
EXPECT_EQ(testAllocatedMemorySize(), size);
EXPECT_EQ(testAllocatedBlockCount(), count);
}
}
s = decltype(s){};
EXPECT_EQ(testAllocatedMemorySize, emptySetAllocatedMemorySize);
EXPECT_EQ(testAllocatedBlockCount, emptySetAllocatedBlockCount);
EXPECT_EQ(testAllocatedMemorySize(), emptySetAllocatedMemorySize);
EXPECT_EQ(testAllocatedBlockCount(), emptySetAllocatedBlockCount);
s.reserve(5);
EXPECT_GT(testAllocatedMemorySize, 0);
EXPECT_GT(testAllocatedMemorySize(), 0);
s = {};
EXPECT_GT(testAllocatedMemorySize, 0);
EXPECT_GT(testAllocatedMemorySize(), 0);
}
EXPECT_EQ(testAllocatedMemorySize, 0);
EXPECT_EQ(testAllocatedBlockCount, 0);
EXPECT_EQ(testAllocatedMemorySize(), 0);
EXPECT_EQ(testAllocatedBlockCount(), 0);
}
template <typename K>
......@@ -239,8 +242,8 @@ void testNestedSetEquality() {
template <template <class...> class TSet>
void testEqualityRefinement() {
TSet<std::pair<int, int>, folly::f14::HashFirst, folly::f14::EqualFirst> s1;
TSet<std::pair<int, int>, folly::f14::HashFirst, folly::f14::EqualFirst> s2;
TSet<std::pair<int, int>, folly::test::HashFirst, folly::test::EqualFirst> s1;
TSet<std::pair<int, int>, folly::test::HashFirst, folly::test::EqualFirst> s2;
s1.insert(std::make_pair(0, 0));
s1.insert(std::make_pair(1, 1));
EXPECT_FALSE(s1.insert(std::make_pair(0, 2)).second);
......@@ -286,6 +289,7 @@ TEST(F14Set, 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) {
......@@ -839,7 +843,7 @@ void runInsertCases(std::string const& /* name */, F const& insertFunc) {
insertFunc(s, k);
// fresh key, value_type const& ->
// copy is expected
EXPECT_EQ(Tracked<0>::counts.dist(Counts{1, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{1, 0, 0, 0}), 0);
}
{
typename S::value_type k{0};
......@@ -848,7 +852,7 @@ void runInsertCases(std::string const& /* name */, F const& insertFunc) {
insertFunc(s, std::move(k));
// fresh key, value_type&& ->
// move is expected
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 1, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 0}), 0);
}
}
......@@ -875,12 +879,12 @@ void runInsertAndEmplace() {
resetTracking();
EXPECT_TRUE(s.insert(k1).second);
// copy is expected on successful insert
EXPECT_EQ(Tracked<0>::counts.dist(Counts{1, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{1, 0, 0, 0}), 0);
resetTracking();
EXPECT_FALSE(s.insert(k2).second);
// no copies or moves on failing insert
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
}
{
typename S::value_type k1{0};
......@@ -889,12 +893,12 @@ void runInsertAndEmplace() {
resetTracking();
EXPECT_TRUE(s.insert(std::move(k1)).second);
// move is expected on successful insert
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 1, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 0}), 0);
resetTracking();
EXPECT_FALSE(s.insert(std::move(k2)).second);
// no copies or moves on failing insert
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
}
{
typename S::value_type k1{0};
......@@ -904,23 +908,23 @@ void runInsertAndEmplace() {
resetTracking();
EXPECT_TRUE(s.emplace(k1).second);
// copy is expected on successful emplace
EXPECT_EQ(Tracked<0>::counts.dist(Counts{1, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{1, 0, 0, 0}), 0);
resetTracking();
EXPECT_FALSE(s.emplace(k2).second);
// no copies or moves on failing emplace with value_type
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
resetTracking();
EXPECT_FALSE(s.emplace(k3).second);
// copy convert expected for failing emplace with wrong type
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 0, 1, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 1, 0}), 0);
s.clear();
resetTracking();
EXPECT_TRUE(s.emplace(k3).second);
// copy convert + move expected for successful emplace with wrong type
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 1, 1, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 1, 0}), 0);
}
{
typename S::value_type k1{0};
......@@ -930,23 +934,23 @@ void runInsertAndEmplace() {
resetTracking();
EXPECT_TRUE(s.emplace(std::move(k1)).second);
// move is expected on successful emplace
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 1, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 0}), 0);
resetTracking();
EXPECT_FALSE(s.emplace(std::move(k2)).second);
// no copies or moves on failing emplace with value_type
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 0, 0, 0}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
resetTracking();
EXPECT_FALSE(s.emplace(std::move(k3)).second);
// move convert expected for failing emplace with wrong type
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 0, 0, 1}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 1}), 0);
s.clear();
resetTracking();
EXPECT_TRUE(s.emplace(std::move(k3)).second);
// move convert + move expected for successful emplace with wrong type
EXPECT_EQ(Tracked<0>::counts.dist(Counts{0, 1, 0, 1}), 0);
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 1}), 0);
}
// Calling the default pair constructor via emplace is valid, but not
......@@ -1010,19 +1014,19 @@ void runMoveOnlyTest() {
}
TEST(F14ValueSet, moveOnly) {
runMoveOnlyTest<F14ValueSet<f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14ValueSet<folly::test::MoveOnlyTestInt>>();
}
TEST(F14NodeSet, moveOnly) {
runMoveOnlyTest<F14NodeSet<f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14NodeSet<folly::test::MoveOnlyTestInt>>();
}
TEST(F14VectorSet, moveOnly) {
runMoveOnlyTest<F14VectorSet<f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14VectorSet<folly::test::MoveOnlyTestInt>>();
}
TEST(F14FastSet, moveOnly) {
runMoveOnlyTest<F14FastSet<f14::MoveOnlyTestInt>>();
runMoveOnlyTest<F14FastSet<folly::test::MoveOnlyTestInt>>();
}
template <typename S>
......@@ -1069,19 +1073,19 @@ void runEraseIntoTest() {
}
TEST(F14ValueSet, eraseInto) {
runEraseIntoTest<F14ValueSet<f14::MoveOnlyTestInt>>();
runEraseIntoTest<F14ValueSet<folly::test::MoveOnlyTestInt>>();
}
TEST(F14NodeSet, eraseInto) {
runEraseIntoTest<F14NodeSet<f14::MoveOnlyTestInt>>();
runEraseIntoTest<F14NodeSet<folly::test::MoveOnlyTestInt>>();
}
TEST(F14VectorSet, eraseInto) {
runEraseIntoTest<F14VectorSet<f14::MoveOnlyTestInt>>();
runEraseIntoTest<F14VectorSet<folly::test::MoveOnlyTestInt>>();
}
TEST(F14FastSet, eraseInto) {
runEraseIntoTest<F14FastSet<f14::MoveOnlyTestInt>>();
runEraseIntoTest<F14FastSet<folly::test::MoveOnlyTestInt>>();
}
TEST(F14ValueSet, heterogeneous) {
......@@ -1213,12 +1217,12 @@ void runHeterogeneousInsertTest() {
resetTracking();
EXPECT_EQ(set.count(10), 0);
EXPECT_FALSE(set.contains(10));
EXPECT_EQ(Tracked<1>::counts.dist(Counts{0, 0, 0, 0}), 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)
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 1}), 0)
<< Tracked<1>::counts;
resetTracking();
......@@ -1231,25 +1235,25 @@ void runHeterogeneousInsertTest() {
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)
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)
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)
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)
EXPECT_EQ(Tracked<1>::counts().dist(Counts{0, 0, 0, 0}), 0)
<< Tracked<1>::counts;
}
......
......@@ -17,13 +17,9 @@
#pragma once
#include <cstddef>
#include <cstring>
#include <limits>
#include <memory>
#include <ostream>
#include <vector>
#include <folly/Function.h>
#include <folly/container/detail/F14Policy.h>
#include <folly/container/detail/F14Table.h>
......@@ -34,7 +30,7 @@ struct Histo {
std::vector<std::size_t> const& data;
};
std::ostream& operator<<(std::ostream& xo, Histo const& histo) {
inline std::ostream& operator<<(std::ostream& xo, Histo const& histo) {
xo << "[";
size_t sum = 0;
for (auto v : histo.data) {
......@@ -55,18 +51,7 @@ std::ostream& operator<<(std::ostream& xo, Histo const& histo) {
return xo;
}
void accumulate(
std::vector<std::size_t>& a,
std::vector<std::size_t> const& d) {
if (a.size() < d.size()) {
a.resize(d.size());
}
for (std::size_t i = 0; i < d.size(); ++i) {
a[i] += d[i];
}
}
double expectedProbe(std::vector<std::size_t> const& probeLengths) {
inline double expectedProbe(std::vector<std::size_t> const& probeLengths) {
std::size_t sum = 0;
std::size_t count = 0;
for (std::size_t i = 1; i < probeLengths.size(); ++i) {
......@@ -78,7 +63,7 @@ double expectedProbe(std::vector<std::size_t> const& probeLengths) {
// Returns i such that probeLengths elements 0 to i (inclusive) account
// for at least 99% of the samples.
std::size_t p99Probe(std::vector<std::size_t> const& probeLengths) {
inline std::size_t p99Probe(std::vector<std::size_t> const& probeLengths) {
std::size_t count = 0;
for (std::size_t i = 1; i < probeLengths.size(); ++i) {
count += probeLengths[i];
......@@ -91,380 +76,7 @@ std::size_t p99Probe(std::vector<std::size_t> const& probeLengths) {
return rv;
}
struct MoveOnlyTestInt {
int x;
bool destroyed{false};
MoveOnlyTestInt() noexcept : x(0) {}
/* implicit */ MoveOnlyTestInt(int x0) : x(x0) {}
MoveOnlyTestInt(MoveOnlyTestInt&& rhs) noexcept : x(rhs.x) {}
MoveOnlyTestInt(MoveOnlyTestInt const&) = delete;
MoveOnlyTestInt& operator=(MoveOnlyTestInt&& rhs) noexcept {
x = rhs.x;
destroyed = rhs.destroyed;
return *this;
}
MoveOnlyTestInt& operator=(MoveOnlyTestInt const&) = delete;
~MoveOnlyTestInt() {
destroyed = true;
}
bool operator==(MoveOnlyTestInt const& rhs) const {
return x == rhs.x && destroyed == rhs.destroyed;
}
bool operator!=(MoveOnlyTestInt const& rhs) const {
return !(*this == rhs);
}
};
struct ThrowOnCopyTestInt {
int x{0};
ThrowOnCopyTestInt() {}
[[noreturn]] ThrowOnCopyTestInt(const ThrowOnCopyTestInt& other)
: x(other.x) {
throw std::exception{};
}
ThrowOnCopyTestInt& operator=(const ThrowOnCopyTestInt&) {
throw std::exception{};
}
bool operator==(const ThrowOnCopyTestInt& other) const {
return x == other.x;
}
bool operator!=(const ThrowOnCopyTestInt& other) const {
return !(x == other.x);
}
};
// Tracked is implicitly constructible across tags
struct Counts {
uint64_t copyConstruct{0};
uint64_t moveConstruct{0};
uint64_t copyConvert{0};
uint64_t moveConvert{0};
uint64_t copyAssign{0};
uint64_t moveAssign{0};
uint64_t defaultConstruct{0};
uint64_t destroyed{0};
explicit Counts(
uint64_t copConstr = 0,
uint64_t movConstr = 0,
uint64_t copConv = 0,
uint64_t movConv = 0,
uint64_t copAssign = 0,
uint64_t movAssign = 0,
uint64_t def = 0,
uint64_t destr = 0)
: copyConstruct{copConstr},
moveConstruct{movConstr},
copyConvert{copConv},
moveConvert{movConv},
copyAssign{copAssign},
moveAssign{movAssign},
defaultConstruct{def},
destroyed{destr} {}
int64_t liveCount() const {
return copyConstruct + moveConstruct + copyConvert + moveConvert +
defaultConstruct - destroyed;
}
// dist ignores destroyed count
uint64_t dist(Counts const& rhs) const {
auto d = [](uint64_t x, uint64_t y) { return (x - y) * (x - y); };
return d(copyConstruct, rhs.copyConstruct) +
d(moveConstruct, rhs.moveConstruct) + d(copyConvert, rhs.copyConvert) +
d(moveConvert, rhs.moveConvert) + d(copyAssign, rhs.copyAssign) +
d(moveAssign, rhs.moveAssign) +
d(defaultConstruct, rhs.defaultConstruct);
}
bool operator==(Counts const& rhs) const {
return dist(rhs) == 0 && destroyed == rhs.destroyed;
}
bool operator!=(Counts const& rhs) const {
return !(*this == rhs);
}
};
std::ostream& operator<<(std::ostream& xo, Counts const& counts) {
xo << "[";
std::string glue = "";
if (counts.copyConstruct > 0) {
xo << glue << counts.copyConstruct << " copy";
glue = ", ";
}
if (counts.moveConstruct > 0) {
xo << glue << counts.moveConstruct << " move";
glue = ", ";
}
if (counts.copyConvert > 0) {
xo << glue << counts.copyConvert << " copy convert";
glue = ", ";
}
if (counts.moveConvert > 0) {
xo << glue << counts.moveConvert << " move convert";
glue = ", ";
}
if (counts.copyAssign > 0) {
xo << glue << counts.copyAssign << " copy assign";
glue = ", ";
}
if (counts.moveAssign > 0) {
xo << glue << counts.moveAssign << " move assign";
glue = ", ";
}
if (counts.defaultConstruct > 0) {
xo << glue << counts.defaultConstruct << " default construct";
glue = ", ";
}
if (counts.destroyed > 0) {
xo << glue << counts.destroyed << " destroyed";
glue = ", ";
}
xo << "]";
return xo;
}
thread_local Counts sumCounts{};
template <int Tag>
struct Tracked {
static_assert(Tag <= 5, "Need to extend Tracked<Tag> in F14TestUtil.h");
static thread_local Counts counts;
uint64_t val_;
Tracked() : val_{0} {
sumCounts.defaultConstruct++;
counts.defaultConstruct++;
}
/* implicit */ Tracked(uint64_t const& val) : val_{val} {
sumCounts.copyConvert++;
counts.copyConvert++;
}
/* implicit */ Tracked(uint64_t&& val) : val_{val} {
sumCounts.moveConvert++;
counts.moveConvert++;
}
Tracked(Tracked const& rhs) : val_{rhs.val_} {
sumCounts.copyConstruct++;
counts.copyConstruct++;
}
Tracked(Tracked&& rhs) noexcept : val_{rhs.val_} {
sumCounts.moveConstruct++;
counts.moveConstruct++;
}
Tracked& operator=(Tracked const& rhs) {
val_ = rhs.val_;
sumCounts.copyAssign++;
counts.copyAssign++;
return *this;
}
Tracked& operator=(Tracked&& rhs) noexcept {
val_ = rhs.val_;
sumCounts.moveAssign++;
counts.moveAssign++;
return *this;
}
template <int T>
/* implicit */ Tracked(Tracked<T> const& rhs) : val_{rhs.val_} {
sumCounts.copyConvert++;
counts.copyConvert++;
}
template <int T>
/* implicit */ Tracked(Tracked<T>&& rhs) : val_{rhs.val_} {
sumCounts.moveConvert++;
counts.moveConvert++;
}
~Tracked() {
sumCounts.destroyed++;
counts.destroyed++;
}
bool operator==(Tracked const& rhs) const {
return val_ == rhs.val_;
}
bool operator!=(Tracked const& rhs) const {
return !(*this == rhs);
}
};
template <int Tag>
struct TransparentTrackedHash {
using is_transparent = void;
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 = void;
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 <>
thread_local Counts Tracked<1>::counts{};
template <>
thread_local Counts Tracked<2>::counts{};
template <>
thread_local Counts Tracked<3>::counts{};
template <>
thread_local Counts Tracked<4>::counts{};
template <>
thread_local Counts Tracked<5>::counts{};
thread_local size_t testAllocatedMemorySize{0};
thread_local size_t testAllocatedBlockCount{0};
thread_local size_t testAllocationCount{0};
thread_local size_t testAllocationMaxCount{
std::numeric_limits<std::size_t>::max()};
inline void limitTestAllocations(std::size_t allocationsBeforeException = 0) {
testAllocationMaxCount = testAllocationCount + allocationsBeforeException;
}
inline void unlimitTestAllocations() {
testAllocationMaxCount = std::numeric_limits<std::size_t>::max();
}
inline void resetTracking() {
sumCounts = Counts{};
Tracked<0>::counts = Counts{};
Tracked<1>::counts = Counts{};
Tracked<2>::counts = Counts{};
Tracked<3>::counts = Counts{};
Tracked<4>::counts = Counts{};
Tracked<5>::counts = Counts{};
testAllocatedMemorySize = 0;
testAllocatedBlockCount = 0;
testAllocationCount = 0;
testAllocationMaxCount = std::numeric_limits<std::size_t>::max();
}
template <class T>
class SwapTrackingAlloc {
public:
using Alloc = std::allocator<T>;
using value_type = typename Alloc::value_type;
using pointer = typename Alloc::pointer;
using const_pointer = typename Alloc::const_pointer;
using reference = typename Alloc::reference;
using const_reference = typename Alloc::const_reference;
using size_type = typename Alloc::size_type;
using propagate_on_container_swap = std::true_type;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
SwapTrackingAlloc() {}
template <class U>
SwapTrackingAlloc(SwapTrackingAlloc<U> const& other) noexcept
: a_(other.a_), t_(other.t_) {}
template <class U>
SwapTrackingAlloc& operator=(SwapTrackingAlloc<U> const& other) noexcept {
a_ = other.a_;
t_ = other.t_;
return *this;
}
template <class U>
SwapTrackingAlloc(SwapTrackingAlloc<U>&& other) noexcept
: a_(std::move(other.a_)), t_(std::move(other.t_)) {}
template <class U>
SwapTrackingAlloc& operator=(SwapTrackingAlloc<U>&& other) noexcept {
a_ = std::move(other.a_);
t_ = std::move(other.t_);
return *this;
}
T* allocate(size_t n) {
if (testAllocationCount >= testAllocationMaxCount) {
throw std::bad_alloc();
}
++testAllocationCount;
testAllocatedMemorySize += n * sizeof(T);
++testAllocatedBlockCount;
std::size_t extra =
std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
T* p = a_.allocate(extra + n);
void* raw = static_cast<void*>(p);
*static_cast<std::size_t*>(raw) = n;
return p + extra;
}
void deallocate(T* p, size_t n) {
testAllocatedMemorySize -= n * sizeof(T);
--testAllocatedBlockCount;
std::size_t extra =
std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
std::size_t check;
void* raw = static_cast<void*>(p - extra);
check = *static_cast<std::size_t*>(raw);
FOLLY_SAFE_CHECK(check == n, "");
a_.deallocate(p - extra, n + extra);
}
private:
std::allocator<T> a_;
folly::f14::Tracked<0> t_;
template <class U>
friend class SwapTrackingAlloc;
};
template <class T>
void swap(SwapTrackingAlloc<T>&, SwapTrackingAlloc<T>&) {
// For argument dependent lookup:
// This function will be called if the custom swap functions of F14 containers
// are used. Otherwise, std::swap() will do 1 move construct and 2 move
// assigns which will get tracked by t_.
}
template <class T1, class T2>
bool operator==(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
return true;
}
template <class T1, class T2>
bool operator!=(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
return false;
}
std::ostream& operator<<(std::ostream& xo, F14TableStats const& stats) {
using f14::Histo;
inline std::ostream& operator<<(std::ostream& xo, F14TableStats const& stats) {
xo << "{ " << std::endl;
xo << " policy: " << stats.policy << std::endl;
xo << " size: " << stats.size << std::endl;
......@@ -494,154 +106,5 @@ std::ostream& operator<<(std::ostream& xo, F14TableStats const& stats) {
return xo;
}
template <class T>
class GenericAlloc {
public:
using value_type = T;
using pointer = T*;
using const_pointer = T const*;
using reference = T&;
using const_reference = T const&;
using size_type = std::size_t;
using propagate_on_container_swap = std::true_type;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using AllocBytesFunc = folly::Function<void*(std::size_t)>;
using DeallocBytesFunc = folly::Function<void(void*, std::size_t)>;
GenericAlloc() = delete;
template <typename A, typename D>
GenericAlloc(A&& alloc, D&& dealloc)
: alloc_{std::make_shared<AllocBytesFunc>(std::forward<A>(alloc))},
dealloc_{std::make_shared<DeallocBytesFunc>(std::forward<D>(dealloc))} {
}
template <class U>
GenericAlloc(GenericAlloc<U> const& other) noexcept
: alloc_{other.alloc_}, dealloc_{other.dealloc_} {}
template <class U>
GenericAlloc& operator=(GenericAlloc<U> const& other) noexcept {
alloc_ = other.alloc_;
dealloc_ = other.dealloc_;
return *this;
}
template <class U>
GenericAlloc(GenericAlloc<U>&& other) noexcept
: alloc_(std::move(other.alloc_)), dealloc_(std::move(other.dealloc_)) {}
template <class U>
GenericAlloc& operator=(GenericAlloc<U>&& other) noexcept {
alloc_ = std::move(other.alloc_);
dealloc_ = std::move(other.dealloc_);
return *this;
}
T* allocate(size_t n) {
return static_cast<T*>((*alloc_)(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
(*dealloc_)(static_cast<void*>(p), n * sizeof(T));
}
template <typename U>
bool operator==(GenericAlloc<U> const& rhs) const {
return alloc_ == rhs.alloc_;
}
template <typename U>
bool operator!=(GenericAlloc<U> const& rhs) const {
return !(*this == rhs);
}
private:
std::shared_ptr<AllocBytesFunc> alloc_;
std::shared_ptr<DeallocBytesFunc> dealloc_;
template <class U>
friend class GenericAlloc;
};
template <typename T>
class GenericEqual {
public:
using EqualFunc = folly::Function<bool(T const&, T const&)>;
GenericEqual() = delete;
template <typename E>
GenericEqual(E&& equal)
: equal_{std::make_shared<EqualFunc>(std::forward<E>(equal))} {}
bool operator()(T const& lhs, T const& rhs) const {
return (*equal_)(lhs, rhs);
}
private:
std::shared_ptr<EqualFunc> equal_;
};
template <typename T>
class GenericHasher {
public:
using HasherFunc = folly::Function<std::size_t(T const&)>;
GenericHasher() = delete;
template <typename H>
GenericHasher(H&& hasher)
: hasher_{std::make_shared<HasherFunc>(std::forward<H>(hasher))} {}
std::size_t operator()(T const& val) const {
return (*hasher_)(val);
}
private:
std::shared_ptr<HasherFunc> hasher_;
};
struct HashFirst {
template <typename P>
std::size_t operator()(P const& p) const {
return folly::Hash{}(p.first);
}
};
struct EqualFirst {
template <typename P>
bool operator()(P const& lhs, P const& rhs) const {
return lhs.first == rhs.first;
}
};
} // namespace f14
} // namespace folly
namespace std {
template <>
struct hash<folly::f14::MoveOnlyTestInt> {
std::size_t operator()(folly::f14::MoveOnlyTestInt const& val) const {
return val.x;
}
};
template <>
struct hash<folly::f14::ThrowOnCopyTestInt> {
std::size_t operator()(folly::f14::ThrowOnCopyTestInt const& val) const {
return val.x;
}
};
template <int Tag>
struct hash<folly::f14::Tracked<Tag>> {
size_t operator()(folly::f14::Tracked<Tag> const& tracked) const {
return tracked.val_ ^ Tag;
}
};
} // namespace std
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cstddef>
#include <limits>
#include <memory>
#include <ostream>
#include <folly/Function.h>
#include <folly/hash/Hash.h>
#include <folly/lang/SafeAssert.h>
namespace folly {
namespace test {
struct MoveOnlyTestInt {
int x;
bool destroyed{false};
MoveOnlyTestInt() noexcept : x(0) {}
/* implicit */ MoveOnlyTestInt(int x0) : x(x0) {}
MoveOnlyTestInt(MoveOnlyTestInt&& rhs) noexcept : x(rhs.x) {}
MoveOnlyTestInt(MoveOnlyTestInt const&) = delete;
MoveOnlyTestInt& operator=(MoveOnlyTestInt&& rhs) noexcept {
x = rhs.x;
destroyed = rhs.destroyed;
return *this;
}
MoveOnlyTestInt& operator=(MoveOnlyTestInt const&) = delete;
~MoveOnlyTestInt() {
destroyed = true;
}
bool operator==(MoveOnlyTestInt const& rhs) const {
return x == rhs.x && destroyed == rhs.destroyed;
}
bool operator!=(MoveOnlyTestInt const& rhs) const {
return !(*this == rhs);
}
};
struct ThrowOnCopyTestInt {
int x{0};
ThrowOnCopyTestInt() {}
[[noreturn]] ThrowOnCopyTestInt(const ThrowOnCopyTestInt& other)
: x(other.x) {
throw std::exception{};
}
ThrowOnCopyTestInt& operator=(const ThrowOnCopyTestInt&) {
throw std::exception{};
}
bool operator==(const ThrowOnCopyTestInt& other) const {
return x == other.x;
}
bool operator!=(const ThrowOnCopyTestInt& other) const {
return !(x == other.x);
}
};
// Tracked is implicitly constructible across tags
struct Counts {
uint64_t copyConstruct{0};
uint64_t moveConstruct{0};
uint64_t copyConvert{0};
uint64_t moveConvert{0};
uint64_t copyAssign{0};
uint64_t moveAssign{0};
uint64_t defaultConstruct{0};
uint64_t destroyed{0};
explicit Counts(
uint64_t copConstr = 0,
uint64_t movConstr = 0,
uint64_t copConv = 0,
uint64_t movConv = 0,
uint64_t copAssign = 0,
uint64_t movAssign = 0,
uint64_t def = 0,
uint64_t destr = 0)
: copyConstruct{copConstr},
moveConstruct{movConstr},
copyConvert{copConv},
moveConvert{movConv},
copyAssign{copAssign},
moveAssign{movAssign},
defaultConstruct{def},
destroyed{destr} {}
int64_t liveCount() const {
return copyConstruct + moveConstruct + copyConvert + moveConvert +
defaultConstruct - destroyed;
}
// dist ignores destroyed count
uint64_t dist(Counts const& rhs) const {
auto d = [](uint64_t x, uint64_t y) { return (x - y) * (x - y); };
return d(copyConstruct, rhs.copyConstruct) +
d(moveConstruct, rhs.moveConstruct) + d(copyConvert, rhs.copyConvert) +
d(moveConvert, rhs.moveConvert) + d(copyAssign, rhs.copyAssign) +
d(moveAssign, rhs.moveAssign) +
d(defaultConstruct, rhs.defaultConstruct);
}
bool operator==(Counts const& rhs) const {
return dist(rhs) == 0 && destroyed == rhs.destroyed;
}
bool operator!=(Counts const& rhs) const {
return !(*this == rhs);
}
};
inline std::ostream& operator<<(std::ostream& xo, Counts const& counts) {
xo << "[";
std::string glue = "";
if (counts.copyConstruct > 0) {
xo << glue << counts.copyConstruct << " copy";
glue = ", ";
}
if (counts.moveConstruct > 0) {
xo << glue << counts.moveConstruct << " move";
glue = ", ";
}
if (counts.copyConvert > 0) {
xo << glue << counts.copyConvert << " copy convert";
glue = ", ";
}
if (counts.moveConvert > 0) {
xo << glue << counts.moveConvert << " move convert";
glue = ", ";
}
if (counts.copyAssign > 0) {
xo << glue << counts.copyAssign << " copy assign";
glue = ", ";
}
if (counts.moveAssign > 0) {
xo << glue << counts.moveAssign << " move assign";
glue = ", ";
}
if (counts.defaultConstruct > 0) {
xo << glue << counts.defaultConstruct << " default construct";
glue = ", ";
}
if (counts.destroyed > 0) {
xo << glue << counts.destroyed << " destroyed";
glue = ", ";
}
xo << "]";
return xo;
}
inline Counts& sumCounts() {
static thread_local Counts value{};
return value;
}
template <int Tag>
struct Tracked {
static_assert(Tag <= 5, "Need to extend Tracked<Tag> in TestUtil.cpp");
static Counts& counts() {
static thread_local Counts value{};
return value;
}
uint64_t val_;
Tracked() : val_{0} {
sumCounts().defaultConstruct++;
counts().defaultConstruct++;
}
/* implicit */ Tracked(uint64_t const& val) : val_{val} {
sumCounts().copyConvert++;
counts().copyConvert++;
}
/* implicit */ Tracked(uint64_t&& val) : val_{val} {
sumCounts().moveConvert++;
counts().moveConvert++;
}
Tracked(Tracked const& rhs) : val_{rhs.val_} {
sumCounts().copyConstruct++;
counts().copyConstruct++;
}
Tracked(Tracked&& rhs) noexcept : val_{rhs.val_} {
sumCounts().moveConstruct++;
counts().moveConstruct++;
}
Tracked& operator=(Tracked const& rhs) {
val_ = rhs.val_;
sumCounts().copyAssign++;
counts().copyAssign++;
return *this;
}
Tracked& operator=(Tracked&& rhs) noexcept {
val_ = rhs.val_;
sumCounts().moveAssign++;
counts().moveAssign++;
return *this;
}
template <int T>
/* implicit */ Tracked(Tracked<T> const& rhs) : val_{rhs.val_} {
sumCounts().copyConvert++;
counts().copyConvert++;
}
template <int T>
/* implicit */ Tracked(Tracked<T>&& rhs) : val_{rhs.val_} {
sumCounts().moveConvert++;
counts().moveConvert++;
}
~Tracked() {
sumCounts().destroyed++;
counts().destroyed++;
}
bool operator==(Tracked const& rhs) const {
return val_ == rhs.val_;
}
bool operator!=(Tracked const& rhs) const {
return !(*this == rhs);
}
};
template <int Tag>
struct TransparentTrackedHash {
using is_transparent = void;
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 = void;
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);
}
};
inline size_t& testAllocatedMemorySize() {
static thread_local size_t value{0};
return value;
}
inline size_t& testAllocatedBlockCount() {
static thread_local size_t value{0};
return value;
}
inline size_t& testAllocationCount() {
static thread_local size_t value{0};
return value;
}
inline size_t& testAllocationMaxCount() {
static thread_local size_t value{0};
return value;
}
inline void limitTestAllocations(std::size_t allocationsBeforeException = 0) {
testAllocationMaxCount() = testAllocationCount() + allocationsBeforeException;
}
inline void unlimitTestAllocations() {
testAllocationMaxCount() = std::numeric_limits<std::size_t>::max();
}
inline void resetTracking() {
sumCounts() = Counts{};
Tracked<0>::counts() = Counts{};
Tracked<1>::counts() = Counts{};
Tracked<2>::counts() = Counts{};
Tracked<3>::counts() = Counts{};
Tracked<4>::counts() = Counts{};
Tracked<5>::counts() = Counts{};
testAllocatedMemorySize() = 0;
testAllocatedBlockCount() = 0;
testAllocationCount() = 0;
testAllocationMaxCount() = std::numeric_limits<std::size_t>::max();
}
template <class T>
class SwapTrackingAlloc {
public:
using Alloc = std::allocator<T>;
using value_type = typename Alloc::value_type;
using pointer = typename Alloc::pointer;
using const_pointer = typename Alloc::const_pointer;
using reference = typename Alloc::reference;
using const_reference = typename Alloc::const_reference;
using size_type = typename Alloc::size_type;
using propagate_on_container_swap = std::true_type;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
SwapTrackingAlloc() {}
template <class U>
/* implicit */ SwapTrackingAlloc(SwapTrackingAlloc<U> const& other) noexcept
: a_(other.a_), t_(other.t_) {}
template <class U>
SwapTrackingAlloc& operator=(SwapTrackingAlloc<U> const& other) noexcept {
a_ = other.a_;
t_ = other.t_;
return *this;
}
template <class U>
/* implicit */ SwapTrackingAlloc(SwapTrackingAlloc<U>&& other) noexcept
: a_(std::move(other.a_)), t_(std::move(other.t_)) {}
template <class U>
SwapTrackingAlloc& operator=(SwapTrackingAlloc<U>&& other) noexcept {
a_ = std::move(other.a_);
t_ = std::move(other.t_);
return *this;
}
T* allocate(size_t n) {
if (testAllocationCount() >= testAllocationMaxCount()) {
throw std::bad_alloc();
}
++testAllocationCount();
testAllocatedMemorySize() += n * sizeof(T);
++testAllocatedBlockCount();
std::size_t extra =
std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
T* p = a_.allocate(extra + n);
void* raw = static_cast<void*>(p);
*static_cast<std::size_t*>(raw) = n;
return p + extra;
}
void deallocate(T* p, size_t n) {
testAllocatedMemorySize() -= n * sizeof(T);
--testAllocatedBlockCount();
std::size_t extra =
std::max<std::size_t>(1, sizeof(std::size_t) / sizeof(T));
std::size_t check;
void* raw = static_cast<void*>(p - extra);
check = *static_cast<std::size_t*>(raw);
FOLLY_SAFE_CHECK(check == n, "");
a_.deallocate(p - extra, n + extra);
}
private:
std::allocator<T> a_;
Tracked<0> t_;
template <class U>
friend class SwapTrackingAlloc;
};
template <class T>
void swap(SwapTrackingAlloc<T>&, SwapTrackingAlloc<T>&) noexcept {
// For argument dependent lookup:
// This function will be called if the custom swap functions of a container
// is used. Otherwise, std::swap() will do 1 move construct and 2 move
// assigns which will get tracked by t_.
}
template <class T1, class T2>
bool operator==(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
return true;
}
template <class T1, class T2>
bool operator!=(SwapTrackingAlloc<T1> const&, SwapTrackingAlloc<T2> const&) {
return false;
}
template <class T>
class GenericAlloc {
public:
using value_type = T;
using pointer = T*;
using const_pointer = T const*;
using reference = T&;
using const_reference = T const&;
using size_type = std::size_t;
using propagate_on_container_swap = std::true_type;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using AllocBytesFunc = folly::Function<void*(std::size_t)>;
using DeallocBytesFunc = folly::Function<void(void*, std::size_t)>;
GenericAlloc() = delete;
template <typename A, typename D>
GenericAlloc(A&& alloc, D&& dealloc)
: alloc_{std::make_shared<AllocBytesFunc>(std::forward<A>(alloc))},
dealloc_{std::make_shared<DeallocBytesFunc>(std::forward<D>(dealloc))} {
}
template <class U>
/* implicit */ GenericAlloc(GenericAlloc<U> const& other) noexcept
: alloc_{other.alloc_}, dealloc_{other.dealloc_} {}
template <class U>
GenericAlloc& operator=(GenericAlloc<U> const& other) noexcept {
alloc_ = other.alloc_;
dealloc_ = other.dealloc_;
return *this;
}
template <class U>
/* implicit */ GenericAlloc(GenericAlloc<U>&& other) noexcept
: alloc_(std::move(other.alloc_)), dealloc_(std::move(other.dealloc_)) {}
template <class U>
GenericAlloc& operator=(GenericAlloc<U>&& other) noexcept {
alloc_ = std::move(other.alloc_);
dealloc_ = std::move(other.dealloc_);
return *this;
}
T* allocate(size_t n) {
return static_cast<T*>((*alloc_)(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
(*dealloc_)(static_cast<void*>(p), n * sizeof(T));
}
template <typename U>
bool operator==(GenericAlloc<U> const& rhs) const {
return alloc_ == rhs.alloc_;
}
template <typename U>
bool operator!=(GenericAlloc<U> const& rhs) const {
return !(*this == rhs);
}
private:
std::shared_ptr<AllocBytesFunc> alloc_;
std::shared_ptr<DeallocBytesFunc> dealloc_;
template <class U>
friend class GenericAlloc;
};
template <typename T>
class GenericEqual {
public:
using EqualFunc = folly::Function<bool(T const&, T const&)>;
GenericEqual() = delete;
template <typename E>
/* implicit */ GenericEqual(E&& equal)
: equal_{std::make_shared<EqualFunc>(std::forward<E>(equal))} {}
bool operator()(T const& lhs, T const& rhs) const {
return (*equal_)(lhs, rhs);
}
private:
std::shared_ptr<EqualFunc> equal_;
};
template <typename T>
class GenericHasher {
public:
using HasherFunc = folly::Function<std::size_t(T const&)>;
GenericHasher() = delete;
template <typename H>
/* implicit */ GenericHasher(H&& hasher)
: hasher_{std::make_shared<HasherFunc>(std::forward<H>(hasher))} {}
std::size_t operator()(T const& val) const {
return (*hasher_)(val);
}
private:
std::shared_ptr<HasherFunc> hasher_;
};
struct HashFirst {
template <typename P>
std::size_t operator()(P const& p) const {
return folly::Hash{}(p.first);
}
};
struct EqualFirst {
template <typename P>
bool operator()(P const& lhs, P const& rhs) const {
return lhs.first == rhs.first;
}
};
} // namespace test
} // namespace folly
namespace std {
template <>
struct hash<folly::test::MoveOnlyTestInt> {
std::size_t operator()(folly::test::MoveOnlyTestInt const& val) const {
return val.x;
}
};
template <>
struct hash<folly::test::ThrowOnCopyTestInt> {
std::size_t operator()(folly::test::ThrowOnCopyTestInt const& val) const {
return val.x;
}
};
template <int Tag>
struct hash<folly::test::Tracked<Tag>> {
size_t operator()(folly::test::Tracked<Tag> const& tracked) const {
return tracked.val_ ^ Tag;
}
};
} // namespace std
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/container/detail/Util.h>
#include <folly/Optional.h>
#include <folly/container/test/TrackingTypes.h>
#include <folly/portability/GTest.h>
using namespace folly::test;
TEST(Tracked, baseline) {
// this is a test that Tracked works like we expect
Tracked<0> a0;
{
resetTracking();
Tracked<0> b0{a0};
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts(), (Counts{1, 0, 0, 0}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{1, 0, 0, 0}));
}
{
resetTracking();
Tracked<0> b0{std::move(a0)};
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts(), (Counts{0, 1, 0, 0}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{0, 1, 0, 0}));
}
{
resetTracking();
Tracked<1> b1{a0};
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts(), (Counts{0, 0, 1, 0}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 1, 0}));
}
{
resetTracking();
Tracked<1> b1{std::move(a0)};
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 1}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 0, 1}));
}
{
Tracked<0> b0;
resetTracking();
b0 = a0;
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 0, 1, 0}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{0, 0, 0, 0, 1, 0}));
}
{
Tracked<0> b0;
resetTracking();
b0 = std::move(a0);
EXPECT_EQ(a0.val_, b0.val_);
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 0, 0, 1}));
EXPECT_EQ(Tracked<0>::counts(), (Counts{0, 0, 0, 0, 0, 1}));
}
{
Tracked<1> b1;
resetTracking();
b1 = a0;
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts(), (Counts{0, 0, 1, 0, 0, 1, 0, 1}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 1, 0, 0, 1, 0, 1}));
}
{
Tracked<1> b1;
resetTracking();
b1 = std::move(a0);
EXPECT_EQ(a0.val_, b1.val_);
EXPECT_EQ(sumCounts(), (Counts{0, 0, 0, 1, 0, 1, 0, 1}));
EXPECT_EQ(Tracked<1>::counts(), (Counts{0, 0, 0, 1, 0, 1, 0, 1}));
}
}
// F should take a templatized func and a pair const& or pair&& and
// call the function using callWithExtractedKey
template <typename F>
void runKeyExtractCases(
std::string const& name,
F const& func,
uint64_t expectedDist = 0) {
folly::Optional<std::pair<Tracked<0> const, Tracked<1>>> sink;
auto sinkFunc = [&sink](Tracked<0> const& key, auto&&... args) {
if (!sink.hasValue() || sink.value().first != key) {
sink.emplace(std::forward<decltype(args)>(args)...);
}
};
{
std::pair<Tracked<0> const, Tracked<1>> p{0, 0};
sink.clear();
resetTracking();
func(sinkFunc, p);
// fresh key, value_type const& ->
// copy is expected
EXPECT_EQ(
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;
}
{
std::pair<Tracked<0> const, Tracked<1>> p{0, 0};
sink.clear();
resetTracking();
func(sinkFunc, std::move(p));
// fresh key, value_type&& ->
// key copy is unfortunate but required
EXPECT_EQ(
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;
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
sink.clear();
resetTracking();
func(sinkFunc, p);
// fresh key, pair<key_type,mapped_type> const& ->
// 1 copy is required
EXPECT_EQ(
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;
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
sink.clear();
resetTracking();
func(sinkFunc, std::move(p));
// fresh key, pair<key_type,mapped_type>&& ->
// this is the happy path for insert(make_pair(.., ..))
EXPECT_EQ(
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;
}
{
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
sink.clear();
resetTracking();
func(sinkFunc, p);
// fresh key, convertible const& ->
// key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts;
// There are three strategies that could be optimal for particular
// ratios of cost:
//
// - convert key and value in place to final position, destroy if
// insert fails. This is the strategy used by std::unordered_map
// and FBHashMap
//
// - convert key and default value in place to final position,
// convert value only if insert succeeds. Nobody uses this strategy
//
// - convert key to a temporary, move key and convert value if
// insert succeeds. This is the strategy used by F14 and what is
// EXPECT_EQ here.
// The expectedDist * 3 is just a hack for the emplace-pieces-by-value
// test, whose test harness copies the original pair and then uses
// move conversion instead of copy conversion.
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 1, 1, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 1, 0}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist * 3);
}
{
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
sink.clear();
resetTracking();
func(sinkFunc, std::move(p));
// fresh key, convertible&& ->
// key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts;
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 1, 0, 1}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 1}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
std::pair<Tracked<0> const, Tracked<1>> p{0, 0};
sink.clear();
sink.emplace(0, 0);
resetTracking();
func(sinkFunc, p);
// duplicate key, value_type const&
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
std::pair<Tracked<0> const, Tracked<1>> p{0, 0};
sink.clear();
sink.emplace(0, 0);
resetTracking();
func(sinkFunc, std::move(p));
// duplicate key, value_type&&
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
sink.clear();
sink.emplace(0, 0);
resetTracking();
func(sinkFunc, p);
// duplicate key, pair<key_type,mapped_type> const&
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
std::pair<Tracked<0>, Tracked<1>> p{0, 0};
sink.clear();
sink.emplace(0, 0);
resetTracking();
func(sinkFunc, std::move(p));
// duplicate key, pair<key_type,mapped_type>&&
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
{
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
sink.clear();
sink.emplace(0, 0);
resetTracking();
func(sinkFunc, p);
// duplicate key, convertible const& ->
// key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts;
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 1, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist * 2);
}
{
std::pair<Tracked<2>, Tracked<3>> p{0, 0};
sink.clear();
sink.emplace(0, 0);
resetTracking();
func(sinkFunc, std::move(p));
// duplicate key, convertible&& ->
// key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts;
EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 0, 1}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<2>::counts().dist(Counts{0, 0, 0, 0}) +
Tracked<3>::counts().dist(Counts{0, 0, 0, 0}),
expectedDist);
}
}
template <typename T>
using FalseFunc1 = std::false_type;
struct DoEmplace1 {
template <typename F, typename P>
void operator()(F&& f, P&& p) const {
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a, std::forward<F>(f), std::forward<P>(p));
}
};
struct DoEmplace2 {
template <typename F, typename U1, typename U2>
void operator()(F&& f, std::pair<U1, U2> const& p) const {
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a, std::forward<F>(f), p.first, p.second);
}
template <typename F, typename U1, typename U2>
void operator()(F&& f, std::pair<U1, U2>&& p) const {
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a, std::forward<F>(f), std::move(p.first), std::move(p.second));
}
};
struct DoEmplace3 {
template <typename F, typename U1, typename U2>
void operator()(F&& f, std::pair<U1, U2> const& p) const {
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a,
std::forward<F>(f),
std::piecewise_construct,
std::forward_as_tuple(p.first),
std::forward_as_tuple(p.second));
}
template <typename F, typename U1, typename U2>
void operator()(F&& f, std::pair<U1, U2>&& p) const {
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a,
std::forward<F>(f),
std::piecewise_construct,
std::forward_as_tuple(std::move(p.first)),
std::forward_as_tuple(std::move(p.second)));
}
};
// Simulates use of piecewise_construct without proper use of
// forward_as_tuple. This code doesn't yield the normal pattern, but
// it should have exactly 1 additional move or copy of the key and 1
// additional move or copy of the mapped value.
struct DoEmplace3Value {
template <typename F, typename U1, typename U2>
void operator()(F&& f, std::pair<U1, U2> const& p) const {
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a,
std::forward<F>(f),
std::piecewise_construct,
std::tuple<U1>{p.first},
std::tuple<U2>{p.second});
}
template <typename F, typename U1, typename U2>
void operator()(F&& f, std::pair<U1, U2>&& p) const {
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a,
std::forward<F>(f),
std::piecewise_construct,
std::tuple<U1>{std::move(p.first)},
std::tuple<U2>{std::move(p.second)});
}
};
TEST(Util, callWithExtractedKey) {
runKeyExtractCases("emplace pair", DoEmplace1{});
runKeyExtractCases("emplace k,v", DoEmplace2{});
runKeyExtractCases("emplace pieces", DoEmplace3{});
runKeyExtractCases("emplace pieces by value", DoEmplace3Value{}, 2);
// Calling the default pair constructor via emplace is valid, but not
// very useful in real life. Verify that it works.
std::allocator<char> a;
folly::detail::callWithExtractedKey<Tracked<0>, FalseFunc1>(
a, [](Tracked<0> const& key, auto&&... args) {
EXPECT_TRUE(key == 0);
std::pair<Tracked<0> const, Tracked<1>> p(
std::forward<decltype(args)>(args)...);
EXPECT_TRUE(p.first == 0);
EXPECT_TRUE(p.second == 0);
});
}
TEST(Util, callWithConstructedKey) {
folly::Optional<Tracked<0>> sink;
auto sinkFunc = [&](Tracked<0> const& key, auto&&... args) {
if (!sink.hasValue()) {
sink.emplace(std::forward<decltype(args)>(args)...);
} else {
EXPECT_TRUE(sink.value() == key);
}
};
std::allocator<char> a;
{
Tracked<0> k1{0};
Tracked<0> k2{0};
uint64_t k3 = 0;
sink.clear();
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, k1);
// copy is expected on successful emplace
EXPECT_EQ(Tracked<0>::counts().dist(Counts{1, 0, 0, 0}), 0);
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, k2);
// no copies or moves on failing emplace with value_type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, k3);
// copy convert expected for failing emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 1, 0}), 0);
sink.clear();
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, k3);
// copy convert + move expected for successful emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 1, 0}), 0);
}
{
Tracked<0> k1{0};
Tracked<0> k2{0};
uint64_t k3 = 0;
sink.clear();
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, std::move(k1));
// move is expected on successful emplace
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 0}), 0);
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, std::move(k2));
// no copies or moves on failing emplace with value_type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 0}), 0);
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, std::move(k3));
// move convert expected for failing emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 0, 0, 1}), 0);
sink.clear();
resetTracking();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(
a, sinkFunc, std::move(k3));
// move convert + move expected for successful emplace with wrong type
EXPECT_EQ(Tracked<0>::counts().dist(Counts{0, 1, 0, 1}), 0);
}
// Calling the default pair constructor via emplace is valid, but not
// very useful in real life. Verify that it works.
sink.clear();
folly::detail::callWithConstructedKey<Tracked<0>, FalseFunc1>(a, sinkFunc);
EXPECT_TRUE(sink.hasValue());
}
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