Commit d5986bf0 authored by Philipp Unterbrunner's avatar Philipp Unterbrunner Committed by Facebook Github Bot

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
parent f34acf2c
/*
* 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);
}
}
......@@ -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 \
......
/*
* 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);
}
......@@ -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)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment