From 5c507b28a23677a85f5e2ed6aefca3ce54517a1e Mon Sep 17 00:00:00 2001 From: Tom Jackson Date: Thu, 23 May 2013 15:34:03 -0700 Subject: [PATCH] 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 --- folly/experimental/Gen-inl.h | 33 ++++++ folly/experimental/Gen.h | 145 ++++++++++++++++++++++- folly/experimental/test/GenBenchmark.cpp | 26 ++++ folly/experimental/test/GenTest.cpp | 77 ++++++++++++ 4 files changed, 280 insertions(+), 1 deletion(-) diff --git a/folly/experimental/Gen-inl.h b/folly/experimental/Gen-inl.h index e8861d0b..f916878c 100644 --- a/folly/experimental/Gen-inl.h +++ b/folly/experimental/Gen-inl.h @@ -532,6 +532,12 @@ class Yield : public GenImpl> { } }; +template +class Empty : public GenImpl> { + public: + template + bool apply(Handler&&) const { return true; } +}; /* * Operators @@ -1036,6 +1042,33 @@ class Order : public Operator> { } }; +/* + * 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() | sum; + * + */ +template +class TypeAssertion : public Operator> { + public: + template + const Source& compose(const GenImpl& source) const { + static_assert(std::is_same::value, + "assert_type() check failed"); + return source.self(); + } + + template + Source&& compose(GenImpl&& source) const { + static_assert(std::is_same::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. diff --git a/folly/experimental/Gen.h b/folly/experimental/Gen.h index 130cc773..264ebf66 100644 --- a/folly/experimental/Gen.h +++ b/folly/experimental/Gen.h @@ -121,6 +121,69 @@ public: } }; +template +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 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 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 @@ -197,6 +260,10 @@ class Chain; template class Yield; +template +class Empty; + + /* * Operators */ @@ -225,6 +292,9 @@ class Distinct; template class Composed; +template +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() + \ [=](const std::function& yield) +/* + * empty() - for producing empty sequences. + */ +template +detail::Empty 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 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(&string::front); + */ +enum MemberType { + Const, + Mutable +}; + +template, + class Map = detail::Map> +typename std::enable_if::type +member(Return (Class::*member)() const) { + return Map(Mem(member)); +} + +template, + class Map = detail::Map> +typename std::enable_if::type +member(Return (Class::*member)()) { + return Map(Mem(member)); +} + +/* + * field(...) - For extracting a field from each value. + * + * vector 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(); + */ +template, + class Map = detail::Map> +Map field(FieldType Class::*field) { + return Map(Field(field)); +} + template> Filter filter(Predicate pred = Predicate()) { @@ -415,6 +553,11 @@ To eachTo() { return To(); } +template +detail::TypeAssertion assert_type() { + return {}; +} + /* * Sink Factories */ diff --git a/folly/experimental/test/GenBenchmark.cpp b/folly/experimental/test/GenBenchmark.cpp index cb33e000..20516a21 100644 --- a/folly/experimental/test/GenBenchmark.cpp +++ b/folly/experimental/test/GenBenchmark.cpp @@ -51,6 +51,10 @@ static vector> testVectorVector = return seq(1, i) | as(); }) | as(); +static vector strings = + from(testVector) + | eachTo() + | as(); 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--) { diff --git a/folly/experimental/test/GenTest.cpp b/folly/experimental/test/GenTest.cpp index 33dc7cd4..8c7b826f 100644 --- a/folly/experimental/test/GenTest.cpp +++ b/folly/experimental/test/GenTest.cpp @@ -104,6 +104,81 @@ TEST(Gen, Map) { EXPECT_EQ((vector{4, 9}), gen | take(2) | as()); } +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() | as(); + 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(); + auto c = empty(); + m | member(&Counter::incr) | assert_type(); + m | member(&Counter::count) | assert_type(); + m | member(&Counter::count) | assert_type(); + m | member(&Counter::ref) | assert_type(); + m | member(&Counter::ref) | assert_type(); + c | member(&Counter::ref) | assert_type(); +} + +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 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() | field(&X::a) | assert_type(); + empty() | field(&X::b) | assert_type(); + empty() | field(&X::c) | assert_type(); + empty() | field(&X::a) | assert_type(); + empty() | field(&X::b) | assert_type(); + empty() | field(&X::c) | assert_type(); + // references don't imply ownership so they're not moved + empty() | field(&X::a) | assert_type(); + empty() | field(&X::b) | assert_type(); + // 'mutable' has no effect on field pointers, by C++ spec + empty() | field(&X::c) | assert_type(); + + // can't form pointer-to-reference field: empty() | 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 { public: TestIntSeq() { } @@ -609,6 +685,7 @@ class TestIntSeq : public GenImpl { TestIntSeq(const TestIntSeq&) = delete; TestIntSeq& operator=(const TestIntSeq&) = delete; }; + } // namespace TEST(Gen, NoGeneratorCopies) { -- 2.34.1