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>> {
}
};
template<class Value>
class Empty : public GenImpl<Value, Empty<Value>> {
public:
template<class Handler>
bool apply(Handler&&) const { return true; }
};
/*
* Operators
......@@ -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
* provided to generate a key to uniquify for each value.
......
......@@ -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 {
public:
template<class Value>
......@@ -197,6 +260,10 @@ class Chain;
template<class Value, class Source>
class Yield;
template<class Value>
class Empty;
/*
* Operators
*/
......@@ -225,6 +292,9 @@ class Distinct;
template<class First, class Second>
class Composed;
template<class Expected>
class TypeAssertion;
/*
* Sinks
*/
......@@ -335,12 +405,20 @@ Yield generator(Source&& source) {
/*
* Create inline generator, used like:
*
* auto gen = GENERATOR(int) { yield(1); yield(2); };
* auto gen = GENERATOR(int) { yield(1); yield(2); };
*/
#define GENERATOR(TYPE) \
::folly::gen::detail::GeneratorBuilder<TYPE>() + \
[=](const std::function<void(TYPE)>& yield)
/*
* empty() - for producing empty sequences.
*/
template<class Value>
detail::Empty<Value> empty() {
return {};
}
/*
* Operator Factories
*/
......@@ -356,6 +434,66 @@ Map map(Predicate pred = Predicate()) {
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,
class Filter = detail::Filter<Predicate>>
Filter filter(Predicate pred = Predicate()) {
......@@ -415,6 +553,11 @@ To eachTo() {
return To();
}
template<class Value>
detail::TypeAssertion<Value> assert_type() {
return {};
}
/*
* Sink Factories
*/
......
......@@ -51,6 +51,10 @@ static vector<vector<int>> testVectorVector =
return seq(1, i) | as<vector>();
})
| as<vector>();
static vector<fbstring> strings =
from(testVector)
| eachTo<fbstring>()
| as<vector>();
auto square = [](int x) { return x * x; };
auto add = [](int a, int b) { return a + b; };
......@@ -98,6 +102,28 @@ BENCHMARK_RELATIVE(Sum_Vector_Gen, iters) {
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) {
int s = 0;
while (iters--) {
......
......@@ -104,6 +104,81 @@ TEST(Gen, Map) {
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) {
// cover the fenceposts of the loop unrolling
for (int n = 1; n < 100; ++n) {
......@@ -590,6 +665,7 @@ TEST(Gen, NoNeedlessCopies) {
}
namespace {
class TestIntSeq : public GenImpl<int, TestIntSeq> {
public:
TestIntSeq() { }
......@@ -609,6 +685,7 @@ class TestIntSeq : public GenImpl<int, TestIntSeq> {
TestIntSeq(const TestIntSeq&) = delete;
TestIntSeq& operator=(const TestIntSeq&) = delete;
};
} // namespace
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