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

callWithExtractedKey fix for pair of lvalue ref, improvements

Summary:
callWithExtractedKey previously incorrectly translated
std::pair<X&,Y&>&& into X&&, Y&&, when reference collapsing rules call
instead for X& and Y&.  This diff fixes the problem, adds unit tests,
and also targets the 2-argument std::pair constructor when possible.
This means that callWithExtractedKey can be safely used on top of
libc++'s std::unordered_map::emplace without disabling the key-finding
optimization in that method.  (That lib's emplace is clever enough to
be able to search on the key before constructing a node, but does not
include the logic to unwrap the std::pair 3-arg piecewise constructor.)

Reviewed By: yfeldblum

Differential Revision: D21419822

fbshipit-source-id: d3c61e45d7fe7d8c6805342bbe63a8c866f4e306
parent fa2e7912
...@@ -84,6 +84,31 @@ struct TemporaryEmplaceKey { ...@@ -84,6 +84,31 @@ struct TemporaryEmplaceKey {
// about heterogeneous lookup you can just pass a single-arg template // about heterogeneous lookup you can just pass a single-arg template
// that extends std::false_type. // that extends std::false_type.
template <typename Func, typename KeyType, typename Arg1, typename Arg2>
auto callWithKeyAndPairArgs(
Func&& f,
KeyType const& key,
std::tuple<Arg1>&& first_args,
std::tuple<Arg2>&& second_args) {
return f(
key,
std::forward<Arg1>(std::get<0>(first_args)),
std::forward<Arg2>(std::get<0>(second_args)));
}
template <typename Func, typename KeyType, typename... Args1, typename... Args2>
auto callWithKeyAndPairArgs(
Func&& f,
KeyType const& key,
std::tuple<Args1...>&& first_args,
std::tuple<Args2...>&& second_args) {
return f(
key,
std::piecewise_construct,
std::move(first_args),
std::move(second_args));
}
template < template <
typename KeyType, typename KeyType,
template <typename> class UsableAsKey, template <typename> class UsableAsKey,
...@@ -103,9 +128,9 @@ auto callWithExtractedKey( ...@@ -103,9 +128,9 @@ auto callWithExtractedKey(
std::tuple<Args2...>&& second_args) { std::tuple<Args2...>&& second_args) {
// we found a usable key in the args :) // we found a usable key in the args :)
auto const& key = std::get<0>(first_args); auto const& key = std::get<0>(first_args);
return f( return callWithKeyAndPairArgs(
std::forward<Func>(f),
key, key,
std::piecewise_construct,
std::tuple<Arg1&&>(std::move(first_args)), std::tuple<Arg1&&>(std::move(first_args)),
std::tuple<Args2&&...>(std::move(second_args))); std::tuple<Args2&&...>(std::move(second_args)));
} }
...@@ -126,9 +151,9 @@ auto callWithExtractedKey( ...@@ -126,9 +151,9 @@ auto callWithExtractedKey(
// we will need to materialize a temporary key :( // we will need to materialize a temporary key :(
TemporaryEmplaceKey<KeyType, Alloc> key( TemporaryEmplaceKey<KeyType, Alloc> key(
a, std::tuple<Args1&&...>(std::move(first_args))); a, std::tuple<Args1&&...>(std::move(first_args)));
return f( return callWithKeyAndPairArgs(
std::forward<Func>(f),
const_cast<KeyType const&>(key.value()), const_cast<KeyType const&>(key.value()),
std::piecewise_construct,
std::forward_as_tuple(std::move(key.value())), std::forward_as_tuple(std::move(key.value())),
std::tuple<Args2&&...>(std::move(second_args))); std::tuple<Args2&&...>(std::move(second_args)));
} }
...@@ -187,12 +212,15 @@ template < ...@@ -187,12 +212,15 @@ template <
typename U1, typename U1,
typename U2> typename U2>
auto callWithExtractedKey(Alloc& a, Func&& f, std::pair<U1, U2>&& p) { auto callWithExtractedKey(Alloc& a, Func&& f, std::pair<U1, U2>&& p) {
// std::move(p.first) is wrong because if U1 is an lvalue reference the
// result will incorrectly be an rvalue ref. static_cast here allows
// proper ref collapsing
return callWithExtractedKey<KeyType, UsableAsKey>( return callWithExtractedKey<KeyType, UsableAsKey>(
a, a,
std::forward<Func>(f), std::forward<Func>(f),
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(std::move(p.first)), std::forward_as_tuple(static_cast<U1&&>(p.first)),
std::forward_as_tuple(std::move(p.second))); std::forward_as_tuple(static_cast<U2&&>(p.second)));
} }
// callWithConstructedKey is the set container analogue of // callWithConstructedKey is the set container analogue of
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
#include <folly/container/detail/Util.h> #include <folly/container/detail/Util.h>
#include <glog/logging.h>
#include <folly/Optional.h> #include <folly/Optional.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/container/test/TrackingTypes.h> #include <folly/container/test/TrackingTypes.h>
...@@ -116,8 +118,8 @@ void runKeyExtractCases( ...@@ -116,8 +118,8 @@ void runKeyExtractCases(
Tracked<0>::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}), Tracked<1>::counts().dist(Counts{1, 0, 0, 0}),
expectedDist) expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> " << name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts; << Tracked<1>::counts();
} }
{ {
std::pair<Tracked<0> const, Tracked<1>> p{0, 0}; std::pair<Tracked<0> const, Tracked<1>> p{0, 0};
...@@ -130,8 +132,8 @@ void runKeyExtractCases( ...@@ -130,8 +132,8 @@ void runKeyExtractCases(
Tracked<0>::counts().dist(Counts{1, 0, 0, 0}) + Tracked<0>::counts().dist(Counts{1, 0, 0, 0}) +
Tracked<1>::counts().dist(Counts{0, 1, 0, 0}), Tracked<1>::counts().dist(Counts{0, 1, 0, 0}),
expectedDist) expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> " << name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts; << Tracked<1>::counts();
} }
{ {
std::pair<Tracked<0>, Tracked<1>> p{0, 0}; std::pair<Tracked<0>, Tracked<1>> p{0, 0};
...@@ -144,8 +146,8 @@ void runKeyExtractCases( ...@@ -144,8 +146,8 @@ void runKeyExtractCases(
Tracked<0>::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}), Tracked<1>::counts().dist(Counts{1, 0, 0, 0}),
expectedDist) expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> " << name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts; << Tracked<1>::counts();
} }
{ {
std::pair<Tracked<0>, Tracked<1>> p{0, 0}; std::pair<Tracked<0>, Tracked<1>> p{0, 0};
...@@ -158,8 +160,8 @@ void runKeyExtractCases( ...@@ -158,8 +160,8 @@ void runKeyExtractCases(
Tracked<0>::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}), Tracked<1>::counts().dist(Counts{0, 1, 0, 0}),
expectedDist) expectedDist)
<< name << "\n0 -> " << Tracked<0>::counts << "\n1 -> " << name << "\n0 -> " << Tracked<0>::counts() << "\n1 -> "
<< Tracked<1>::counts; << Tracked<1>::counts();
} }
{ {
std::pair<Tracked<2>, Tracked<3>> p{0, 0}; std::pair<Tracked<2>, Tracked<3>> p{0, 0};
...@@ -170,7 +172,7 @@ void runKeyExtractCases( ...@@ -170,7 +172,7 @@ void runKeyExtractCases(
// key_type ops: Tracked<0>::counts // key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts // mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts // key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts; // mapped_src ops: Tracked<3>::counts();
// There are three strategies that could be optimal for particular // There are three strategies that could be optimal for particular
// ratios of cost: // ratios of cost:
...@@ -205,7 +207,7 @@ void runKeyExtractCases( ...@@ -205,7 +207,7 @@ void runKeyExtractCases(
// key_type ops: Tracked<0>::counts // key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts // mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts // key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts; // mapped_src ops: Tracked<3>::counts();
EXPECT_EQ( EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 1, 0, 1}) + Tracked<0>::counts().dist(Counts{0, 1, 0, 1}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 1}) + Tracked<1>::counts().dist(Counts{0, 0, 0, 1}) +
...@@ -271,7 +273,7 @@ void runKeyExtractCases( ...@@ -271,7 +273,7 @@ void runKeyExtractCases(
// key_type ops: Tracked<0>::counts // key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts // mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts // key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts; // mapped_src ops: Tracked<3>::counts();
EXPECT_EQ( EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 1, 0}) + Tracked<0>::counts().dist(Counts{0, 0, 1, 0}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) + Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) +
...@@ -289,7 +291,7 @@ void runKeyExtractCases( ...@@ -289,7 +291,7 @@ void runKeyExtractCases(
// key_type ops: Tracked<0>::counts // key_type ops: Tracked<0>::counts
// mapped_type ops: Tracked<1>::counts // mapped_type ops: Tracked<1>::counts
// key_src ops: Tracked<2>::counts // key_src ops: Tracked<2>::counts
// mapped_src ops: Tracked<3>::counts; // mapped_src ops: Tracked<3>::counts();
EXPECT_EQ( EXPECT_EQ(
Tracked<0>::counts().dist(Counts{0, 0, 0, 1}) + Tracked<0>::counts().dist(Counts{0, 0, 0, 1}) +
Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) + Tracked<1>::counts().dist(Counts{0, 0, 0, 0}) +
...@@ -479,66 +481,87 @@ TEST(Util, callWithConstructedKey) { ...@@ -479,66 +481,87 @@ TEST(Util, callWithConstructedKey) {
template <typename T> template <typename T>
using IsStringPiece = std::is_same<T, StringPiece>; using IsStringPiece = std::is_same<T, StringPiece>;
template <typename KeyType, typename Arg1, typename Arg2>
struct ExpectArgTypes {
int which{0};
KeyType const* expectedAddr{nullptr};
template <typename K, typename... Args>
void operator()(K const&, Args&&... args) {
// avoid static_assert to ensure we don't affect SFINAE
EXPECT_TRUE((std::is_same<K, KeyType>::value)) << which;
using T = std::tuple<Args&&...>;
EXPECT_EQ(std::tuple_size<T>::value, 2) << which;
EXPECT_TRUE((std::is_same<std::tuple_element_t<0, T>, Arg1>::value))
<< which;
EXPECT_TRUE((std::is_same<std::tuple_element_t<1, T>, Arg2>::value))
<< which;
auto t = std::forward_as_tuple(std::forward<Args>(args)...);
EXPECT_TRUE(expectedAddr == nullptr || expectedAddr == &std::get<0>(t))
<< which;
}
};
TEST(Util, callWithExtractedHeterogeneousKey) { TEST(Util, callWithExtractedHeterogeneousKey) {
std::allocator<char> a; std::allocator<char> a;
std::string str{"key"}; std::string str{"key"};
StringPiece sp{"key"}; StringPiece sp{"key"};
char const* ptr{"key"}; char const* ptr{"key"};
auto strPair = std::make_pair(str, 0);
std::pair<std::string&, int> strLRefPair(str, 0);
std::pair<std::string&&, int> strRRefPair(std::move(str), 0);
auto spPair = std::make_pair(sp, 0);
auto ptrPair = std::make_pair(ptr, 0);
// none of the std::move below actually get consumed
detail::callWithExtractedKey<std::string, IsStringPiece>(
a, ExpectArgTypes<std::string, std::string&, int&&>{0, &str}, str, 0);
detail::callWithExtractedKey<std::string, IsStringPiece>( detail::callWithExtractedKey<std::string, IsStringPiece>(
a, a,
[](auto const& key, auto&&... args) { ExpectArgTypes<std::string, std::string const&, int const&>{
// avoid static_assert to ensure we don't affect SFINAE 1, &strPair.first},
EXPECT_TRUE((std::is_same<decltype(key), std::string const&>::value)); strPair);
using T = std::tuple<decltype(args)&&...>;
EXPECT_EQ(std::tuple_size<T>::value, 3);
EXPECT_TRUE((std::is_same<
std::tuple_element_t<1, T>,
std::tuple<std::string&>&&>::value));
EXPECT_TRUE(
(std::is_same<std::tuple_element_t<2, T>, std::tuple<int&&>&&>::
value));
},
str,
0);
detail::callWithExtractedKey<std::string, IsStringPiece>( detail::callWithExtractedKey<std::string, IsStringPiece>(
a, a,
[](auto const& key, auto&&... args) { ExpectArgTypes<std::string, std::string&, int&&>{2, &str},
EXPECT_TRUE((std::is_same<decltype(key), std::string const&>::value)); std::move(strLRefPair));
using T = std::tuple<decltype(args)&&...>; detail::callWithExtractedKey<std::string, IsStringPiece>(
EXPECT_EQ(std::tuple_size<T>::value, 3); a,
EXPECT_TRUE((std::is_same< ExpectArgTypes<std::string, std::string&, int const&>{10, &str},
std::tuple_element_t<1, T>, strLRefPair);
std::tuple<std::string&&>&&>::value));
}, detail::callWithExtractedKey<std::string, IsStringPiece>(
a,
ExpectArgTypes<std::string, std::string&&, int&&>{3, &str},
std::move(str), std::move(str),
0); 0);
detail::callWithExtractedKey<std::string, IsStringPiece>( detail::callWithExtractedKey<std::string, IsStringPiece>(
a, a,
[](auto const& key, auto&&... args) { ExpectArgTypes<std::string, std::string&&, int&&>{4, &strPair.first},
EXPECT_TRUE((std::is_same<decltype(key), StringPiece const&>::value)); std::move(strPair));
using T = std::tuple<decltype(args)&&...>;
EXPECT_EQ(std::tuple_size<T>::value, 3);
EXPECT_TRUE((std::is_same<
std::tuple_element_t<1, T>,
std::tuple<StringPiece&>&&>::value));
},
sp,
0);
detail::callWithExtractedKey<std::string, IsStringPiece>( detail::callWithExtractedKey<std::string, IsStringPiece>(
a, a,
[](auto const& key, auto&&... args) { ExpectArgTypes<std::string, std::string&, int const&>{5, &str},
// avoid static_assert to ensure we don't affect SFINAE strRRefPair);
EXPECT_TRUE((std::is_same<decltype(key), std::string const&>::value)); detail::callWithExtractedKey<std::string, IsStringPiece>(
using T = std::tuple<decltype(args)&&...>; a,
EXPECT_EQ(std::tuple_size<T>::value, 3); ExpectArgTypes<std::string, std::string&&, int&&>{11, &str},
EXPECT_TRUE((std::is_same< std::move(strRRefPair));
std::tuple_element_t<1, T>,
std::tuple<std::string&&>&&>::value)); detail::callWithExtractedKey<std::string, IsStringPiece>(
auto t = std::forward_as_tuple(std::forward<decltype(args)>(args)...); a, ExpectArgTypes<StringPiece, StringPiece&, int&&>{6, &sp}, sp, 0);
EXPECT_EQ(&key, &std::get<0>(std::get<1>(t))); detail::callWithExtractedKey<std::string, IsStringPiece>(
}, a,
ptr, ExpectArgTypes<StringPiece, StringPiece const&, int const&>{
0); 7, &spPair.first},
spPair);
detail::callWithExtractedKey<std::string, IsStringPiece>(
a, ExpectArgTypes<std::string, std::string&&, int&&>{8}, ptr, 0);
detail::callWithExtractedKey<std::string, IsStringPiece>(
a, ExpectArgTypes<std::string, std::string&&, int const&>{9}, ptrPair);
} }
TEST(Util, callWithConstructedHeterogeneousKey) { TEST(Util, callWithConstructedHeterogeneousKey) {
......
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