folly::overload and folly::variant_match
authorPavel Aslanov <aslpavel@fb.com>
Wed, 26 Apr 2017 21:15:50 +0000 (14:15 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 26 Apr 2017 21:20:03 +0000 (14:20 -0700)
Summary: Pattern matching like functionality for boost::vairant. See unittests for usage examples.

Reviewed By: yfeldblum, ericniebler

Differential Revision: D4851133

fbshipit-source-id: cda7dc766dac5870bcc4ab1859de0e4e7f0a6599

folly/DiscriminatedPtr.h
folly/Makefile.am
folly/Overload.h [new file with mode: 0644]
folly/test/DiscriminatedPtrTest.cpp
folly/test/OverloadTest.cpp [new file with mode: 0644]

index 7fda96b..0d485f5 100644 (file)
@@ -215,4 +215,25 @@ class DiscriminatedPtr {
   uintptr_t data_;
 };
 
+template <typename Visitor, typename... Args>
+decltype(auto) apply_visitor(
+    Visitor&& visitor,
+    const DiscriminatedPtr<Args...>& variant) {
+  return variant.apply(std::forward<Visitor>(visitor));
+}
+
+template <typename Visitor, typename... Args>
+decltype(auto) apply_visitor(
+    Visitor&& visitor,
+    DiscriminatedPtr<Args...>& variant) {
+  return variant.apply(std::forward<Visitor>(visitor));
+}
+
+template <typename Visitor, typename... Args>
+decltype(auto) apply_visitor(
+    Visitor&& visitor,
+    DiscriminatedPtr<Args...>&& variant) {
+  return variant.apply(std::forward<Visitor>(visitor));
+}
+
 }  // namespace folly
index 692a719..68543e5 100644 (file)
@@ -291,6 +291,7 @@ nobase_follyinclude_HEADERS = \
        MPMCPipeline.h \
        MPMCQueue.h \
        Optional.h \
+       Overload.h \
        PackedSyncPtr.h \
        Padded.h \
        Partial.h \
