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

reduce key comparisons in map and set operator==

Summary:
The standard requires that operator== for associative containers
check key equality using operator==, which may be a refinement of the
container's key_eq.  (The requirement is expressed via equal_range and
std::is_permutation, but has the same effect.)  The straightforward way to
implement this results in duplicate key comparisons.  This diff adds a
containsEqualValue method that can avoid the second check, and uses
it for map and set operator==.

Reviewed By: yfeldblum

Differential Revision: D16455735

fbshipit-source-id: 8ccd0743f8c11bee5d91c065214cead96dd8b72c
parent a752b25d
......@@ -765,6 +765,17 @@ class F14BasicMap {
//// PUBLIC - F14 Extensions
// containsEqualValue returns true iff there is an element in the map
// that compares equal to value using operator==. It is undefined
// behavior to call this function if operator== on key_type can ever
// return true when the same keys passed to key_eq() would return false
// (the opposite is allowed).
bool containsEqualValue(value_type const& value) const {
auto it = table_.findMatching(
value.first, [&](auto& key) { return value.first == key; });
return !it.atEnd() && value.second == table_.valueAtItem(it.citem()).second;
}
// Get memory footprint, not including sizeof(*this).
std::size_t getAllocatedMemorySize() const {
return table_.getAllocatedMemorySize();
......@@ -818,33 +829,6 @@ class F14BasicMap {
protected:
F14Table<Policy> table_;
};
template <typename M>
bool mapsEqual(M const& lhs, M const& rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
for (auto& kv : lhs) {
auto iter = rhs.find(kv.first);
if (iter == rhs.end()) {
return false;
}
if (std::is_same<
typename M::key_equal,
std::equal_to<typename M::key_type>>::value) {
// find already checked key, just check value
if (!(kv.second == iter->second)) {
return false;
}
} else {
// spec says we compare key with == as well as with key_eq()
if (!(kv == *iter)) {
return false;
}
}
}
return true;
}
} // namespace detail
} // namespace f14
......@@ -896,20 +880,6 @@ class F14ValueMap
}
};
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14ValueMap<K, M, H, E, A> const& lhs,
F14ValueMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14ValueMap<K, M, H, E, A> const& lhs,
F14ValueMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <
typename Key,
typename Mapped,
......@@ -963,20 +933,6 @@ class F14NodeMap
// TODO extract and node_handle insert
};
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14NodeMap<K, M, H, E, A> const& lhs,
F14NodeMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14NodeMap<K, M, H, E, A> const& lhs,
F14NodeMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
namespace f14 {
namespace detail {
template <
......@@ -1236,20 +1192,6 @@ class F14VectorMap : public f14::detail::F14VectorMapImpl<
}
};
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14VectorMap<K, M, H, E, A> const& lhs,
F14VectorMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14VectorMap<K, M, H, E, A> const& lhs,
F14VectorMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <
typename Key,
typename Mapped,
......@@ -1293,20 +1235,6 @@ class F14FastMap : public std::conditional_t<
this->table_.swap(rhs.table_);
}
};
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14FastMap<K, M, H, E, A> const& lhs,
F14FastMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14FastMap<K, M, H, E, A> const& lhs,
F14FastMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
} // namespace folly
#else // !if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
......@@ -1334,6 +1262,17 @@ class F14BasicMap : public std::unordered_map<K, M, H, E, A> {
//// PUBLIC - F14 Extensions
bool containsEqualValue(value_type const& value) const {
auto slot = this->bucket(value.first);
auto e = this->end(slot);
for (auto b = this->begin(slot); b != e; ++b) {
if (b->first == value.first) {
return b->second == value.second;
}
}
return false;
}
// exact for libstdc++, approximate for others
std::size_t getAllocatedMemorySize() const {
std::size_t rv = 0;
......@@ -1457,10 +1396,81 @@ class F14FastMap
}
};
} // namespace folly
#endif // if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE else
namespace folly {
namespace f14 {
namespace detail {
template <typename M>
bool mapsEqual(M const& lhs, M const& rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
for (auto& kv : lhs) {
if (!rhs.containsEqualValue(kv)) {
return false;
}
}
return true;
}
} // namespace detail
} // namespace f14
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14ValueMap<K, M, H, E, A> const& lhs,
F14ValueMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14ValueMap<K, M, H, E, A> const& lhs,
F14ValueMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14NodeMap<K, M, H, E, A> const& lhs,
F14NodeMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14NodeMap<K, M, H, E, A> const& lhs,
F14NodeMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14VectorMap<K, M, H, E, A> const& lhs,
F14VectorMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14VectorMap<K, M, H, E, A> const& lhs,
F14VectorMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator==(
F14FastMap<K, M, H, E, A> const& lhs,
F14FastMap<K, M, H, E, A> const& rhs) {
return mapsEqual(lhs, rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
bool operator!=(
F14FastMap<K, M, H, E, A> const& lhs,
F14FastMap<K, M, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename M, typename H, typename E, typename A>
void swap(
......
......@@ -568,6 +568,17 @@ class F14BasicSet {
//// PUBLIC - F14 Extensions
// containsEqualValue returns true iff there is an element in the set
// that compares equal to key using operator==. It is undefined
// behjavior to call this function if operator== on key_type can ever
// return true when the same keys passed to key_eq() would return false
// (the opposite is allowed). When using the default key_eq this function
// is equivalent to contains().
bool containsEqualValue(value_type const& value) const {
return !table_.findMatching(value, [&](auto& k) { return value == k; })
.atEnd();
}
// Get memory footprint, not including sizeof(*this).
std::size_t getAllocatedMemorySize() const {
return table_.getAllocatedMemorySize();
......@@ -612,28 +623,6 @@ class F14BasicSet {
protected:
F14Table<Policy> table_;
};
template <typename S>
bool setsEqual(S const& lhs, S const& rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
for (auto& k : lhs) {
auto iter = rhs.find(k);
if (iter == rhs.end()) {
return false;
}
if (!std::is_same<
typename S::key_equal,
std::equal_to<typename S::value_type>>::value) {
// spec says we compare key with == as well as with key_eq()
if (!(k == *iter)) {
return false;
}
}
}
return true;
}
} // namespace detail
} // namespace f14
......@@ -678,20 +667,6 @@ class F14ValueSet
}
};
template <typename K, typename H, typename E, typename A>
bool operator==(
F14ValueSet<K, H, E, A> const& lhs,
F14ValueSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14ValueSet<K, H, E, A> const& lhs,
F14ValueSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
class F14NodeSet
: public f14::detail::F14BasicSet<f14::detail::SetPolicyWithDefaults<
......@@ -736,20 +711,6 @@ class F14NodeSet
}
};
template <typename K, typename H, typename E, typename A>
bool operator==(
F14NodeSet<K, H, E, A> const& lhs,
F14NodeSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14NodeSet<K, H, E, A> const& lhs,
F14NodeSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
namespace f14 {
namespace detail {
template <
......@@ -1010,20 +971,6 @@ class F14VectorSet
}
};
template <typename K, typename H, typename E, typename A>
bool operator==(
F14VectorSet<K, H, E, A> const& lhs,
F14VectorSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14VectorSet<K, H, E, A> const& lhs,
F14VectorSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename Key, typename Hasher, typename KeyEqual, typename Alloc>
class F14FastSet
: public std::conditional_t<
......@@ -1053,20 +1000,6 @@ class F14FastSet
this->table_.swap(rhs.table_);
}
};
template <typename K, typename H, typename E, typename A>
bool operator==(
F14FastSet<K, H, E, A> const& lhs,
F14FastSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14FastSet<K, H, E, A> const& lhs,
F14FastSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
} // namespace folly
#else // !if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
......@@ -1093,6 +1026,17 @@ class F14BasicSet : public std::unordered_set<K, H, E, A> {
//// PUBLIC - F14 Extensions
bool containsEqualValue(value_type const& value) const {
auto slot = this->bucket(value);
auto e = this->end(slot);
for (auto b = this->begin(slot); b != e; ++b) {
if (*b == value) {
return true;
}
}
return false;
}
// exact for libstdc++, approximate for others
std::size_t getAllocatedMemorySize() const {
std::size_t rv = 0;
......@@ -1200,6 +1144,78 @@ class F14FastSet
#endif // if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE else
namespace folly {
namespace f14 {
namespace detail {
template <typename S>
bool setsEqual(S const& lhs, S const& rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
for (auto& k : lhs) {
if (!rhs.containsEqualValue(k)) {
return false;
}
}
return true;
}
} // namespace detail
} // namespace f14
template <typename K, typename H, typename E, typename A>
bool operator==(
F14ValueSet<K, H, E, A> const& lhs,
F14ValueSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14ValueSet<K, H, E, A> const& lhs,
F14ValueSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator==(
F14NodeSet<K, H, E, A> const& lhs,
F14NodeSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14NodeSet<K, H, E, A> const& lhs,
F14NodeSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator==(
F14VectorSet<K, H, E, A> const& lhs,
F14VectorSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14VectorSet<K, H, E, A> const& lhs,
F14VectorSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator==(
F14FastSet<K, H, E, A> const& lhs,
F14FastSet<K, H, E, A> const& rhs) {
return setsEqual(lhs, rhs);
}
template <typename K, typename H, typename E, typename A>
bool operator!=(
F14FastSet<K, H, E, A> const& lhs,
F14FastSet<K, H, E, A> const& rhs) {
return !(lhs == rhs);
}
template <typename K, typename H, typename E, typename A>
void swap(F14ValueSet<K, H, E, A>& lhs, F14ValueSet<K, H, E, A>& rhs) noexcept(
......
......@@ -588,6 +588,10 @@ class ValueContainerPolicy : public BasePolicy<
return Super::moveValue(item);
}
Value const& valueAtItem(Item const& item) const {
return item;
}
Value&& valueAtItemForExtract(Item& item) {
return std::move(item);
}
......@@ -836,6 +840,10 @@ class NodeContainerPolicy
return Super::moveValue(*item);
}
Value const& valueAtItem(Item const& item) const {
return *item;
}
Value&& valueAtItemForExtract(Item& item) {
return std::move(*item);
}
......@@ -1179,6 +1187,10 @@ class VectorContainerPolicy : public BasePolicy<
return {item};
}
Value const& valueAtItem(Item const& item) const {
return values_[item];
}
Value&& valueAtItemForExtract(Item& item) {
return std::move(values_[item]);
}
......
......@@ -1432,6 +1432,36 @@ class F14Table : public Policy {
return findImpl(static_cast<HashPair>(token), key);
}
// Searches for a key using a key predicate that is a refinement
// of key equality. func(k) should return true only if k is equal
// to key according to key_eq(), but is allowed to apply additional
// constraints.
template <typename K, typename F>
FOLLY_ALWAYS_INLINE ItemIter findMatching(K const& key, F&& func) const {
auto hp = splitHash(this->computeKeyHash(key));
std::size_t index = hp.first;
std::size_t step = probeDelta(hp);
for (std::size_t tries = 0; tries <= chunkMask_; ++tries) {
ChunkPtr chunk = chunks_ + (index & chunkMask_);
if (sizeof(Chunk) > 64) {
prefetchAddr(chunk->itemAddr(8));
}
auto hits = chunk->tagMatchIter(hp.second);
while (hits.hasNext()) {
auto i = hits.next();
if (LIKELY(
func(this->keyForValue(this->valueAtItem(chunk->item(i)))))) {
return ItemIter{chunk, i};
}
}
if (LIKELY(chunk->outboundOverflowCount() == 0)) {
break;
}
index += step;
}
return ItemIter{};
}
private:
void adjustSizeAndBeginAfterInsert(ItemIter iter) {
if (kEnableItemIteration) {
......
......@@ -69,9 +69,15 @@ void repeat(F const& func) {
}
}
bool asanFailuresExpected() {
return kIsLibrarySanitizeAddress &&
f14::detail::getF14IntrinsicsMode() !=
f14::detail::F14IntrinsicsMode::None;
}
template <typename F>
void expectAsanFailure(F const& func) {
if (kIsLibrarySanitizeAddress) {
if (asanFailuresExpected()) {
EXPECT_EXIT(
repeat(func), testing::ExitedWithCode(1), ".*heap-use-after-free.*");
}
......@@ -131,7 +137,7 @@ TEST(F14AsanSupportTest, F14VectorErase) {
auto& v = *set.begin();
EXPECT_EQ(v, 3);
set.erase(2);
if (kIsLibrarySanitizeAddress) {
if (asanFailuresExpected()) {
EXPECT_NE(v, 3);
}
}
......@@ -190,6 +190,93 @@ TEST(F14Map, pmr_empty) {
}
#endif
namespace {
struct NestedHash {
template <typename N>
std::size_t operator()(N const& v) const;
};
template <template <class...> class TMap>
struct Nested {
std::unique_ptr<TMap<Nested, int, NestedHash>> map_;
explicit Nested(int depth)
: map_(std::make_unique<TMap<Nested, int, NestedHash>>()) {
if (depth > 0) {
map_->emplace(Nested{depth - 1}, 0);
}
}
};
template <typename N>
std::size_t NestedHash::operator()(N const& v) const {
std::size_t rv = 0;
for (auto& kv : *v.map_) {
rv += folly::Hash{}(operator()(kv.first), kv.second);
}
return folly::Hash{}(rv);
}
template <template <class...> class TMap>
bool operator==(Nested<TMap> const& lhs, Nested<TMap> const& rhs) {
return *lhs.map_ == *rhs.map_;
}
template <template <class...> class TMap>
bool operator!=(Nested<TMap> const& lhs, Nested<TMap> const& rhs) {
return !(lhs == rhs);
}
template <template <class...> class TMap>
void testNestedMapEquality() {
auto n1 = Nested<TMap>(100);
auto n2 = Nested<TMap>(100);
auto n3 = Nested<TMap>(99);
EXPECT_TRUE(n1 == n1);
EXPECT_TRUE(n1 == n2);
EXPECT_FALSE(n1 == n3);
EXPECT_FALSE(n1 != n1);
EXPECT_FALSE(n1 != n2);
EXPECT_TRUE(n1 != n3);
}
template <template <class...> class TMap>
void testEqualityRefinement() {
TMap<std::pair<int, int>, int, folly::f14::HashFirst, folly::f14::EqualFirst>
m1;
TMap<std::pair<int, int>, int, folly::f14::HashFirst, folly::f14::EqualFirst>
m2;
m1[std::make_pair(0, 0)] = 0;
m1[std::make_pair(1, 1)] = 1;
EXPECT_FALSE(m1.insert(std::make_pair(std::make_pair(0, 2), 0)).second);
EXPECT_EQ(m1.size(), 2);
EXPECT_EQ(m1.count(std::make_pair(0, 10)), 1);
for (auto& kv : m1) {
m2.emplace(std::make_pair(kv.first.first, kv.first.second + 1), kv.second);
}
EXPECT_EQ(m1.size(), m2.size());
for (auto& kv : m1) {
EXPECT_EQ(m2.count(kv.first), 1);
}
EXPECT_FALSE(m1 == m2);
EXPECT_TRUE(m1 != m2);
}
} // namespace
TEST(F14Map, nestedMapEquality) {
testNestedMapEquality<folly::F14ValueMap>();
testNestedMapEquality<folly::F14NodeMap>();
testNestedMapEquality<folly::F14VectorMap>();
testNestedMapEquality<folly::F14FastMap>();
}
TEST(F14Map, equalityRefinement) {
testEqualityRefinement<folly::F14ValueMap>();
testEqualityRefinement<folly::F14NodeMap>();
testEqualityRefinement<folly::F14VectorMap>();
testEqualityRefinement<folly::F14FastMap>();
}
///////////////////////////////////
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
......
......@@ -187,6 +187,91 @@ TEST(F14Set, pmr_empty) {
}
#endif
namespace {
struct NestedHash {
template <typename N>
std::size_t operator()(N const& v) const;
};
template <template <class...> class TSet>
struct Nested {
std::unique_ptr<TSet<Nested, NestedHash>> set_;
explicit Nested(int depth)
: set_(std::make_unique<TSet<Nested, NestedHash>>()) {
if (depth > 0) {
set_->emplace(depth - 1);
}
}
};
template <typename N>
std::size_t NestedHash::operator()(N const& v) const {
std::size_t rv = 0;
for (auto& k : *v.set_) {
rv += operator()(k);
}
return folly::Hash{}(rv);
}
template <template <class...> class TSet>
bool operator==(Nested<TSet> const& lhs, Nested<TSet> const& rhs) {
return *lhs.set_ == *rhs.set_;
}
template <template <class...> class TSet>
bool operator!=(Nested<TSet> const& lhs, Nested<TSet> const& rhs) {
return !(lhs == rhs);
}
template <template <class...> class TSet>
void testNestedSetEquality() {
auto n1 = Nested<TSet>(100);
auto n2 = Nested<TSet>(100);
auto n3 = Nested<TSet>(99);
EXPECT_TRUE(n1 == n1);
EXPECT_TRUE(n1 == n2);
EXPECT_FALSE(n1 == n3);
EXPECT_FALSE(n1 != n1);
EXPECT_FALSE(n1 != n2);
EXPECT_TRUE(n1 != n3);
}
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;
s1.insert(std::make_pair(0, 0));
s1.insert(std::make_pair(1, 1));
EXPECT_FALSE(s1.insert(std::make_pair(0, 2)).second);
EXPECT_EQ(s1.size(), 2);
EXPECT_EQ(s1.count(std::make_pair(0, 10)), 1);
for (auto& k : s1) {
s2.emplace(k.first, k.second + 1);
}
EXPECT_EQ(s1.size(), s2.size());
for (auto& k : s1) {
EXPECT_EQ(s2.count(k), 1);
}
EXPECT_FALSE(s1 == s2);
EXPECT_TRUE(s1 != s2);
}
} // namespace
TEST(F14Set, nestedSetEquality) {
testNestedSetEquality<folly::F14ValueSet>();
testNestedSetEquality<folly::F14NodeSet>();
testNestedSetEquality<folly::F14VectorSet>();
testNestedSetEquality<folly::F14FastSet>();
}
TEST(F14Set, equalityRefinement) {
testEqualityRefinement<folly::F14ValueSet>();
testEqualityRefinement<folly::F14NodeSet>();
testEqualityRefinement<folly::F14VectorSet>();
testEqualityRefinement<folly::F14FastSet>();
}
///////////////////////////////////
#if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
///////////////////////////////////
......
......@@ -606,6 +606,20 @@ class GenericHasher {
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
......
......@@ -1253,3 +1253,35 @@ TEST(Dynamic, Math) {
}
}
}
dynamic buildNestedKeys(size_t depth) {
if (depth == 0) {
return dynamic(0);
}
return dynamic::object(buildNestedKeys(depth - 1), 0);
}
dynamic buildNestedValues(size_t depth) {
if (depth == 0) {
return dynamic(0);
}
return dynamic::object(0, buildNestedValues(depth - 1));
}
TEST(Dynamic, EqualNestedKeys) {
// This tests for exponential behavior in the depth of the keys.
// If it is exponential this test won't finish.
size_t const kDepth = 100;
dynamic obj1 = buildNestedKeys(kDepth);
dynamic obj2 = obj1;
EXPECT_EQ(obj1, obj2);
}
TEST(Dynamic, EqualNestedValues) {
// This tests for exponential behavior in the depth of the values.
// If it is exponential this test won't finish.
size_t const kDepth = 100;
dynamic obj1 = buildNestedValues(kDepth);
dynamic obj2 = obj1;
EXPECT_EQ(obj1, obj2);
}
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