back_emplace_iterator and related classes and utility functions
authorPhilipp Unterbrunner <philippu@fb.com>
Tue, 9 May 2017 01:37:28 +0000 (18:37 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Tue, 9 May 2017 01:51:37 +0000 (18:51 -0700)
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 [new file with mode: 0644]
folly/Makefile.am
folly/test/IteratorTest.cpp [new file with mode: 0644]
folly/test/Makefile.am

diff --git a/folly/Iterator.h b/folly/Iterator.h
new file mode 100644 (file)
index 0000000..54d1dba
--- /dev/null
@@ -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 <functional>
+#include <iterator>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Functional.h>
+
+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 <typename... Args>
+struct emplace_args : public std::tuple<std::decay_t<Args>...> {
+  using storage_type = std::tuple<std::decay_t<Args>...>;
+  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<Widget> makeWidgets(const std::vector<int>& in) {
+ *     std::vector<Widget> out;
+ *     std::transform(
+ *         in.begin(),
+ *         in.end(),
+ *         folly::back_emplacer(out),
+ *         [](int i) { return folly::make_emplace_args(i, i); });
+ *     return out;
+ *   }
+ */
+template <typename... Args>
+emplace_args<Args...> make_emplace_args(Args&&... args) noexcept(
+    noexcept(emplace_args<Args...>(std::forward<Args>(args)...))) {
+  return emplace_args<Args...>(std::forward<Args>(args)...);
+}
+
+namespace detail {
+template <typename Arg>
+decltype(auto) unwrap_emplace_arg(Arg&& arg) noexcept {
+  return std::forward<Arg>(arg);
+}
+template <typename Arg>
+decltype(auto) unwrap_emplace_arg(std::reference_wrapper<Arg> arg) noexcept {
+  return arg.get();
+}
+template <typename Arg>
+decltype(auto) unwrap_emplace_arg(
+    folly::rvalue_reference_wrapper<Arg> 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 <size_t I, typename... Args>
+decltype(auto) get_emplace_arg(emplace_args<Args...>&& args) noexcept {
+  using Out = std::tuple<Args...>;
+  return detail::unwrap_emplace_arg(
+      std::forward<std::tuple_element_t<I, Out>>(std::get<I>(args)));
+}
+template <size_t I, typename... Args>
+decltype(auto) get_emplace_arg(emplace_args<Args...>& args) noexcept {
+  return detail::unwrap_emplace_arg(std::get<I>(args));
+}
+template <size_t I, typename... Args>
+decltype(auto) get_emplace_arg(const emplace_args<Args...>& args) noexcept {
+  return detail::unwrap_emplace_arg(std::get<I>(args));
+}
+template <size_t I, typename Args>
+decltype(auto) get_emplace_arg(Args&& args) noexcept {
+  return std::get<I>(std::move(args));
+}
+template <size_t I, typename Args>
+decltype(auto) get_emplace_arg(Args& args) noexcept {
+  return std::get<I>(args);
+}
+template <size_t I, typename Args>
+decltype(auto) get_emplace_arg(const Args& args) noexcept {
+  return std::get<I>(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 <typename Derived, typename Container, bool implicit_unpack>
+class emplace_iterator_base;
+
+/**
+ * Partial specialization of emplace_iterator_base with implicit unpacking
+ * disabled.
+ */
+template <typename Derived, typename Container>
+class emplace_iterator_base<Derived, Container, false> {
+ 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 <typename T>
+  Derived& operator=(T&& arg) {
+    return static_cast<Derived*>(this)->emplace(std::forward<T>(arg));
+  }
+
+  /**
+   * Special output operator for packed arguments. Unpacks args and performs
+   * variadic call to container's emplace function.
+   */
+  template <typename... Args>
+  Derived& operator=(emplace_args<Args...>& args) {
+    return unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(const emplace_args<Args...>& args) {
+    return unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(emplace_args<Args...>&& args) {
+    return unpackAndEmplace(
+        std::move(args), std::index_sequence_for<Args...>{});
+  }
+
+  // No-ops.
+  Derived& operator*() {
+    return static_cast<Derived&>(*this);
+  }
+  Derived& operator++() {
+    return static_cast<Derived&>(*this);
+  }
+  Derived& operator++(int) {
+    return static_cast<Derived&>(*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 <typename Args, std::size_t... I>
+  Derived& unpackAndEmplace(Args& args, std::index_sequence<I...>) {
+    return static_cast<Derived*>(this)->emplace(get_emplace_arg<I>(args)...);
+  }
+  template <typename Args, std::size_t... I>
+  Derived& unpackAndEmplace(const Args& args, std::index_sequence<I...>) {
+    return static_cast<Derived*>(this)->emplace(get_emplace_arg<I>(args)...);
+  }
+  template <typename Args, std::size_t... I>
+  Derived& unpackAndEmplace(Args&& args, std::index_sequence<I...>) {
+    return static_cast<Derived*>(this)->emplace(
+        get_emplace_arg<I>(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 <typename Derived, typename Container>
+class emplace_iterator_base<Derived, Container, true>
+    : public emplace_iterator_base<Derived, Container, false> {
+ public:
+  using emplace_iterator_base<Derived, Container, false>::emplace_iterator_base;
+  using emplace_iterator_base<Derived, Container, false>::operator=;
+
+  /**
+   * Special output operator for arguments packed into a std::pair. Unpacks
+   * the pair and performs variadic call to container's emplace function.
+   */
+  template <typename... Args>
+  Derived& operator=(std::pair<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(const std::pair<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(std::pair<Args...>&& args) {
+    return this->unpackAndEmplace(
+        std::move(args), std::index_sequence_for<Args...>{});
+  }
+
+  /**
+   * Special output operator for arguments packed into a std::tuple. Unpacks
+   * the tuple and performs variadic call to container's emplace function.
+   */
+  template <typename... Args>
+  Derived& operator=(std::tuple<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(const std::tuple<Args...>& args) {
+    return this->unpackAndEmplace(args, std::index_sequence_for<Args...>{});
+  }
+  template <typename... Args>
+  Derived& operator=(std::tuple<Args...>&& args) {
+    return this->unpackAndEmplace(
+        std::move(args), std::index_sequence_for<Args...>{});
+  }
+
+  // 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 <typename Container, bool implicit_unpack = true>
+class emplace_iterator : public detail::emplace_iterator_base<
+                             emplace_iterator<Container>,
+                             Container,
+                             implicit_unpack> {
+ private:
+  using Base = detail::emplace_iterator_base<
+      emplace_iterator<Container>,
+      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 <typename... Args>
+  emplace_iterator& emplace(Args&&... args) {
+    iter = this->container->emplace(iter, std::forward<Args>(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 <typename Container, bool implicit_unpack = true>
+class front_emplace_iterator : public detail::emplace_iterator_base<
+                                   front_emplace_iterator<Container>,
+                                   Container,
+                                   implicit_unpack> {
+ private:
+  using Base = detail::emplace_iterator_base<
+      front_emplace_iterator<Container>,
+      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 <typename... Args>
+  front_emplace_iterator& emplace(Args&&... args) {
+    this->container->emplace_front(std::forward<Args>(args)...);
+    return *this;
+  }
+};
+
+/**
+ * Behaves just like std::back_insert_iterator except that it calls
+ * emplace_back() instead of insert_back(). Uses perfect forwarding.
+ */
+template <typename Container, bool implicit_unpack = true>
+class back_emplace_iterator : public detail::emplace_iterator_base<
+                                  back_emplace_iterator<Container>,
+                                  Container,
+                                  implicit_unpack> {
+ private:
+  using Base = detail::emplace_iterator_base<
+      back_emplace_iterator<Container>,
+      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 <typename... Args>
+  back_emplace_iterator& emplace(Args&&... args) {
+    this->container->emplace_back(std::forward<Args>(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 <bool implicit_unpack = true, typename Container>
+emplace_iterator<Container, implicit_unpack> emplacer(
+    Container& c,
+    typename Container::iterator i) {
+  return emplace_iterator<Container, implicit_unpack>(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 <bool implicit_unpack = true, typename Container>
+front_emplace_iterator<Container, implicit_unpack> front_emplacer(
+    Container& c) {
+  return front_emplace_iterator<Container, implicit_unpack>(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 <bool implicit_unpack = true, typename Container>
+back_emplace_iterator<Container, implicit_unpack> back_emplacer(Container& c) {
+  return back_emplace_iterator<Container, implicit_unpack>(c);
+}
+}
index f5f5a8fc82cc7578c03ca3149cf2b478e8563813..0104510576e767dee1d9cd7d712dacdbba43283c 100644 (file)
@@ -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 (file)
index 0000000..815e9eb
--- /dev/null
@@ -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 <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <deque>
+#include <functional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Iterator.h>
+#include <folly/portability/GTest.h>
+
+namespace {
+/**
+ * Container type used for unit tests.
+ */
+template <typename T>
+using Container = std::deque<T>;
+
+// 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 <typename Iterator>
+void copy_and_move_test(Container<int>& 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<int>{4711});
+}
+
+/**
+ * Test for emplacement with perfect forwarding.
+ */
+template <typename Iterator>
+void emplace_test(Container<Object>& 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<const decltype(args)&>(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<int> q;
+    copy_and_move_test(q, emplacer(q, q.begin()));
+  }
+  {
+    Container<Object> q;
+    emplace_test(q, emplacer(q, q.begin()));
+  }
+  {
+    Container<int> 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<int>({3, 4, 0, 1, 2}));
+  }
+}
+
+/**
+ * Basic tests for folly::front_emplace_iterator.
+ */
+TEST(EmplaceIterator, FrontEmplacerTest) {
+  {
+    Container<int> q;
+    copy_and_move_test(q, front_emplacer(q));
+  }
+  {
+    Container<Object> q;
+    emplace_test(q, front_emplacer(q));
+  }
+  {
+    Container<int> q;
+    auto it = front_emplacer(q);
+    it = 0;
+    it = 1;
+    it = 2;
+    it = front_emplacer(q);
+    it = 3;
+    it = 4;
+    EXPECT_EQ(q, Container<int>({4, 3, 2, 1, 0}));
+  }
+}
+
+/**
+ * Basic tests for folly::back_emplace_iterator.
+ */
+TEST(EmplaceIterator, BackEmplacerTest) {
+  {
+    Container<int> q;
+    copy_and_move_test(q, back_emplacer(q));
+  }
+  {
+    Container<Object> q;
+    emplace_test(q, back_emplacer(q));
+  }
+  {
+    Container<int> q;
+    auto it = back_emplacer(q);
+    it = 0;
+    it = 1;
+    it = 2;
+    it = back_emplacer(q);
+    it = 3;
+    it = 4;
+    EXPECT_EQ(q, Container<int>({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<int> in({0, 1, 2});
+  Container<Object> 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<int> in({0, 1, 2});
+  Container<Object> 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<const Object>,
+            std::tuple_element_t<6, decltype(args)::storage_type>>::value,
+        "");
+    static_assert(
+        std::is_same<
+            rvalue_reference_wrapper<Object>,
+            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<int&&, decltype(get_emplace_arg<0>(std::move(args)))>::
+            value,
+        "");
+    static_assert(
+        std::is_same<Object&, decltype(get_emplace_arg<1>(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<Object&, decltype(get_emplace_arg<3>(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<Object&&, decltype(get_emplace_arg<5>(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<Object&&, decltype(get_emplace_arg<7>(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<int&, decltype(get_emplace_arg<0>(args))>::value, "");
+    static_assert(
+        std::is_same<Object&, decltype(get_emplace_arg<1>(args))>::value, "");
+    static_assert(
+        std::is_same<Object&, decltype(get_emplace_arg<2>(args))>::value, "");
+    static_assert(
+        std::is_same<Object&, decltype(get_emplace_arg<3>(args))>::value, "");
+    static_assert(
+        std::is_same<Object&, decltype(get_emplace_arg<4>(args))>::value, "");
+    static_assert(
+        std::is_same<Object&, decltype(get_emplace_arg<5>(args))>::value, "");
+    static_assert(
+        std::is_same<const Object&, decltype(get_emplace_arg<6>(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<int, int>&) {
+      ++pairCtrCnt;
+    }
+    explicit Object2(const std::tuple<int, int>&) {
+      ++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<Object2> q;
+
+  test(emplacer(q, q.begin()), true);
+  test(emplacer<false>(q, q.begin()), false);
+  test(front_emplacer(q), true);
+  test(front_emplacer<false>(q), false);
+  test(back_emplacer(q), true);
+  test(back_emplacer<false>(q), false);
+}
index 461596cb63fb08b223d4209b912576ce84965fdc..d3d12b51474ec257156a2d6e17d75a973ffd5dca 100644 (file)
@@ -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)