From: Sven Over Date: Tue, 16 Aug 2016 15:42:54 +0000 (-0700) Subject: Introducing folly::partial X-Git-Tag: v2016.08.22.00~32 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=6a287baae2b85280c1fc9faa42802f1f8596e569 Introducing folly::partial Summary: This diff adds folly::partial, a function to partially apply a set of zero or more arguments to a callable. It is similar to Python's `functools.partial`. `folly::partial` takes a callable object and additional arguments and returns a callable with those additional arguments bound to it. When the returned callable is invoked with additional arguments, those are appended to the set of arguments that were passed to `folly::partial`. It is similar to `std::bind`, but more simple as it does not support reordering of parameters, but also does not require you to know how many arguments will be eventually passed to the callable. Also, `std::bind` does not support move-only types being passed by-value. `folly::partial` does: void someFunc(std::unique_ptr, int); auto p = folly::partial(&someFunc, std::move(foo_unique_ptr)); ... std::move(p)(42); Reviewed By: mhx Differential Revision: D3252539 fbshipit-source-id: ee093771ac732fa70052b9908dcb75e90ba80efe --- diff --git a/folly/Makefile.am b/folly/Makefile.am index 116ef90e..16228fb2 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -253,6 +253,7 @@ nobase_follyinclude_HEADERS = \ Optional.h \ PackedSyncPtr.h \ Padded.h \ + Partial.h \ PicoSpinLock.h \ Portability.h \ portability/Asm.h \ diff --git a/folly/Partial.h b/folly/Partial.h new file mode 100644 index 00000000..f19941fb --- /dev/null +++ b/folly/Partial.h @@ -0,0 +1,89 @@ +/* + * Copyright 2016 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 + +namespace folly { + +template +class Partial { + private: + F f_; + Tuple stored_args_; + + public: + template + Partial(Callable&& callable, Args&&... args) + : f_(std::forward(callable)), + stored_args_(std::forward(args)...) {} + + template + auto operator()(CArgs&&... cargs) & { + return applyTuple( + static_cast(f_), + static_cast(stored_args_), + std::forward_as_tuple(std::forward(cargs)...)); + } + + template + auto operator()(CArgs&&... cargs) const& { + return applyTuple( + static_cast(f_), + static_cast(stored_args_), + std::forward_as_tuple(std::forward(cargs)...)); + } + + template + auto operator()(CArgs&&... cargs) && { + return applyTuple( + static_cast(f_), + static_cast(stored_args_), + std::forward_as_tuple(std::forward(cargs)...)); + } +}; + +/** + * Partially applies arguments to a callable + * + * `partial` takes a callable and zero or more additional arguments and returns + * a callable object, which when called with zero or more arguments, will invoke + * the original callable with the additional arguments passed to `partial`, + * followed by those passed to the call. + * + * E.g. `partial(Foo, 1, 2)(3)` is equivalent to `Foo(1, 2, 3)`. + * + * `partial` can be used to bind a class method to an instance: + * `partial(&Foo::method, foo_pointer)` returns a callable object that can be + * invoked in the same way as `foo_pointer->method`. In case the first + * argument in a call to `partial` is a member pointer, the second argument + * can be a reference, pointer or any object that can be dereferenced to + * an object of type Foo (like `std::shared_ptr` or `std::unique_ptr`). + * + * `partial` is similar to `std::bind`, but you don't have to use placeholders + * to have arguments passed on. Any number of arguments passed to the object + * returned by `partial` when called will be added to those passed to `partial` + * and passed to the original callable. + */ +template +auto partial(F&& f, Args&&... args) -> Partial< + typename std::decay::type, + std::tuple::type...>> { + return {std::forward(f), std::forward(args)...}; +} + +} // namespace folly diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index 24e241f4..d8c5acae 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -301,4 +301,8 @@ apply_tuple_test_SOURCES = ApplyTupleTest.cpp apply_tuple_test_LDADD = libfollytestmain.la TESTS += apply_tuple_test +partial_test_SOURCES = PartialTest.cpp +partial_test_LDADD = libfollytestmain.la +TESTS += partial_test + check_PROGRAMS += $(TESTS) diff --git a/folly/test/PartialTest.cpp b/folly/test/PartialTest.cpp new file mode 100644 index 00000000..025cd25f --- /dev/null +++ b/folly/test/PartialTest.cpp @@ -0,0 +1,133 @@ +/* + * Copyright 2016 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 + +using folly::partial; + +int add3(int x, int y, int z) { + return 100 * x + 10 * y + z; +} + +TEST(Partial, Simple) { + auto p0 = partial(&add3); + EXPECT_EQ(123, p0(1, 2, 3)); + + auto p1 = partial(&add3, 2); + EXPECT_EQ(234, p1(3, 4)); + + auto p2 = partial(&add3, 3, 4); + EXPECT_EQ(345, p2(5)); + + auto p3 = partial(&add3, 4, 5, 6); + EXPECT_EQ(456, p3()); +} + +struct Foo { + int method(int& x, int& y, int& z) { + return 1000 + 100 * x + 10 * y + z; + } + int constMethod(int const& x, int const& y, int const& z) const { + return 2000 + 100 * x + 10 * y + z; + } + int tempMethod(int&& x, int&& y, int&& z) { + return 3000 + 100 * x + 10 * y + z; + } +}; + +TEST(Partial, ReferenceArguments) { + auto p0 = partial(&Foo::method, Foo{}, 2, 3); + int four = 4; + EXPECT_EQ(1234, p0(four)); + + auto const p1 = partial(&Foo::constMethod, Foo{}, 3, 4); + EXPECT_EQ(2345, p1(5)); + + auto p2 = partial(&Foo::tempMethod, Foo{}, 4, 5); + EXPECT_EQ(3456, std::move(p2)(6)); +} + +struct RefQualifiers { + int operator()(int x, int y, int z) & { + return 1000 + 100 * x + 10 * y + z; + } + int operator()(int x, int y, int z) const& { + return 2000 + 100 * x + 10 * y + z; + } + int operator()(int x, int y, int z) && { + return 3000 + 100 * x + 10 * y + z; + } +}; + +TEST(Partial, RefQualifiers) { + auto p = partial(RefQualifiers{}); + auto const& pconst = p; + + EXPECT_EQ(1234, p(2, 3, 4)); + EXPECT_EQ(2345, pconst(3, 4, 5)); + EXPECT_EQ(3456, std::move(p)(4, 5, 6)); +} + +struct RefQualifiers2 { + int operator()(int& x, int const& y, int z) & { + return 1000 + 100 * x + 10 * y + z; + } + int operator()(int const& x, int y, int z) const& { + return 2000 + 100 * x + 10 * y + z; + } + int operator()(int&& x, int const& y, int z) && { + return 3000 + 100 * x + 10 * y + z; + } +}; + +TEST(Partial, RefQualifiers2) { + auto p = partial(RefQualifiers2{}, 9, 8); + auto const& pconst = p; + + EXPECT_EQ(1984, p(4)); + EXPECT_EQ(2985, pconst(5)); + EXPECT_EQ(3986, std::move(p)(6)); +} + +std::unique_ptr calc_uptr(std::unique_ptr x, std::unique_ptr y) { + *x = 100 * *x + 10 * *y; + return x; +} + +TEST(Partial, MoveOnly) { + auto five = std::make_unique(5); + auto six = std::make_unique(6); + + // create a partial object which holds a pointer to the `calc_uptr` function + // and a `unique_ptr` for the first argument + auto p = partial(&calc_uptr, std::move(five)); + + // `five` should be moved out of + EXPECT_FALSE(five); + + // call to the partial object as rvalue, which allows the call to consume + // captured data (here: the `unique_ptr` storing 5), and pass it + // the other `unique_ptr` + auto result = std::move(p)(std::move(six)); + + // ...which now should be moved out of + EXPECT_FALSE(six); + + EXPECT_EQ(560, *result); +}