From 7d06adb42b4cfb52384c3e0c7a5e3a2ffe2a801c Mon Sep 17 00:00:00 2001 From: Pavel Aslanov Date: Wed, 26 Apr 2017 14:15:50 -0700 Subject: [PATCH] folly::overload and folly::variant_match 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 | 21 ++++++ folly/Makefile.am | 1 + folly/Overload.h | 79 +++++++++++++++++++++ folly/test/DiscriminatedPtrTest.cpp | 3 + folly/test/OverloadTest.cpp | 102 ++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 folly/Overload.h create mode 100644 folly/test/OverloadTest.cpp diff --git a/folly/DiscriminatedPtr.h b/folly/DiscriminatedPtr.h index 7fda96b9..0d485f50 100644 --- a/folly/DiscriminatedPtr.h +++ b/folly/DiscriminatedPtr.h @@ -215,4 +215,25 @@ class DiscriminatedPtr { uintptr_t data_; }; +template +decltype(auto) apply_visitor( + Visitor&& visitor, + const DiscriminatedPtr& variant) { + return variant.apply(std::forward(visitor)); +} + +template +decltype(auto) apply_visitor( + Visitor&& visitor, + DiscriminatedPtr& variant) { + return variant.apply(std::forward(visitor)); +} + +template +decltype(auto) apply_visitor( + Visitor&& visitor, + DiscriminatedPtr&& variant) { + return variant.apply(std::forward(visitor)); +} + } // namespace folly diff --git a/folly/Makefile.am b/folly/Makefile.am index 692a719c..68543e5b 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -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 index 00000000..de1f5354 --- /dev/null +++ b/folly/Overload.h @@ -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 +#include + +/** + * folly implementation of `std::overload` like functionality + * + * Example: + * struct One {}; + * struct Two {}; + * boost::variant value; + * + * variant_match(value, + * [] (const One& one) { ... }, + * [] (const Two& two) { ... }); + */ + +namespace folly { + +namespace details { +template +struct Overload; + +template +struct Overload : Overload, Case { + Overload(Case c, Cases... cs) + : Overload(std::move(cs)...), Case(std::move(c)) {} + + using Case::operator(); + using Overload::operator(); +}; + +template +struct Overload : Case { + explicit Overload(Case c) : Case(std::move(c)) {} + + using Case::operator(); +}; +} // details + +/* + * Combine multiple `Cases` in one function object + */ +template +decltype(auto) overload(Cases&&... cases) { + return details::Overload::type...>{ + std::forward(cases)...}; +} + +/* + * Match `Variant` with one of the `Cases` + * + * Note: you can also use `[] (const auto&) {...}` as default case + * + */ +template +decltype(auto) variant_match(Variant&& variant, Cases&&... cases) { + return apply_visitor( + overload(std::forward(cases)...), std::forward(variant)); +} + +} // folly diff --git a/folly/test/DiscriminatedPtrTest.cpp b/folly/test/DiscriminatedPtrTest.cpp index fafd6fa9..f4d4ca70 100644 --- a/folly/test/DiscriminatedPtrTest.cpp +++ b/folly/test/DiscriminatedPtrTest.cpp @@ -86,6 +86,9 @@ TEST(DiscriminatedPtr, Apply) { p.set(&foo); EXPECT_EQ("Foo", p.apply(Visitor())); EXPECT_EQ("const Foo", static_cast(p).apply(Visitor())); + EXPECT_EQ("Foo", apply_visitor(Visitor(), p)); + EXPECT_EQ("const Foo", apply_visitor(Visitor(), static_cast(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 index 00000000..697b2dec --- /dev/null +++ b/folly/test/OverloadTest.cpp @@ -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 +#include +#include +#include + +namespace folly { +namespace test { + +struct One { + std::string toString() const { + return "One"; + } +}; +struct Two { + std::string toString() const { + return "Two"; + } +}; +using OneOrTwo = boost::variant; + +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 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(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 -- 2.34.1