From d5986bf055513d4ca02b27234afff6a841f9ba00 Mon Sep 17 00:00:00 2001 From: Philipp Unterbrunner Date: Mon, 8 May 2017 18:37:28 -0700 Subject: [PATCH] back_emplace_iterator and related classes and utility functions Summary: C++ up to and including C++17 lacks an alternative to std::back_inserter() that uses emplace_back() instead of push_back(). This causes unnecessary temporary objects in some cases, when using std::back_inserter() together with STL functions such as std::copy() or std::transform(). The same holds for std::front_inserter() and std::inserter(). This diff introduces folly::back_emplacer(), folly::front_emplacer(), folly::emplacer(), and related iterator classes, which call emplace_back(), emplace_front(), and emplace() respectively, with perfect forwarding of any arguments to the output iterator's operator=. Includes support for variadic emplacement / multi-argument constructors through a utility function folly::make_emplace_args() which packs its arguments into a special tuple for use with operator=. Reviewed By: ericniebler Differential Revision: D4897174 fbshipit-source-id: c85c30c457e0c946938051819baa662d1a0b8ca1 --- folly/Iterator.h | 463 ++++++++++++++++++++++++++++++++++ folly/Makefile.am | 1 + folly/test/IteratorTest.cpp | 487 ++++++++++++++++++++++++++++++++++++ folly/test/Makefile.am | 4 + 4 files changed, 955 insertions(+) create mode 100644 folly/Iterator.h create mode 100644 folly/test/IteratorTest.cpp diff --git a/folly/Iterator.h b/folly/Iterator.h new file mode 100644 index 00000000..54d1dbae --- /dev/null +++ b/folly/Iterator.h @@ -0,0 +1,463 @@ +/* + * 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 +#include +#include +#include +#include + +#include + +namespace folly { + +/** + * Argument tuple for variadic emplace/constructor calls. Stores arguments by + * (decayed) value. Restores original argument types with reference qualifiers + * and adornments at unpack time to emulate perfect forwarding. + * + * Uses inheritance instead of a type alias to std::tuple so that emplace + * iterators with implicit unpacking disabled can distinguish between + * emplace_args and std::tuple parameters. + * + * @seealso folly::make_emplace_args + * @seealso folly::get_emplace_arg + */ +template +struct emplace_args : public std::tuple...> { + using storage_type = std::tuple...>; + using storage_type::storage_type; +}; + +/** + * Pack arguments in a tuple for assignment to a folly::emplace_iterator, + * folly::front_emplace_iterator, or folly::back_emplace_iterator. The + * iterator's operator= will unpack the tuple and pass the unpacked arguments + * to the container's emplace function, which in turn forwards the arguments to + * the (multi-argument) constructor of the target class. + * + * Argument tuples generated with folly::make_emplace_args will be unpacked + * before being passed to the container's emplace function, even for iterators + * where implicit_unpack is set to false (so they will not implicitly unpack + * std::pair or std::tuple arguments to operator=). + * + * Arguments are copied (lvalues) or moved (rvalues). To avoid copies and moves, + * wrap references using std::ref(), std::cref(), and folly::rref(). Beware of + * dangling references, especially references to temporary objects created with + * folly::rref(). + * + * Note that an argument pack created with folly::make_emplace_args is different + * from an argument pack created with std::make_pair or std::make_tuple. + * Specifically, passing a std::pair&& or std::tuple&& to an emplace iterator's + * operator= will pass rvalue references to all fields of that tuple to the + * container's emplace function, while passing an emplace_args&& to operator= + * will cast those field references to the exact argument types as passed to + * folly::make_emplace_args previously. If all arguments have been wrapped by + * std::reference_wrappers or folly::rvalue_reference_wrappers, the result will + * be the same as if the container's emplace function had been called directly + * (perfect forwarding), with no temporary copies of the arguments. + * + * @seealso folly::rref + * + * @example + * class Widget { Widget(int, int); }; + * std::vector makeWidgets(const std::vector& in) { + * std::vector out; + * std::transform( + * in.begin(), + * in.end(), + * folly::back_emplacer(out), + * [](int i) { return folly::make_emplace_args(i, i); }); + * return out; + * } + */ +template +emplace_args make_emplace_args(Args&&... args) noexcept( + noexcept(emplace_args(std::forward(args)...))) { + return emplace_args(std::forward(args)...); +} + +namespace detail { +template +decltype(auto) unwrap_emplace_arg(Arg&& arg) noexcept { + return std::forward(arg); +} +template +decltype(auto) unwrap_emplace_arg(std::reference_wrapper arg) noexcept { + return arg.get(); +} +template +decltype(auto) unwrap_emplace_arg( + folly::rvalue_reference_wrapper arg) noexcept { + return std::move(arg).get(); +} +} + +/** + * Getter function for unpacking a single emplace argument. + * + * Calling get_emplace_arg on an emplace_args rvalue reference results in + * perfect forwarding of the original input types. A special case are + * std::reference_wrapper and folly::rvalue_reference_wrapper objects within + * folly::emplace_args. These are also unwrapped so that the bare reference is + * returned. + * + * std::get is not a customization point in the standard library, so the + * cleanest solution was to define our own getter function. + */ +template +decltype(auto) get_emplace_arg(emplace_args&& args) noexcept { + using Out = std::tuple; + return detail::unwrap_emplace_arg( + std::forward>(std::get(args))); +} +template +decltype(auto) get_emplace_arg(emplace_args& args) noexcept { + return detail::unwrap_emplace_arg(std::get(args)); +} +template +decltype(auto) get_emplace_arg(const emplace_args& args) noexcept { + return detail::unwrap_emplace_arg(std::get(args)); +} +template +decltype(auto) get_emplace_arg(Args&& args) noexcept { + return std::get(std::move(args)); +} +template +decltype(auto) get_emplace_arg(Args& args) noexcept { + return std::get(args); +} +template +decltype(auto) get_emplace_arg(const Args& args) noexcept { + return std::get(args); +} + +namespace detail { +/** + * Common typedefs and methods for folly::emplace_iterator, + * folly::front_emplace_iterator, and folly::back_emplace_iterator. Implements + * everything except the actual emplace function call. + */ +template +class emplace_iterator_base; + +/** + * Partial specialization of emplace_iterator_base with implicit unpacking + * disabled. + */ +template +class emplace_iterator_base { + public: + // Iterator traits. + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + using container_type = Container; + + explicit emplace_iterator_base(Container& container) + : container(std::addressof(container)) {} + + /** + * Canonical output operator. Forwards single argument straight to container's + * emplace function. + */ + template + Derived& operator=(T&& arg) { + return static_cast(this)->emplace(std::forward(arg)); + } + + /** + * Special output operator for packed arguments. Unpacks args and performs + * variadic call to container's emplace function. + */ + template + Derived& operator=(emplace_args& args) { + return unpackAndEmplace(args, std::index_sequence_for{}); + } + template + Derived& operator=(const emplace_args& args) { + return unpackAndEmplace(args, std::index_sequence_for{}); + } + template + Derived& operator=(emplace_args&& args) { + return unpackAndEmplace( + std::move(args), std::index_sequence_for{}); + } + + // No-ops. + Derived& operator*() { + return static_cast(*this); + } + Derived& operator++() { + return static_cast(*this); + } + Derived& operator++(int) { + return static_cast(*this); + } + + // We need all of these explicit defaults because the custom operator= + // overloads disable implicit generation of these functions. + emplace_iterator_base(const emplace_iterator_base&) = default; + emplace_iterator_base(emplace_iterator_base&&) noexcept = default; + emplace_iterator_base& operator=(emplace_iterator_base&) = default; + emplace_iterator_base& operator=(const emplace_iterator_base&) = default; + emplace_iterator_base& operator=(emplace_iterator_base&&) noexcept = default; + + protected: + using Class = emplace_iterator_base; + + template + Derived& unpackAndEmplace(Args& args, std::index_sequence) { + return static_cast(this)->emplace(get_emplace_arg(args)...); + } + template + Derived& unpackAndEmplace(const Args& args, std::index_sequence) { + return static_cast(this)->emplace(get_emplace_arg(args)...); + } + template + Derived& unpackAndEmplace(Args&& args, std::index_sequence) { + return static_cast(this)->emplace( + get_emplace_arg(std::move(args))...); + } + + Container* container; +}; + +/** + * Partial specialization of emplace_iterator_base with implicit unpacking + * enabled. + * + * Uses inheritance rather than SFINAE. operator= requires a single argument, + * which makes it impossible to use std::enable_if or similar. + */ +template +class emplace_iterator_base + : public emplace_iterator_base { + public: + using emplace_iterator_base::emplace_iterator_base; + using emplace_iterator_base::operator=; + + /** + * Special output operator for arguments packed into a std::pair. Unpacks + * the pair and performs variadic call to container's emplace function. + */ + template + Derived& operator=(std::pair& args) { + return this->unpackAndEmplace(args, std::index_sequence_for{}); + } + template + Derived& operator=(const std::pair& args) { + return this->unpackAndEmplace(args, std::index_sequence_for{}); + } + template + Derived& operator=(std::pair&& args) { + return this->unpackAndEmplace( + std::move(args), std::index_sequence_for{}); + } + + /** + * Special output operator for arguments packed into a std::tuple. Unpacks + * the tuple and performs variadic call to container's emplace function. + */ + template + Derived& operator=(std::tuple& args) { + return this->unpackAndEmplace(args, std::index_sequence_for{}); + } + template + Derived& operator=(const std::tuple& args) { + return this->unpackAndEmplace(args, std::index_sequence_for{}); + } + template + Derived& operator=(std::tuple&& args) { + return this->unpackAndEmplace( + std::move(args), std::index_sequence_for{}); + } + + // We need all of these explicit defaults because the custom operator= + // overloads disable implicit generation of these functions. + emplace_iterator_base(const emplace_iterator_base&) = default; + emplace_iterator_base(emplace_iterator_base&&) noexcept = default; + emplace_iterator_base& operator=(emplace_iterator_base&) = default; + emplace_iterator_base& operator=(const emplace_iterator_base&) = default; + emplace_iterator_base& operator=(emplace_iterator_base&&) noexcept = default; +}; +} // folly::detail + +/** + * Behaves just like std::insert_iterator except that it calls emplace() + * instead of insert(). Uses perfect forwarding. + */ +template +class emplace_iterator : public detail::emplace_iterator_base< + emplace_iterator, + Container, + implicit_unpack> { + private: + using Base = detail::emplace_iterator_base< + emplace_iterator, + Container, + implicit_unpack>; + + public: + emplace_iterator(Container& container, typename Container::iterator i) + : Base(container), iter(std::move(i)) {} + + using Base::operator=; + + // We need all of these explicit defaults because the custom operator= + // overloads disable implicit generation of these functions. + emplace_iterator(const emplace_iterator&) = default; + emplace_iterator(emplace_iterator&&) noexcept = default; + emplace_iterator& operator=(emplace_iterator&) = default; + emplace_iterator& operator=(const emplace_iterator&) = default; + emplace_iterator& operator=(emplace_iterator&&) noexcept = default; + + protected: + typename Container::iterator iter; + + private: + friend typename Base::Class; + template + emplace_iterator& emplace(Args&&... args) { + iter = this->container->emplace(iter, std::forward(args)...); + ++iter; + return *this; + } +}; + +/** + * Behaves just like std::front_insert_iterator except that it calls + * emplace_front() instead of insert_front(). Uses perfect forwarding. + */ +template +class front_emplace_iterator : public detail::emplace_iterator_base< + front_emplace_iterator, + Container, + implicit_unpack> { + private: + using Base = detail::emplace_iterator_base< + front_emplace_iterator, + Container, + implicit_unpack>; + + public: + using Base::Base; + using Base::operator=; + + // We need all of these explicit defaults because the custom operator= + // overloads disable implicit generation of these functions. + front_emplace_iterator(const front_emplace_iterator&) = default; + front_emplace_iterator(front_emplace_iterator&&) noexcept = default; + front_emplace_iterator& operator=(front_emplace_iterator&) = default; + front_emplace_iterator& operator=(const front_emplace_iterator&) = default; + front_emplace_iterator& operator=(front_emplace_iterator&&) noexcept = + default; + + private: + friend typename Base::Class; + template + front_emplace_iterator& emplace(Args&&... args) { + this->container->emplace_front(std::forward(args)...); + return *this; + } +}; + +/** + * Behaves just like std::back_insert_iterator except that it calls + * emplace_back() instead of insert_back(). Uses perfect forwarding. + */ +template +class back_emplace_iterator : public detail::emplace_iterator_base< + back_emplace_iterator, + Container, + implicit_unpack> { + private: + using Base = detail::emplace_iterator_base< + back_emplace_iterator, + Container, + implicit_unpack>; + + public: + using Base::Base; + using Base::operator=; + + // We need all of these explicit defaults because the custom operator= + // overloads disable implicit generation of these functions. + back_emplace_iterator(const back_emplace_iterator&) = default; + back_emplace_iterator(back_emplace_iterator&&) noexcept = default; + back_emplace_iterator& operator=(back_emplace_iterator&) = default; + back_emplace_iterator& operator=(const back_emplace_iterator&) = default; + back_emplace_iterator& operator=(back_emplace_iterator&&) noexcept = default; + + private: + friend typename Base::Class; + template + back_emplace_iterator& emplace(Args&&... args) { + this->container->emplace_back(std::forward(args)...); + return *this; + } +}; + +/** + * Convenience function to construct a folly::emplace_iterator, analogous to + * std::inserter(). + * + * Setting implicit_unpack to false will disable implicit unpacking of + * single std::pair and std::tuple arguments to the iterator's operator=. That + * may be desirable in case of constructors that expect a std::pair or + * std::tuple argument. + */ +template +emplace_iterator emplacer( + Container& c, + typename Container::iterator i) { + return emplace_iterator(c, std::move(i)); +} + +/** + * Convenience function to construct a folly::front_emplace_iterator, analogous + * to std::front_inserter(). + * + * Setting implicit_unpack to false will disable implicit unpacking of + * single std::pair and std::tuple arguments to the iterator's operator=. That + * may be desirable in case of constructors that expect a std::pair or + * std::tuple argument. + */ +template +front_emplace_iterator front_emplacer( + Container& c) { + return front_emplace_iterator(c); +} + +/** + * Convenience function to construct a folly::back_emplace_iterator, analogous + * to std::back_inserter(). + * + * Setting implicit_unpack to false will disable implicit unpacking of + * single std::pair and std::tuple arguments to the iterator's operator=. That + * may be desirable in case of constructors that expect a std::pair or + * std::tuple argument. + */ +template +back_emplace_iterator back_emplacer(Container& c) { + return back_emplace_iterator(c); +} +} diff --git a/folly/Makefile.am b/folly/Makefile.am index f5f5a8fc..01045105 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -272,6 +272,7 @@ nobase_follyinclude_HEADERS = \ io/async/test/TimeUtil.h \ io/async/test/UndelayedDestruction.h \ io/async/test/Util.h \ + Iterator.h \ json.h \ Lazy.h \ LifoSem.h \ diff --git a/folly/test/IteratorTest.cpp b/folly/test/IteratorTest.cpp new file mode 100644 index 00000000..815e9ebd --- /dev/null +++ b/folly/test/IteratorTest.cpp @@ -0,0 +1,487 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include + +namespace { +/** + * Container type used for unit tests. + */ +template +using Container = std::deque; + +// Constructor and assignment operator call counters for struct Object. +std::size_t gDefaultCtrCnt; +std::size_t gCopyCtrCnt; +std::size_t gMoveCtrCnt; +std::size_t gExplicitCtrCnt; +std::size_t gMultiargCtrCnt; +std::size_t gCopyOpCnt; +std::size_t gMoveOpCnt; +std::size_t gConvertOpCnt; + +/** + * Class that increases various counters to keep track of how objects have + * been constructed or assigned to, to verify iterator behavior. + */ +struct Object { + Object() { + ++gDefaultCtrCnt; + } + Object(const Object&) { + ++gCopyCtrCnt; + } + Object(Object&&) noexcept { + ++gMoveCtrCnt; + } + explicit Object(int) { + ++gExplicitCtrCnt; + } + explicit Object(int, int) { + ++gMultiargCtrCnt; + } + Object& operator=(const Object&) { + ++gCopyOpCnt; + return *this; + } + Object& operator=(Object&&) noexcept { + ++gMoveOpCnt; + return *this; + } + Object& operator=(int) noexcept { + ++gConvertOpCnt; + return *this; + } +}; + +/** + * Reset all call counters to 0. + */ +void init_counters() { + gDefaultCtrCnt = gCopyCtrCnt = gMoveCtrCnt = gExplicitCtrCnt = + gMultiargCtrCnt = gCopyOpCnt = gMoveOpCnt = gConvertOpCnt = 0; +} + +/** + * Test for iterator copy and move. + */ +template +void copy_and_move_test(Container& q, Iterator it) { + assert(q.empty()); + const auto it2(it); // copy construct + it = it2; // copy assign from const + it = it; // self assign + auto it3(std::move(it)); // move construct + it = std::move(it3); // move assign + // Make sure iterator still works. + it = 4711; // emplace + EXPECT_EQ(q, Container{4711}); +} + +/** + * Test for emplacement with perfect forwarding. + */ +template +void emplace_test(Container& q, Iterator it) { + using folly::make_emplace_args; + assert(q.empty()); + init_counters(); + it = Object{}; // default construct + move construct + Object obj; // default construct + it = obj; // copy construct + it = std::move(obj); // move construct + const Object obj2; // default construct + it = obj2; // copy construct from const + it = std::move(obj2); // copy construct (const defeats move) + it = 0; // explicit construct + it = make_emplace_args(0, 0); // explicit multiarg construct + it = std::make_pair(0, 0); // implicit multiarg construct + it = std::make_tuple(0, 0); // implicit multiarg construct + auto args = make_emplace_args(Object{}); // default construct + move construct + it = args; // copy construct + it = const_cast(args); // copy construct from const + it = std::move(args); // move construct + auto args2 = std::make_tuple(Object{}); // default construct + move construct + it = args2; // (implicit multiarg) copy construct + it = std::move(args2); // (implicit multiarg) move construct + auto args3 = std::make_pair(0, 0); + it = args3; // implicit multiarg construct + it = std::move(args3); // implicit multiarg construct + ASSERT_EQ(q.size(), 16); + EXPECT_EQ(gDefaultCtrCnt, 5); + EXPECT_EQ(gCopyCtrCnt, 6); + EXPECT_EQ(gMoveCtrCnt, 6); + EXPECT_EQ(gExplicitCtrCnt, 1); + EXPECT_EQ(gMultiargCtrCnt, 5); + EXPECT_EQ(gCopyOpCnt, 0); + EXPECT_EQ(gMoveOpCnt, 0); + EXPECT_EQ(gConvertOpCnt, 0); +} +} + +using namespace folly; + +/** + * Basic tests for folly::emplace_iterator. + */ +TEST(EmplaceIterator, EmplacerTest) { + { + Container q; + copy_and_move_test(q, emplacer(q, q.begin())); + } + { + Container q; + emplace_test(q, emplacer(q, q.begin())); + } + { + Container q; + auto it = emplacer(q, q.begin()); + it = 0; + it = 1; + it = 2; + it = emplacer(q, q.begin()); + it = 3; + it = 4; + EXPECT_EQ(q, Container({3, 4, 0, 1, 2})); + } +} + +/** + * Basic tests for folly::front_emplace_iterator. + */ +TEST(EmplaceIterator, FrontEmplacerTest) { + { + Container q; + copy_and_move_test(q, front_emplacer(q)); + } + { + Container q; + emplace_test(q, front_emplacer(q)); + } + { + Container q; + auto it = front_emplacer(q); + it = 0; + it = 1; + it = 2; + it = front_emplacer(q); + it = 3; + it = 4; + EXPECT_EQ(q, Container({4, 3, 2, 1, 0})); + } +} + +/** + * Basic tests for folly::back_emplace_iterator. + */ +TEST(EmplaceIterator, BackEmplacerTest) { + { + Container q; + copy_and_move_test(q, back_emplacer(q)); + } + { + Container q; + emplace_test(q, back_emplacer(q)); + } + { + Container q; + auto it = back_emplacer(q); + it = 0; + it = 1; + it = 2; + it = back_emplacer(q); + it = 3; + it = 4; + EXPECT_EQ(q, Container({0, 1, 2, 3, 4})); + } +} + +/** + * Test std::copy() with explicit conversion. This would not compile with a + * std::back_insert_iterator, because the constructor of Object that takes a + * single int is explicit. + */ +TEST(EmplaceIterator, Copy) { + init_counters(); + Container in({0, 1, 2}); + Container out; + std::copy(in.begin(), in.end(), back_emplacer(out)); + EXPECT_EQ(3, out.size()); + EXPECT_EQ(gDefaultCtrCnt, 0); + EXPECT_EQ(gCopyCtrCnt, 0); + EXPECT_EQ(gMoveCtrCnt, 0); + EXPECT_EQ(gExplicitCtrCnt, 3); + EXPECT_EQ(gMultiargCtrCnt, 0); + EXPECT_EQ(gCopyOpCnt, 0); + EXPECT_EQ(gMoveOpCnt, 0); + EXPECT_EQ(gConvertOpCnt, 0); +} + +/** + * Test std::transform() with multi-argument constructors. This would require + * a temporary Object with std::back_insert_iterator. + */ +TEST(EmplaceIterator, Transform) { + init_counters(); + Container in({0, 1, 2}); + Container out; + std::transform(in.begin(), in.end(), back_emplacer(out), [](int i) { + return make_emplace_args(i, i); + }); + EXPECT_EQ(3, out.size()); + EXPECT_EQ(gDefaultCtrCnt, 0); + EXPECT_EQ(gCopyCtrCnt, 0); + EXPECT_EQ(gMoveCtrCnt, 0); + EXPECT_EQ(gExplicitCtrCnt, 0); + EXPECT_EQ(gMultiargCtrCnt, 3); + EXPECT_EQ(gCopyOpCnt, 0); + EXPECT_EQ(gMoveOpCnt, 0); + EXPECT_EQ(gConvertOpCnt, 0); +} + +/** + * Test multi-argument store and forward. + */ +TEST(EmplaceIterator, EmplaceArgs) { + Object o1; + const Object o2; + Object& o3 = o1; + const Object& o4 = o3; + Object o5; + + { + // Test copy construction. + auto args = make_emplace_args(0, o1, o2, o3, o4, Object{}, std::cref(o2)); + init_counters(); + auto args2 = args; + EXPECT_EQ(gDefaultCtrCnt, 0); + EXPECT_EQ(gCopyCtrCnt, 5); + EXPECT_EQ(gMoveCtrCnt, 0); + EXPECT_EQ(gExplicitCtrCnt, 0); + EXPECT_EQ(gMultiargCtrCnt, 0); + EXPECT_EQ(gCopyOpCnt, 0); + EXPECT_EQ(gMoveOpCnt, 0); + EXPECT_EQ(gConvertOpCnt, 0); + + // Test copy assignment. + init_counters(); + args = args2; + EXPECT_EQ(gDefaultCtrCnt, 0); + EXPECT_EQ(gCopyCtrCnt, 0); + EXPECT_EQ(gMoveCtrCnt, 0); + EXPECT_EQ(gExplicitCtrCnt, 0); + EXPECT_EQ(gMultiargCtrCnt, 0); + EXPECT_EQ(gCopyOpCnt, 5); + EXPECT_EQ(gMoveOpCnt, 0); + EXPECT_EQ(gConvertOpCnt, 0); + } + + { + // Test RVO. + init_counters(); + auto args = make_emplace_args( + 0, o1, o2, o3, o4, Object{}, std::cref(o2), rref(std::move(o5))); + EXPECT_EQ(gDefaultCtrCnt, 1); + EXPECT_EQ(gCopyCtrCnt, 4); + EXPECT_EQ(gMoveCtrCnt, 1); + EXPECT_EQ(gExplicitCtrCnt, 0); + EXPECT_EQ(gMultiargCtrCnt, 0); + EXPECT_EQ(gCopyOpCnt, 0); + EXPECT_EQ(gMoveOpCnt, 0); + EXPECT_EQ(gConvertOpCnt, 0); + + // Test move construction. + init_counters(); + auto args2 = std::move(args); + EXPECT_EQ(gDefaultCtrCnt, 0); + EXPECT_EQ(gCopyCtrCnt, 0); + EXPECT_EQ(gMoveCtrCnt, 5); + EXPECT_EQ(gExplicitCtrCnt, 0); + EXPECT_EQ(gMultiargCtrCnt, 0); + EXPECT_EQ(gCopyOpCnt, 0); + EXPECT_EQ(gMoveOpCnt, 0); + EXPECT_EQ(gConvertOpCnt, 0); + + // Test move assignment. + init_counters(); + args = std::move(args2); + EXPECT_EQ(gDefaultCtrCnt, 0); + EXPECT_EQ(gCopyCtrCnt, 0); + EXPECT_EQ(gMoveCtrCnt, 0); + EXPECT_EQ(gExplicitCtrCnt, 0); + EXPECT_EQ(gMultiargCtrCnt, 0); + EXPECT_EQ(gCopyOpCnt, 0); + EXPECT_EQ(gMoveOpCnt, 5); + EXPECT_EQ(gConvertOpCnt, 0); + + // Make sure arguments are stored correctly. lvalues by reference, rvalues + // by (moved) copy. Rvalues cannot be stored by reference because they may + // refer to an expired temporary by the time they are accessed. + static_assert( + std::is_same< + int, + std::tuple_element_t<0, decltype(args)::storage_type>>::value, + ""); + static_assert( + std::is_same< + Object, + std::tuple_element_t<1, decltype(args)::storage_type>>::value, + ""); + static_assert( + std::is_same< + Object, + std::tuple_element_t<2, decltype(args)::storage_type>>::value, + ""); + static_assert( + std::is_same< + Object, + std::tuple_element_t<3, decltype(args)::storage_type>>::value, + ""); + static_assert( + std::is_same< + Object, + std::tuple_element_t<4, decltype(args)::storage_type>>::value, + ""); + static_assert( + std::is_same< + Object, + std::tuple_element_t<5, decltype(args)::storage_type>>::value, + ""); + static_assert( + std::is_same< + std::reference_wrapper, + std::tuple_element_t<6, decltype(args)::storage_type>>::value, + ""); + static_assert( + std::is_same< + rvalue_reference_wrapper, + std::tuple_element_t<7, decltype(args)::storage_type>>::value, + ""); + + // Check whether args.get() restores the original argument type for + // rvalue references to emplace_args. + static_assert( + std::is_same(std::move(args)))>:: + value, + ""); + static_assert( + std::is_same(std::move(args)))>:: + value, + ""); + static_assert( + std::is_same< + const Object&, + decltype(get_emplace_arg<2>(std::move(args)))>::value, + ""); + static_assert( + std::is_same(std::move(args)))>:: + value, + ""); + static_assert( + std::is_same< + const Object&, + decltype(get_emplace_arg<4>(std::move(args)))>::value, + ""); + static_assert( + std::is_same(std::move(args)))>:: + value, + ""); + static_assert( + std::is_same< + const Object&, + decltype(get_emplace_arg<6>(std::move(args)))>::value, + ""); + static_assert( + std::is_same(std::move(args)))>:: + value, + ""); + + // lvalue references to emplace_args should behave mostly like std::tuples. + // Note that get_emplace_arg<7>(args) does not compile, because + // folly::rvalue_reference_wrappers can only be unwrapped through an rvalue + // reference. + static_assert( + std::is_same(args))>::value, ""); + static_assert( + std::is_same(args))>::value, ""); + static_assert( + std::is_same(args))>::value, ""); + static_assert( + std::is_same(args))>::value, ""); + static_assert( + std::is_same(args))>::value, ""); + static_assert( + std::is_same(args))>::value, ""); + static_assert( + std::is_same(args))>::value, + ""); + } +} + +/** + * Test implicit unpacking. + */ +TEST(EmplaceIterator, ImplicitUnpack) { + static std::size_t multiCtrCnt; + static std::size_t pairCtrCnt; + static std::size_t tupleCtrCnt; + + struct Object2 { + Object2(int, int) { + ++multiCtrCnt; + } + explicit Object2(const std::pair&) { + ++pairCtrCnt; + } + explicit Object2(const std::tuple&) { + ++tupleCtrCnt; + } + }; + + auto test = [](auto&& it, bool expectUnpack) { + multiCtrCnt = pairCtrCnt = tupleCtrCnt = 0; + it = std::make_pair(0, 0); + it = std::make_tuple(0, 0); + if (expectUnpack) { + EXPECT_EQ(multiCtrCnt, 2); + EXPECT_EQ(pairCtrCnt, 0); + EXPECT_EQ(tupleCtrCnt, 0); + } else { + EXPECT_EQ(multiCtrCnt, 0); + EXPECT_EQ(pairCtrCnt, 1); + EXPECT_EQ(tupleCtrCnt, 1); + } + }; + + Container q; + + test(emplacer(q, q.begin()), true); + test(emplacer(q, q.begin()), false); + test(front_emplacer(q), true); + test(front_emplacer(q), false); + test(back_emplacer(q), true); + test(back_emplacer(q), false); +} diff --git a/folly/test/Makefile.am b/folly/test/Makefile.am index 461596cb..d3d12b51 100644 --- a/folly/test/Makefile.am +++ b/folly/test/Makefile.am @@ -332,4 +332,8 @@ utility_test_SOURCES = UtilityTest.cpp utility_test_LDADD = libfollytestmain.la TESTS += utility_test +iterator_test_SOURCES = IteratorTest.cpp +iterator_test_LDADD = libfollytestmain.la +TESTS += iterator_test + check_PROGRAMS += $(TESTS) -- 2.34.1