Commit 7c1e9ed8 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

A transparent adapter to facilitate the is_transparent protocol

Summary:
[Folly] A `transparent` adapter to facilitate the `is_transparent` protocol.

Compare types with member type or member alias `is_transparent` may be used to enable heterogeneous lookup in ordered containers which support it, such as `std::set` and `std::map` as of C++14, and `folly::sorted_vector_set` and `folly::sorted_vector_map`.

In some cases, nothing more is required than to add that `is_transparent` member to an existing compare type - `folly::transparent` may facilitate such cases.

Example:

```lang=c++
using TransparentStringPieceLess = folly::transparent<std::less<folly::StringPiece>>>;
std::map<std::string, float, /* Compare = */ TransparentStringPieceLess> m;
m["somekey"] = 12.;
m.find(folly::StringPiece("somekey"))->second = 13.; // heterogeneous lookup
```

To demonstrate the example, rewrite the `folly::sorted_vector_set` and `folly::sorted_vector_map` transparent-lookup tests.

Reviewed By: ot

Differential Revision: D6921862

fbshipit-source-id: 306ff1f6f178ae29ac80ee7cd28595b80fc393d1
parent cd90eca5
...@@ -304,6 +304,12 @@ constexpr presorted_t presorted{}; ...@@ -304,6 +304,12 @@ constexpr presorted_t presorted{};
struct unsorted_t {}; struct unsorted_t {};
constexpr unsorted_t unsorted{}; constexpr unsorted_t unsorted{};
template <typename T>
struct transparent : T {
using is_transparent = void;
using T::T;
};
/** /**
* A simple function object that passes its argument through unchanged. * A simple function object that passes its argument through unchanged.
* *
...@@ -324,7 +330,6 @@ constexpr unsorted_t unsorted{}; ...@@ -324,7 +330,6 @@ constexpr unsorted_t unsorted{};
* // is no longer valid * // is no longer valid
*/ */
struct Identity { struct Identity {
using is_transparent = void;
template <class T> template <class T>
constexpr T&& operator()(T&& x) const noexcept { constexpr T&& operator()(T&& x) const noexcept {
return static_cast<T&&>(x); return static_cast<T&&>(x);
......
...@@ -19,7 +19,9 @@ ...@@ -19,7 +19,9 @@
#include <iterator> #include <iterator>
#include <list> #include <list>
#include <memory> #include <memory>
#include <string>
#include <folly/Range.h>
#include <folly/portability/GMock.h> #include <folly/portability/GMock.h>
#include <folly/portability/GTest.h> #include <folly/portability/GTest.h>
...@@ -167,70 +169,52 @@ TEST(SortedVectorTypes, SimpleSetTest) { ...@@ -167,70 +169,52 @@ TEST(SortedVectorTypes, SimpleSetTest) {
} }
TEST(SortedVectorTypes, TransparentSetTest) { TEST(SortedVectorTypes, TransparentSetTest) {
sorted_vector_set<Opaque, Opaque::Compare> s; using namespace folly::string_piece_literals;
EXPECT_TRUE(s.empty()); using Compare = folly::transparent<std::less<folly::StringPiece>>;
for (int i = 0; i < 1000; ++i) {
s.insert(Opaque{rand() % 100000}); constexpr auto buddy = "buddy"_sp;
} constexpr auto hello = "hello"_sp;
EXPECT_FALSE(s.empty()); constexpr auto stake = "stake"_sp;
check_invariant(s); constexpr auto world = "world"_sp;
constexpr auto zebra = "zebra"_sp;
sorted_vector_set<Opaque, Opaque::Compare> s2;
s2.insert(s.begin(), s.end()); sorted_vector_set<std::string, Compare> const s({hello.str(), world.str()});
check_invariant(s2);
EXPECT_TRUE(s == s2); // find
EXPECT_TRUE(s.end() == s.find(buddy));
auto it = s2.lower_bound(32); EXPECT_EQ(hello, *s.find(hello));
if (it->value == 32) { EXPECT_TRUE(s.end() == s.find(stake));
s2.erase(it); EXPECT_EQ(world, *s.find(world));
it = s2.lower_bound(32); EXPECT_TRUE(s.end() == s.find(zebra));
// count
EXPECT_EQ(0, s.count(buddy));
EXPECT_EQ(1, s.count(hello));
EXPECT_EQ(0, s.count(stake));
EXPECT_EQ(1, s.count(world));
EXPECT_EQ(0, s.count(zebra));
// lower_bound
EXPECT_TRUE(s.find(hello) == s.lower_bound(buddy));
EXPECT_TRUE(s.find(hello) == s.lower_bound(hello));
EXPECT_TRUE(s.find(world) == s.lower_bound(stake));
EXPECT_TRUE(s.find(world) == s.lower_bound(world));
EXPECT_TRUE(s.end() == s.lower_bound(zebra));
// upper_bound
EXPECT_TRUE(s.find(hello) == s.upper_bound(buddy));
EXPECT_TRUE(s.find(world) == s.upper_bound(hello));
EXPECT_TRUE(s.find(world) == s.upper_bound(stake));
EXPECT_TRUE(s.end() == s.upper_bound(world));
EXPECT_TRUE(s.end() == s.upper_bound(zebra));
// equal_range
for (auto value : {buddy, hello, stake, world, zebra}) {
EXPECT_TRUE(
std::make_pair(s.lower_bound(value), s.upper_bound(value)) ==
s.equal_range(value))
<< value;
} }
check_invariant(s2);
auto oldSz = s2.size();
s2.insert(it, Opaque{32});
EXPECT_TRUE(s2.size() == oldSz + 1);
check_invariant(s2);
const sorted_vector_set<Opaque, Opaque::Compare>& cs2 = s2;
auto range = cs2.equal_range(32);
auto lbound = cs2.lower_bound(32);
auto ubound = cs2.upper_bound(32);
EXPECT_TRUE(range.first == lbound);
EXPECT_TRUE(range.second == ubound);
EXPECT_TRUE(range.first != cs2.end());
EXPECT_TRUE(range.second != cs2.end());
EXPECT_TRUE(cs2.count(32) == 1);
EXPECT_FALSE(cs2.find(32) == cs2.end());
// Bad insert hint.
s2.insert(s2.begin() + 3, Opaque{33});
EXPECT_TRUE(s2.find(33) != s2.begin());
EXPECT_TRUE(s2.find(33) != s2.end());
check_invariant(s2);
s2.erase(Opaque{33});
check_invariant(s2);
it = s2.find(32);
EXPECT_FALSE(it == s2.end());
s2.erase(it);
EXPECT_TRUE(s2.size() == oldSz);
check_invariant(s2);
sorted_vector_set<Opaque, Opaque::Compare> cpy(s);
check_invariant(cpy);
EXPECT_TRUE(cpy == s);
sorted_vector_set<Opaque, Opaque::Compare> cpy2(s);
cpy2.insert(Opaque{100001});
EXPECT_TRUE(cpy2 != cpy);
EXPECT_TRUE(cpy2 != s);
check_invariant(cpy2);
EXPECT_TRUE(cpy2.count(100001) == 1);
s.swap(cpy2);
check_invariant(cpy2);
check_invariant(s);
EXPECT_TRUE(s != cpy);
EXPECT_TRUE(s != cpy2);
EXPECT_TRUE(cpy2 == cpy);
} }
TEST(SortedVectorTypes, BadHints) { TEST(SortedVectorTypes, BadHints) {
...@@ -310,64 +294,53 @@ TEST(SortedVectorTypes, SimpleMapTest) { ...@@ -310,64 +294,53 @@ TEST(SortedVectorTypes, SimpleMapTest) {
} }
TEST(SortedVectorTypes, TransparentMapTest) { TEST(SortedVectorTypes, TransparentMapTest) {
sorted_vector_map<Opaque, float, Opaque::Compare> m; using namespace folly::string_piece_literals;
for (int i = 0; i < 1000; ++i) { using Compare = folly::transparent<std::less<folly::StringPiece>>;
m[Opaque{i}] = i / 1000.0;
constexpr auto buddy = "buddy"_sp;
constexpr auto hello = "hello"_sp;
constexpr auto stake = "stake"_sp;
constexpr auto world = "world"_sp;
constexpr auto zebra = "zebra"_sp;
sorted_vector_map<std::string, float, Compare> const m(
{{hello.str(), -1.}, {world.str(), +1.}});
// find
EXPECT_TRUE(m.end() == m.find(buddy));
EXPECT_EQ(hello, m.find(hello)->first);
EXPECT_TRUE(m.end() == m.find(stake));
EXPECT_EQ(world, m.find(world)->first);
EXPECT_TRUE(m.end() == m.find(zebra));
// count
EXPECT_EQ(0, m.count(buddy));
EXPECT_EQ(1, m.count(hello));
EXPECT_EQ(0, m.count(stake));
EXPECT_EQ(1, m.count(world));
EXPECT_EQ(0, m.count(zebra));
// lower_bound
EXPECT_TRUE(m.find(hello) == m.lower_bound(buddy));
EXPECT_TRUE(m.find(hello) == m.lower_bound(hello));
EXPECT_TRUE(m.find(world) == m.lower_bound(stake));
EXPECT_TRUE(m.find(world) == m.lower_bound(world));
EXPECT_TRUE(m.end() == m.lower_bound(zebra));
// upper_bound
EXPECT_TRUE(m.find(hello) == m.upper_bound(buddy));
EXPECT_TRUE(m.find(world) == m.upper_bound(hello));
EXPECT_TRUE(m.find(world) == m.upper_bound(stake));
EXPECT_TRUE(m.end() == m.upper_bound(world));
EXPECT_TRUE(m.end() == m.upper_bound(zebra));
// equal_range
for (auto value : {buddy, hello, stake, world, zebra}) {
EXPECT_TRUE(
std::make_pair(m.lower_bound(value), m.upper_bound(value)) ==
m.equal_range(value))
<< value;
} }
check_invariant(m);
m[Opaque{32}] = 100.0;
check_invariant(m);
EXPECT_TRUE(m.count(32) == 1);
EXPECT_DOUBLE_EQ(100.0, m.at(Opaque{32}));
EXPECT_FALSE(m.find(32) == m.end());
m.erase(Opaque{32});
EXPECT_TRUE(m.find(32) == m.end());
check_invariant(m);
EXPECT_THROW(m.at(Opaque{32}), std::out_of_range);
sorted_vector_map<Opaque, float, Opaque::Compare> m2 = m;
EXPECT_TRUE(m2 == m);
EXPECT_FALSE(m2 != m);
auto it = m2.lower_bound(1 << 20);
EXPECT_TRUE(it == m2.end());
m2.insert(it, std::make_pair(Opaque{1 << 20}, 10.0f));
check_invariant(m2);
EXPECT_TRUE(m2.count(1 << 20) == 1);
EXPECT_TRUE(m < m2);
EXPECT_TRUE(m <= m2);
const sorted_vector_map<Opaque, float, Opaque::Compare>& cm = m;
auto range = cm.equal_range(42);
auto lbound = cm.lower_bound(42);
auto ubound = cm.upper_bound(42);
EXPECT_TRUE(range.first == lbound);
EXPECT_TRUE(range.second == ubound);
EXPECT_FALSE(range.first == cm.end());
EXPECT_FALSE(range.second == cm.end());
m.erase(m.lower_bound(42));
check_invariant(m);
sorted_vector_map<Opaque, float, Opaque::Compare> m3;
m3.insert(m2.begin(), m2.end());
check_invariant(m3);
EXPECT_TRUE(m3 == m2);
EXPECT_FALSE(m3 == m);
EXPECT_TRUE(m != m2);
EXPECT_TRUE(m2 == m3);
EXPECT_TRUE(m3 != m);
m.swap(m3);
check_invariant(m);
check_invariant(m2);
check_invariant(m3);
EXPECT_TRUE(m3 != m2);
EXPECT_TRUE(m3 != m);
EXPECT_TRUE(m == m2);
// Bad insert hint.
m.insert(m.begin() + 3, std::make_pair(Opaque{1 << 15}, 1.0f));
check_invariant(m);
} }
TEST(SortedVectorTypes, Sizes) { TEST(SortedVectorTypes, Sizes) {
......
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