From: Philipp Unterbrunner Date: Thu, 27 Apr 2017 21:03:31 +0000 (-0700) Subject: folly::rvalue_reference_wrapper for store&forward of rvalue references X-Git-Tag: v2017.05.01.00~3 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=682dfa7172b2e7ec39e2a29583292f7e2e06b63f folly::rvalue_reference_wrapper for store&forward of rvalue references Summary: Class template that wraps a reference to an rvalue. Similar to std::reference_wrapper but with three important differences: 1) folly::rvalue_reference_wrappers can only be moved, not copied; 2) the get() function and the conversion-to-T operator are destructive and not const, they invalidate the wrapper; 3) the constructor-from-T is explicit. These restrictions are designed to make it harder to accidentally create a a dangling rvalue reference, or to use an rvalue reference multiple times. (Using an rvalue reference typically implies invalidation of the target object, such as move-assignment to another object.) Reviewed By: yfeldblum Differential Revision: D4931483 fbshipit-source-id: 68453553bf4656ec41976699669a4491fcab79c9 --- diff --git a/folly/Functional.h b/folly/Functional.h new file mode 100644 index 00000000..05c49a5f --- /dev/null +++ b/folly/Functional.h @@ -0,0 +1,154 @@ +/* + * Copyright 2017-present 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 +#include + +namespace folly { + +/** + * Class template that wraps a reference to an rvalue. Similar to + * std::reference_wrapper but with four important differences: + * + * 1) folly::rvalue_reference_wrappers can only be moved, not copied; + * 2) the get() function and the conversion-to-T operator are destructive and + * not const, they invalidate the wrapper; + * 3) the constructor-from-T is explicit. + * + * These restrictions are designed to make it harder to accidentally create a + * a dangling rvalue reference, or to use an rvalue reference multiple times. + * (Using an rvalue reference typically implies invalidation of the target + * object, such as move-assignment to another object.) + * + * @seealso folly::rref + */ +template +class rvalue_reference_wrapper { + public: + using type = T; + + /** + * Default constructor. Creates an invalid reference. Must be move-assigned + * to in order to be come valid. + */ + rvalue_reference_wrapper() noexcept : ptr_(nullptr) {} + + /** + * Explicit constructor to make it harder to accidentally create a dangling + * reference to a temporary. + */ + explicit rvalue_reference_wrapper(T&& ref) noexcept + : ptr_(std::addressof(ref)) {} + + /** + * No construction from lvalue reference. Use std::move. + */ + explicit rvalue_reference_wrapper(T&) noexcept = delete; + + /** + * Destructive move construction. + */ + rvalue_reference_wrapper(rvalue_reference_wrapper&& other) noexcept + : ptr_(other.ptr_) { + other.ptr_ = nullptr; + } + + /** + * Destructive move assignment. + */ + rvalue_reference_wrapper& operator=( + rvalue_reference_wrapper&& other) noexcept { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + return *this; + } + + /** + * Implicit conversion to raw reference. Destructive. + */ + /* implicit */ operator T &&() && noexcept { + return static_cast(*this).get(); + } + + /** + * Explicit unwrap. Destructive. + */ + T&& get() && noexcept { + assert(valid()); + T& ref = *ptr_; + ptr_ = nullptr; + return static_cast(ref); + } + + /** + * Calls the callable object to whom reference is stored. Only available if + * the wrapped reference points to a callable object. Destructive. + */ + template + decltype(auto) operator()(Args&&... args) && + noexcept(noexcept(std::declval()(std::forward(args)...))) { + return static_cast(*this).get()( + std::forward(args)...); + } + + /** + * Check whether wrapped reference is valid. + */ + bool valid() const noexcept { + return ptr_ != nullptr; + } + + private: + // Disallow copy construction and copy assignment, to make it harder to + // accidentally use an rvalue reference multiple times. + rvalue_reference_wrapper(const rvalue_reference_wrapper&) = delete; + rvalue_reference_wrapper& operator=(const rvalue_reference_wrapper&) = delete; + + T* ptr_; +}; + +/** + * Create a folly::rvalue_reference_wrapper. Analogous to std::ref(). + * + * Warning: folly::rvalue_reference_wrappers are potentially dangerous, because + * they can easily be used to capture references to temporary values. Users must + * ensure that the target object outlives the reference wrapper. + * + * @example + * class Object {}; + * void f(Object&&); + * // BAD + * void g() { + * auto ref = folly::rref(Object{}); // create reference to temporary + * f(std::move(ref)); // pass dangling reference + * } + * // GOOD + * void h() { + * Object o; + * auto ref = folly::rref(std::move(o)); + * f(std::move(ref)); + * } + */ +template +rvalue_reference_wrapper rref(T&& value) noexcept { + return rvalue_reference_wrapper(std::move(value)); +} +template +rvalue_reference_wrapper rref(T&) noexcept = delete; +} diff --git a/folly/Makefile.am b/folly/Makefile.am index 68543e5b..8af497b2 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -394,6 +394,7 @@ nobase_follyinclude_HEADERS = \ Try.h \ Unicode.h \ Function.h \ + Functional.h \ UncaughtExceptions.h \ Unit.h \ Uri.h \ diff --git a/folly/test/FunctionalTest.cpp b/folly/test/FunctionalTest.cpp new file mode 100644 index 00000000..f8a81f1b --- /dev/null +++ b/folly/test/FunctionalTest.cpp @@ -0,0 +1,84 @@ +/* + * 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 + +TEST(RvalueReferenceWrapper, MoveAndConvert) { + using folly::rvalue_reference_wrapper; + + // Destructive moves. + int i1 = 0; + rvalue_reference_wrapper rref1(std::move(i1)); + ASSERT_TRUE(rref1.valid()); + rvalue_reference_wrapper rref0(std::move(rref1)); + ASSERT_TRUE(rref0.valid()); + ASSERT_FALSE(rref1.valid()); + rref1 = std::move(rref0); + ASSERT_FALSE(rref0.valid()); + ASSERT_TRUE(rref1.valid()); + const int& r1 = std::move(rref1); + ASSERT_FALSE(rref1.valid()); + ASSERT_EQ(&r1, &i1); + + // Destructive unwrap to T&&. + int i2 = 0; + rvalue_reference_wrapper rref2(std::move(i2)); + int&& r2 = std::move(rref2); + ASSERT_EQ(&r2, &i2); + + // Destructive unwrap to const T&. + const int i3 = 0; + rvalue_reference_wrapper rref3(std::move(i3)); + const int& r3 = std::move(rref3); + ASSERT_EQ(&r3, &i3); + + // Destructive unwrap to const T&&. + const int i4 = 0; + rvalue_reference_wrapper rref4(std::move(i4)); + const int&& r4 = std::move(rref4); + ASSERT_EQ(&r4, &i4); + + /* + * Things that intentionally do not compile. Copy construction, copy + * assignment, unwrap of lvalue reference to wrapper, const violations. + * + int i5; + const int i6 = 0; + rvalue_reference_wrapper rref5(i5); + rvalue_reference_wrapper rref6(i6); + rref1 = rref5; + int& r5 = rref5; + const int& r6 = rref6; + int i7; + const rvalue_reference_wrapper rref7(std::move(i7)); + int& r7 = std::move(rref7); + */ +} + +TEST(RvalueReferenceWrapper, Call) { + int a = 4711, b, c; + auto callMe = [&](int x, const int& y, int&& z) -> int { + EXPECT_EQ(a, x); + EXPECT_EQ(&b, &y); + EXPECT_EQ(&c, &z); + return a; + }; + int result = folly::rref(std::move(callMe))(a, b, std::move(c)); + EXPECT_EQ(a, result); +} diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index b22aa683..461596cb 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -302,6 +302,11 @@ function_test_SOURCES = \ function_test_LDADD = libfollytestmain.la TESTS += function_test +functional_test_SOURCES = \ + FunctionalTest.cpp +functional_test_LDADD = libfollytestmain.la +TESTS += functional_test + ssl_test_SOURCES = \ ../ssl/test/OpenSSLHashTest.cpp ssl_test_LDADD = libfollytestmain.la -lcrypto