diff --git a/folly/Overload.h b/folly/Overload.h
new file mode 100644 (file)
index 0000000..de1f535
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+
+/**
+ * folly implementation of `std::overload` like functionality
+ *
+ * Example:
+ *  struct One {};
+ *  struct Two {};
+ *  boost::variant<One, Two> value;
+ *
+ *  variant_match(value,
+ *    [] (const One& one) { ... },
+ *    [] (const Two& two) { ... });
+ */
+
+namespace folly {
+
+namespace details {
+template <typename...>
+struct Overload;
+
+template <typename Case, typename... Cases>
+struct Overload<Case, Cases...> : Overload<Cases...>, Case {
+  Overload(Case c, Cases... cs)
+      : Overload<Cases...>(std::move(cs)...), Case(std::move(c)) {}
+
+  using Case::operator();
+  using Overload<Cases...>::operator();
+};
+
+template <typename Case>
+struct Overload<Case> : Case {
+  explicit Overload(Case c) : Case(std::move(c)) {}
+
+  using Case::operator();
+};
+} // details
+
+/*
+ * Combine multiple `Cases` in one function object
+ */
+template <typename... Cases>
+decltype(auto) overload(Cases&&... cases) {
+  return details::Overload<typename std::decay<Cases>::type...>{
+      std::forward<Cases>(cases)...};
+}
+
+/*
+ * Match `Variant` with one of the `Cases`
+ *
+ * Note: you can also use `[] (const auto&) {...}` as default case
+ *
+ */
+template <typename Variant, typename... Cases>
+decltype(auto) variant_match(Variant&& variant, Cases&&... cases) {
+  return apply_visitor(
+      overload(std::forward<Cases>(cases)...), std::forward<Variant>(variant));
+}
+
+} // folly
index fafd6fa..f4d4ca7 100644 (file)
@@ -86,6 +86,9 @@ TEST(DiscriminatedPtr, Apply) {
   p.set(&foo);
   EXPECT_EQ("Foo", p.apply(Visitor()));
   EXPECT_EQ("const Foo", static_cast<const Ptr&>(p).apply(Visitor()));
+  EXPECT_EQ("Foo", apply_visitor(Visitor(), p));
+  EXPECT_EQ("const Foo", apply_visitor(Visitor(), static_cast<const Ptr&>(p)));
+  EXPECT_EQ("Foo", apply_visitor(Visitor(), std::move(p)));
 
   p.clear();
   EXPECT_THROW({p.apply(Visitor());}, std::invalid_argument);
diff --git a/folly/test/OverloadTest.cpp b/folly/test/OverloadTest.cpp
new file mode 100644 (file)
index 0000000..697b2de
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <boost/variant.hpp>
+#include <folly/DiscriminatedPtr.h>
+#include <folly/Overload.h>
+#include <folly/portability/GTest.h>
+
+namespace folly {
+namespace test {
+
+struct One {
+  std::string toString() const {
+    return "One";
+  }
+};
+struct Two {
+  std::string toString() const {
+    return "Two";
+  }
+};
+using OneOrTwo = boost::variant<One, Two>;
+
+TEST(Overload, BoostVariant) {
+  OneOrTwo one(One{});
+  OneOrTwo two(Two{});
+
+  EXPECT_TRUE(variant_match(
+      one, [](const One&) { return true; }, [](const Two&) { return false; }));
+  EXPECT_TRUE(variant_match(
+      two, [](const One&) { return false; }, [](const Two&) { return true; }));
+
+  auto toString = [](const auto& variant) {
+    return variant_match(
+        variant, [](const auto& value) { return value.toString(); });
+  };
+  EXPECT_EQ(toString(one), "One");
+  EXPECT_EQ(toString(two), "Two");
+}
+
+TEST(Overload, DiscriminatedPtr) {
+  using V = DiscriminatedPtr<One, Two>;
+  One one_obj;
+  Two two_obj;
+  V one_ptr(&one_obj);
+  V two_ptr(&two_obj);
+
+  EXPECT_TRUE(variant_match(
+      one_ptr,
+      [](const One*) { return true; },
+      [](const Two*) { return false; }));
+  EXPECT_TRUE(variant_match(
+      two_ptr,
+      [](const One*) { return false; },
+      [](const Two*) { return true; }));
+
+  auto toString = [](const auto& variant) {
+    return variant_match(
+        variant, [](const auto* value) { return value->toString(); });
+  };
+  EXPECT_EQ(toString(one_ptr), "One");
+  EXPECT_EQ(toString(two_ptr), "Two");
+}
+
+TEST(Overload, Pattern) {
+  OneOrTwo one(One{});
+  OneOrTwo two(Two{});
+
+  auto is_one_overload = overload(
+      [](const One&) { return true; }, [](const Two&) { return false; });
+  EXPECT_TRUE(boost::apply_visitor(is_one_overload, one));
+  EXPECT_TRUE(variant_match(one, is_one_overload));
+  EXPECT_FALSE(variant_match(two, is_one_overload));
+
+  auto is_two_overload = overload(
+      [](const One&) { return false; }, [](const Two&) { return true; });
+  EXPECT_TRUE(boost::apply_visitor(is_two_overload, two));
+  EXPECT_FALSE(variant_match(one, is_two_overload));
+  EXPECT_TRUE(variant_match(two, is_two_overload));
+
+  auto is_one_copy = overload(is_one_overload);
+  auto is_one_const_copy =
+      overload(static_cast<const decltype(is_one_overload)&>(is_one_overload));
+  EXPECT_TRUE(variant_match(one, is_one_copy));
+  EXPECT_TRUE(variant_match(one, is_one_const_copy));
+  EXPECT_FALSE(variant_match(two, is_one_copy));
+  EXPECT_FALSE(variant_match(two, is_one_const_copy));
+}
+}
+} // folly::test