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

fix heterogeneous lookup for non-transitive is_convertible

Summary:
In most cases two implicit conversions can be chained to perform
an explicit construction, but that is not the case when the intermediate
type has a template parameter that can't be deduced. This interferes
with heterogeneous lookup when the transparent hasher or equality functor
work by implicit conversion to a base type (like folly::Range). This diff
disables heteregeneous find in cases where the hash functor or equality
functor are not invocable.

Reviewed By: yfeldblum

Differential Revision: D13264397

fbshipit-source-id: 082c3215e81ab58f3fa9519da872642e45b16245
parent 179e9a12
...@@ -231,7 +231,11 @@ struct EligibleForHeterogeneousFind< ...@@ -231,7 +231,11 @@ struct EligibleForHeterogeneousFind<
Hasher, Hasher,
KeyEqual, KeyEqual,
ArgKey, ArgKey,
void_t<typename Hasher::is_transparent, typename KeyEqual::is_transparent>> void_t<
typename Hasher::is_transparent,
typename KeyEqual::is_transparent,
invoke_result_t<Hasher, ArgKey const&>,
invoke_result_t<KeyEqual, ArgKey const&, TableKey const&>>>
: std::true_type {}; : std::true_type {};
template < template <
......
...@@ -1500,6 +1500,72 @@ TEST(F14FastMap, heterogeneousInsert) { ...@@ -1500,6 +1500,72 @@ TEST(F14FastMap, heterogeneousInsert) {
runHeterogeneousInsertStringTest<F14FastMap<std::string, std::string>>(); runHeterogeneousInsertStringTest<F14FastMap<std::string, std::string>>();
} }
namespace {
// std::is_convertible is not transitive :( Problem scenario: B<T> is
// implicitly convertible to A, so hasher that takes A can be used as a
// transparent hasher for a map with key of type B<T>. C is implicitly
// convertible to any B<T>, but we have to disable heterogeneous find
// for C. There is no way to infer the T of the intermediate type so C
// can't be used to explicitly construct A.
struct A {
int value;
bool operator==(A const& rhs) const {
return value == rhs.value;
}
bool operator!=(A const& rhs) const {
return !(*this == rhs);
}
};
struct AHasher {
std::size_t operator()(A const& v) const {
return v.value;
}
};
template <typename T>
struct B {
int value;
explicit B(int v) : value(v) {}
/* implicit */ B(A const& v) : value(v.value) {}
/* implicit */ operator A() const {
return A{value};
}
};
struct C {
int value;
template <typename T>
/* implicit */ operator B<T>() const {
return B<T>{value};
}
};
} // namespace
TEST(F14FastMap, disabledDoubleTransparent) {
static_assert(std::is_convertible<B<char>, A>::value, "");
static_assert(std::is_convertible<C, B<char>>::value, "");
static_assert(!std::is_convertible<C, A>::value, "");
F14FastMap<
B<char>,
int,
folly::transparent<AHasher>,
folly::transparent<std::equal_to<A>>>
map;
map[A{10}] = 10;
EXPECT_TRUE(map.find(C{10}) != map.end());
EXPECT_TRUE(map.find(C{20}) == map.end());
}
/////////////////////////////////// ///////////////////////////////////
#endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE #endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
/////////////////////////////////// ///////////////////////////////////
...@@ -1138,6 +1138,71 @@ TEST(F14FastSet, heterogeneousInsert) { ...@@ -1138,6 +1138,71 @@ TEST(F14FastSet, heterogeneousInsert) {
runHeterogeneousInsertStringTest<F14FastSet<std::string>>(); runHeterogeneousInsertStringTest<F14FastSet<std::string>>();
} }
namespace {
// std::is_convertible is not transitive :( Problem scenario: B<T> is
// implicitly convertible to A, so hasher that takes A can be used as a
// transparent hasher for a map with key of type B<T>. C is implicitly
// convertible to any B<T>, but we have to disable heterogeneous find
// for C. There is no way to infer the T of the intermediate type so C
// can't be used to explicitly construct A.
struct A {
int value;
bool operator==(A const& rhs) const {
return value == rhs.value;
}
bool operator!=(A const& rhs) const {
return !(*this == rhs);
}
};
struct AHasher {
std::size_t operator()(A const& v) const {
return v.value;
}
};
template <typename T>
struct B {
int value;
explicit B(int v) : value(v) {}
/* implicit */ B(A const& v) : value(v.value) {}
/* implicit */ operator A() const {
return A{value};
}
};
struct C {
int value;
template <typename T>
/* implicit */ operator B<T>() const {
return B<T>{value};
}
};
} // namespace
TEST(F14FastSet, disabledDoubleTransparent) {
static_assert(std::is_convertible<B<char>, A>::value, "");
static_assert(std::is_convertible<C, B<char>>::value, "");
static_assert(!std::is_convertible<C, A>::value, "");
F14FastSet<
B<char>,
folly::transparent<AHasher>,
folly::transparent<std::equal_to<A>>>
set;
set.emplace(A{10});
EXPECT_TRUE(set.find(C{10}) != set.end());
EXPECT_TRUE(set.find(C{20}) == set.end());
}
namespace { namespace {
struct CharArrayHasher { struct CharArrayHasher {
template <std::size_t N> template <std::size_t N>
......
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