member(&Foo::getter), field(&Foo::field)
authorTom Jackson <tjackson@fb.com>
Thu, 23 May 2013 22:34:03 +0000 (15:34 -0700)
committerOwen Yamauchi <oyamauchi@fb.com>
Mon, 3 Jun 2013 19:20:37 +0000 (12:20 -0700)
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
folly/experimental/Gen.h
folly/experimental/test/GenBenchmark.cpp
folly/experimental/test/GenTest.cpp

index e8861d0bb9f87181efe2d22b324df4928d37abfc..f916878c48b8861a6c59f7623a0b4c2f28ddf2df 100644 (file)
@@ -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.
index 130cc7732c60b9a2cff336a5d79e64fa09994821..264ebf665af46aae7198e99c8136079ce52101a0 100644 (file)
@@ -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
  */
index cb33e0004c22dbf68bfdb76cfcfe9d213e31c67a..20516a215f354eb91486cc30d760607a66420c6e 100644 (file)
@@ -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--) {
index 33dc7cd476f97e8563b7c840d0758bc6bd25d660..8c7b826f7fe755e6c5b7ce152bfebb710752fa86 100644 (file)
@@ -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) {