Commit 5c507b28 authored by Tom Jackson's avatar Tom Jackson Committed by Owen Yamauchi

member(&Foo::getter), field(&Foo::field)

Summary: For easily calling a getter on every item in a sequence.

Test Plan: Unit tests, benchmarks

Reviewed By: mmcurtiss@fb.com

FB internal diff: D651206
parent 42eed265
...@@ -532,6 +532,12 @@ class Yield : public GenImpl<Value, Yield<Value, Source>> { ...@@ -532,6 +532,12 @@ class Yield : public GenImpl<Value, Yield<Value, Source>> {
} }
}; };
template<class Value>
class Empty : public GenImpl<Value, Empty<Value>> {
public:
template<class Handler>
bool apply(Handler&&) const { return true; }
};
/* /*
* Operators * Operators
...@@ -1036,6 +1042,33 @@ class Order : public Operator<Order<Selector, Comparer>> { ...@@ -1036,6 +1042,33 @@ class Order : public Operator<Order<Selector, Comparer>> {
} }
}; };
/*
* TypeAssertion - For verifying the exact type of the value produced by a
* generator. Useful for testing and debugging, and acts as a no-op at runtime.
* Pass-through at runtime. Used through the 'assert_type<>()' factory method
* like so:
*
* auto c = from(vector) | assert_type<int&>() | sum;
*
*/
template<class Expected>
class TypeAssertion : public Operator<TypeAssertion<Expected>> {
public:
template<class Source, class Value>
const Source& compose(const GenImpl<Value, Source>& source) const {
static_assert(std::is_same<Expected, Value>::value,
"assert_type() check failed");
return source.self();
}
template<class Source, class Value>
Source&& compose(GenImpl<Value, Source>&& source) const {
static_assert(std::is_same<Expected, Value>::value,
"assert_type() check failed");
return std::move(source.self());
}
};
/** /**
* Distinct - For filtering duplicates out of a sequence. A selector may be * Distinct - For filtering duplicates out of a sequence. A selector may be
* provided to generate a key to uniquify for each value. * provided to generate a key to uniquify for each value.
......
...@@ -121,6 +121,69 @@ public: ...@@ -121,6 +121,69 @@ public:
} }
}; };
template<class Class,
class Result>
class MemberFunction {
public:
typedef Result (Class::*MemberPtr)();
private:
MemberPtr member_;
public:
explicit MemberFunction(MemberPtr member)
: member_(member)
{}
Result operator()(Class&& x) const {
return (x.*member_)();
}
Result operator()(Class& x) const {
return (x.*member_)();
}
};
template<class Class,
class Result>
class ConstMemberFunction{
public:
typedef Result (Class::*MemberPtr)() const;
private:
MemberPtr member_;
public:
explicit ConstMemberFunction(MemberPtr member)
: member_(member)
{}
Result operator()(const Class& x) const {
return (x.*member_)();
}
};
template<class Class,
class FieldType>
class Field {
public:
typedef FieldType (Class::*FieldPtr);
private:
FieldPtr field_;
public:
explicit Field(FieldPtr field)
: field_(field)
{}
const FieldType& operator()(const Class& x) const {
return x.*field_;
}
FieldType& operator()(Class& x) const {
return x.*field_;
}
FieldType&& operator()(Class&& x) const {
return std::move(x.*field_);
}
};
class Move { class Move {
public: public:
template<class Value> template<class Value>
...@@ -197,6 +260,10 @@ class Chain; ...@@ -197,6 +260,10 @@ class Chain;
template<class Value, class Source> template<class Value, class Source>
class Yield; class Yield;
template<class Value>
class Empty;
/* /*
* Operators * Operators
*/ */
...@@ -225,6 +292,9 @@ class Distinct; ...@@ -225,6 +292,9 @@ class Distinct;
template<class First, class Second> template<class First, class Second>
class Composed; class Composed;
template<class Expected>
class TypeAssertion;
/* /*
* Sinks * Sinks
*/ */
...@@ -335,12 +405,20 @@ Yield generator(Source&& source) { ...@@ -335,12 +405,20 @@ Yield generator(Source&& source) {
/* /*
* Create inline generator, used like: * Create inline generator, used like:
* *
* auto gen = GENERATOR(int) { yield(1); yield(2); }; * auto gen = GENERATOR(int) { yield(1); yield(2); };
*/ */
#define GENERATOR(TYPE) \ #define GENERATOR(TYPE) \
::folly::gen::detail::GeneratorBuilder<TYPE>() + \ ::folly::gen::detail::GeneratorBuilder<TYPE>() + \
[=](const std::function<void(TYPE)>& yield) [=](const std::function<void(TYPE)>& yield)
/*
* empty() - for producing empty sequences.
*/
template<class Value>
detail::Empty<Value> empty() {
return {};
}
/* /*
* Operator Factories * Operator Factories
*/ */
...@@ -356,6 +434,66 @@ Map map(Predicate pred = Predicate()) { ...@@ -356,6 +434,66 @@ Map map(Predicate pred = Predicate()) {
return Map(std::move(pred)); return Map(std::move(pred));
} }
/*
* member(...) - For extracting a member from each value.
*
* vector<string> strings = ...;
* auto sizes = from(strings) | member(&string::size);
*
* If a member is const overridden (like 'front()'), pass template parameter
* 'Const' to select the const version, or 'Mutable' to select the non-const
* version:
*
* auto heads = from(strings) | member<Const>(&string::front);
*/
enum MemberType {
Const,
Mutable
};
template<MemberType Constness = Const,
class Class,
class Return,
class Mem = ConstMemberFunction<Class, Return>,
class Map = detail::Map<Mem>>
typename std::enable_if<Constness == Const, Map>::type
member(Return (Class::*member)() const) {
return Map(Mem(member));
}
template<MemberType Constness = Mutable,
class Class,
class Return,
class Mem = MemberFunction<Class, Return>,
class Map = detail::Map<Mem>>
typename std::enable_if<Constness == Mutable, Map>::type
member(Return (Class::*member)()) {
return Map(Mem(member));
}
/*
* field(...) - For extracting a field from each value.
*
* vector<Item> items = ...;
* auto names = from(items) | field(&Item::name);
*
* Note that if the values of the generator are rvalues, any non-reference
* fields will be rvalues as well. As an example, the code below does not copy
* any strings, only moves them:
*
* auto namesVector = from(items)
* | move
* | field(&Item::name)
* | as<vector>();
*/
template<class Class,
class FieldType,
class Field = Field<Class, FieldType>,
class Map = detail::Map<Field>>
Map field(FieldType Class::*field) {
return Map(Field(field));
}
template<class Predicate, template<class Predicate,
class Filter = detail::Filter<Predicate>> class Filter = detail::Filter<Predicate>>
Filter filter(Predicate pred = Predicate()) { Filter filter(Predicate pred = Predicate()) {
...@@ -415,6 +553,11 @@ To eachTo() { ...@@ -415,6 +553,11 @@ To eachTo() {
return To(); return To();
} }
template<class Value>
detail::TypeAssertion<Value> assert_type() {
return {};
}
/* /*
* Sink Factories * Sink Factories
*/ */
......
...@@ -51,6 +51,10 @@ static vector<vector<int>> testVectorVector = ...@@ -51,6 +51,10 @@ static vector<vector<int>> testVectorVector =
return seq(1, i) | as<vector>(); return seq(1, i) | as<vector>();
}) })
| as<vector>(); | as<vector>();
static vector<fbstring> strings =
from(testVector)
| eachTo<fbstring>()
| as<vector>();
auto square = [](int x) { return x * x; }; auto square = [](int x) { return x * x; };
auto add = [](int a, int b) { return a + b; }; auto add = [](int a, int b) { return a + b; };
...@@ -98,6 +102,28 @@ BENCHMARK_RELATIVE(Sum_Vector_Gen, iters) { ...@@ -98,6 +102,28 @@ BENCHMARK_RELATIVE(Sum_Vector_Gen, iters) {
BENCHMARK_DRAW_LINE() BENCHMARK_DRAW_LINE()
BENCHMARK(Member, iters) {
int s = 0;
while(iters--) {
s += from(strings)
| member(&fbstring::size)
| sum;
}
folly::doNotOptimizeAway(s);
}
BENCHMARK_RELATIVE(MapMember, iters) {
int s = 0;
while(iters--) {
s += from(strings)
| map([](const fbstring& x) { return x.size(); })
| sum;
}
folly::doNotOptimizeAway(s);
}
BENCHMARK_DRAW_LINE()
BENCHMARK(Count_Vector_NoGen, iters) { BENCHMARK(Count_Vector_NoGen, iters) {
int s = 0; int s = 0;
while (iters--) { while (iters--) {
......
...@@ -104,6 +104,81 @@ TEST(Gen, Map) { ...@@ -104,6 +104,81 @@ TEST(Gen, Map) {
EXPECT_EQ((vector<int>{4, 9}), gen | take(2) | as<vector>()); EXPECT_EQ((vector<int>{4, 9}), gen | take(2) | as<vector>());
} }
TEST(Gen, Member) {
struct Counter {
Counter(int start = 0)
: c(start)
{}
int count() const { return c; }
int incr() { return ++c; }
int& ref() { return c; }
const int& ref() const { return c; }
private:
int c;
};
auto counters = seq(1, 10) | eachAs<Counter>() | as<vector>();
EXPECT_EQ(10 * (1 + 10) / 2,
from(counters)
| member(&Counter::count)
| sum);
EXPECT_EQ(10 * (2 + 11) / 2,
from(counters)
| member(&Counter::incr)
| sum);
EXPECT_EQ(10 * (2 + 11) / 2,
from(counters)
| member(&Counter::count)
| sum);
// type-verifications
auto m = empty<Counter&>();
auto c = empty<const Counter&>();
m | member(&Counter::incr) | assert_type<int&&>();
m | member(&Counter::count) | assert_type<int&&>();
m | member(&Counter::count) | assert_type<int&&>();
m | member<Const>(&Counter::ref) | assert_type<const int&>();
m | member<Mutable>(&Counter::ref) | assert_type<int&>();
c | member<Const>(&Counter::ref) | assert_type<const int&>();
}
TEST(Gen, Field) {
struct X {
X() : a(2), b(3), c(4), d(b) {}
const int a;
int b;
mutable int c;
int& d; // can't access this with a field pointer.
};
std::vector<X> xs(1);
EXPECT_EQ(2, from(xs)
| field(&X::a)
| first);
EXPECT_EQ(3, from(xs)
| field(&X::b)
| first);
EXPECT_EQ(4, from(xs)
| field(&X::c)
| first);
// type-verification
empty<X&>() | field(&X::a) | assert_type<const int&>();
empty<X&>() | field(&X::b) | assert_type<int&>();
empty<X&>() | field(&X::c) | assert_type<int&>();
empty<X&&>() | field(&X::a) | assert_type<const int&&>();
empty<X&&>() | field(&X::b) | assert_type<int&&>();
empty<X&&>() | field(&X::c) | assert_type<int&&>();
// references don't imply ownership so they're not moved
empty<const X&>() | field(&X::a) | assert_type<const int&>();
empty<const X&>() | field(&X::b) | assert_type<const int&>();
// 'mutable' has no effect on field pointers, by C++ spec
empty<const X&>() | field(&X::c) | assert_type<const int&>();
// can't form pointer-to-reference field: empty<X&>() | field(&X::d)
}
TEST(Gen, Seq) { TEST(Gen, Seq) {
// cover the fenceposts of the loop unrolling // cover the fenceposts of the loop unrolling
for (int n = 1; n < 100; ++n) { for (int n = 1; n < 100; ++n) {
...@@ -590,6 +665,7 @@ TEST(Gen, NoNeedlessCopies) { ...@@ -590,6 +665,7 @@ TEST(Gen, NoNeedlessCopies) {
} }
namespace { namespace {
class TestIntSeq : public GenImpl<int, TestIntSeq> { class TestIntSeq : public GenImpl<int, TestIntSeq> {
public: public:
TestIntSeq() { } TestIntSeq() { }
...@@ -609,6 +685,7 @@ class TestIntSeq : public GenImpl<int, TestIntSeq> { ...@@ -609,6 +685,7 @@ class TestIntSeq : public GenImpl<int, TestIntSeq> {
TestIntSeq(const TestIntSeq&) = delete; TestIntSeq(const TestIntSeq&) = delete;
TestIntSeq& operator=(const TestIntSeq&) = delete; TestIntSeq& operator=(const TestIntSeq&) = delete;
}; };
} // namespace } // namespace
TEST(Gen, NoGeneratorCopies) { TEST(Gen, NoGeneratorCopies) {
......
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