/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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 <folly/container/Iterator.h>
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <deque>
#include <functional>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <set>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include <folly/portability/GTest.h>
#if defined(__cpp_lib_concepts)
#include <concepts>
#endif
class IteratorTest : public testing::Test {};
TEST_F(IteratorTest, iterator_has_known_distance_v) {
EXPECT_FALSE((folly::iterator_has_known_distance_v<int*, int const*>));
EXPECT_TRUE((folly::iterator_has_known_distance_v<int*, int*>));
}
TEST_F(IteratorTest, range_has_known_distance_v) {
EXPECT_FALSE(folly::range_has_known_distance_v<std::list<int>&>);
EXPECT_TRUE(folly::range_has_known_distance_v<std::vector<int>&>);
}
TEST_F(IteratorTest, iterator_category_t) {
EXPECT_TRUE(( //
std::is_same_v<
folly::iterator_category_t<
std::iterator<std::input_iterator_tag, int>>,
std::input_iterator_tag>));
EXPECT_FALSE(( //
std::is_same_v<
folly::iterator_category_t<
std::iterator<std::input_iterator_tag, int>>,
std::output_iterator_tag>));
}
TEST_F(IteratorTest, iterator_category_matches_v) {
EXPECT_TRUE(( //
folly::iterator_category_matches_v<
std::iterator<std::input_iterator_tag, int>,
std::input_iterator_tag>));
EXPECT_FALSE(( //
folly::iterator_category_matches_v<
std::iterator<std::input_iterator_tag, int>,
std::output_iterator_tag>));
EXPECT_FALSE(( //
folly::iterator_category_matches_v<int, std::input_iterator_tag>));
}
TEST_F(IteratorTest, iterator_value_type_t) {
EXPECT_TRUE(( //
std::is_same_v<
std::map<int, double>::value_type,
folly::iterator_value_type_t<std::map<int, double>::iterator>>));
EXPECT_TRUE(( //
std::is_same_v<
std::map<std::reference_wrapper<int>, double&>::value_type,
folly::iterator_value_type_t<
std::map<std::reference_wrapper<int>, double&>::iterator>>));
EXPECT_FALSE(( //
std::is_same_v<
std::map<int, float>::value_type,
folly::iterator_value_type_t<std::map<int, double>::iterator>>));
}
TEST_F(IteratorTest, iterator_key_type_t) {
EXPECT_TRUE(( //
std::is_same_v<
std::map<int, double>::key_type,
folly::iterator_key_type_t<std::map<int, double>::iterator>>));
EXPECT_TRUE(( //
std::is_same_v<
std::map<std::reference_wrapper<int>, double&>::key_type,
folly::iterator_key_type_t<
std::map<std::reference_wrapper<int>, double&>::iterator>>));
EXPECT_TRUE(( //
std::is_same_v<
int,
folly::iterator_key_type_t<std::iterator<
std::input_iterator_tag,
std::pair<const int&, double>>>>));
EXPECT_FALSE(( //
std::is_same_v<
std::map<char, double>::key_type,
folly::iterator_key_type_t<std::map<int, double>::iterator>>));
}
TEST_F(IteratorTest, iterator_mapped_type_t) {
EXPECT_TRUE(( //
std::is_same_v<
std::map<int, double>::mapped_type,
folly::iterator_mapped_type_t<std::map<int, double>::iterator>>));
EXPECT_TRUE(( //
std::is_same_v<
std::map<std::reference_wrapper<int>, double&>::mapped_type,
folly::iterator_mapped_type_t<
std::map<std::reference_wrapper<int>, double&>::iterator>>));
EXPECT_FALSE(( //
std::is_same_v<
std::map<int, float>::mapped_type,
folly::iterator_mapped_type_t<std::map<int, double>::iterator>>));
}
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
FOLLY_PUSH_WARNING
FOLLY_CLANG_DISABLE_WARNING("-Wself-assign-overloaded")
it = it; // self assign
FOLLY_POP_WARNING
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);
}
} // namespace
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}));
}
}
/**
* Basic tests for folly::hint_emplace_iterator.
*/
TEST(EmplaceIterator, HintEmplacerTest) {
{
init_counters();
std::map<int, Object> m;
auto it = hint_emplacer(m, m.end());
it = make_emplace_args(
std::piecewise_construct,
std::forward_as_tuple(0),
std::forward_as_tuple(0));
it = make_emplace_args(
std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(0, 0));
it = make_emplace_args(
std::piecewise_construct,
std::forward_as_tuple(2),
std::forward_as_tuple(Object{}));
ASSERT_EQ(m.size(), 3);
EXPECT_EQ(gDefaultCtrCnt, 1);
EXPECT_EQ(gCopyCtrCnt, 0);
EXPECT_EQ(gMoveCtrCnt, 1);
EXPECT_EQ(gExplicitCtrCnt, 1);
EXPECT_EQ(gMultiargCtrCnt, 1);
EXPECT_EQ(gCopyOpCnt, 0);
EXPECT_EQ(gMoveOpCnt, 0);
EXPECT_EQ(gConvertOpCnt, 0);
}
{
struct O {
explicit O(int i_) : i(i_) {}
bool operator<(const O& other) const { return i < other.i; }
bool operator==(const O& other) const { return i == other.i; }
int i;
};
std::vector<int> v1 = {0, 1, 2, 3, 4};
std::vector<int> v2 = {0, 2, 4};
std::set<O> diff;
std::set_difference(
v1.begin(),
v1.end(),
v2.begin(),
v2.end(),
hint_emplacer(diff, diff.end()));
std::set<O> expected = {O(1), O(3)};
ASSERT_EQ(diff, expected);
}
}
/**
* 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);
}
// IndexIterator -------------
namespace index_iterator_type_tests {
namespace {
struct WeirdContainer {
std::vector<bool> body_;
using value_type = bool;
using size_type = std::uint32_t;
using difference_type = std::int32_t;
auto operator[](size_type idx) { return body_[idx]; }
auto operator[](size_type idx) const { return body_[idx]; }
};
// iterator concepts ------------------------
#if defined(__cpp_lib_concepts)
using vit = folly::index_iterator<std::vector<int>>;
using cvit = folly::index_iterator<const std::vector<int>>;
using vit_weird = folly::index_iterator<WeirdContainer>;
using cvit_weird = folly::index_iterator<const WeirdContainer>;
static_assert(std::random_access_iterator<vit>);
static_assert(std::random_access_iterator<cvit>);
static_assert(std::random_access_iterator<vit_weird>);
static_assert(std::random_access_iterator<cvit_weird>);
#endif
// dependent type computations ------------------------
struct ref_type {};
struct cref_type {};
struct just_operator {
using value_type = int;
ref_type operator[](std::size_t) { return {}; }
cref_type operator[](std::size_t) const { return {}; }
};
struct has_size_type : just_operator {
using size_type = std::uint32_t;
};
struct has_difference_type : just_operator {
using difference_type = std::int32_t;
};
struct has_both_size_and_difference_type : just_operator {
using size_type = std::uint32_t;
using difference_type = std::int32_t;
};
struct no_operator {
using value_type = int;
friend ref_type tag_invoke(
folly::cpo_t<index_iterator_access_at>, no_operator&, std::size_t) {
return {};
}
friend cref_type tag_invoke(
folly::cpo_t<index_iterator_access_at>, const no_operator&, std::size_t) {
return {};
}
[[maybe_unused]] int operator[](std::size_t);
[[maybe_unused]] int operator[](std::size_t) const;
};
struct no_operator_private {
using value_type = int;
private:
friend ref_type tag_invoke(
folly::cpo_t<index_iterator_access_at>,
no_operator_private&,
std::size_t) {
return {};
}
friend cref_type tag_invoke(
folly::cpo_t<index_iterator_access_at>,
const no_operator_private&,
std::size_t) {
return {};
}
};
template <typename T>
using all_types = std::tuple<
typename std::iterator_traits<folly::index_iterator<T>>::value_type,
typename std::iterator_traits<folly::index_iterator<T>>::reference,
typename std::iterator_traits<folly::index_iterator<T>>::difference_type,
typename folly::index_iterator<T>::size_type>;
// gives better error messages than std::is_same
template <typename T>
void is_same_test(T, T) {}
} // namespace
TEST(IndexIterator, Types) {
is_same_test(
all_types<just_operator>{},
std::tuple<int, ref_type, std::ptrdiff_t, std::size_t>{});
is_same_test(
all_types<const just_operator>{},
std::tuple<int, cref_type, std::ptrdiff_t, std::size_t>{});
is_same_test(
all_types<has_size_type>{},
std::tuple<int, ref_type, std::ptrdiff_t, std::uint32_t>{});
is_same_test(
all_types<const has_size_type>{},
std::tuple<int, cref_type, std::ptrdiff_t, std::uint32_t>{});
is_same_test(
all_types<has_difference_type>{},
std::tuple<int, ref_type, std::int32_t, std::size_t>{});
is_same_test(
all_types<const has_difference_type>{},
std::tuple<int, cref_type, std::int32_t, std::size_t>{});
is_same_test(
all_types<has_both_size_and_difference_type>{},
std::tuple<int, ref_type, std::int32_t, std::uint32_t>{});
is_same_test(
all_types<const has_both_size_and_difference_type>{},
std::tuple<int, cref_type, std::int32_t, std::uint32_t>{});
is_same_test(
all_types<no_operator>{},
std::tuple<int, ref_type, std::ptrdiff_t, std::size_t>{});
is_same_test(
all_types<const no_operator>{},
std::tuple<int, cref_type, std::ptrdiff_t, std::size_t>{});
is_same_test(
all_types<no_operator_private>{},
std::tuple<int, ref_type, std::ptrdiff_t, std::size_t>{});
is_same_test(
all_types<const no_operator_private>{},
std::tuple<int, cref_type, std::ptrdiff_t, std::size_t>{});
}
} // namespace index_iterator_type_tests
TEST(IndexIterator, Sort) {
std::vector<int> v(100, 0);
std::iota(v.rbegin(), v.rend(), 0);
std::sort(
index_iterator<std::vector<int>>{v, 0},
index_iterator<std::vector<int>>{v, v.size()});
std::vector<int> expected(100, 0);
std::iota(expected.begin(), expected.end(), 0);
ASSERT_EQ(expected, v);
}
namespace {
constexpr bool accessTest() {
std::array<int, 2> a{0, 1};
const std::array<int, 2> b{0, 1};
const index_iterator<std::array<int, 2>> mutI{a, 0};
const index_iterator<const std::array<int, 2>> constI{b, 0};
if (*mutI != 0 || *constI != 0) {
return false;
}
if (mutI[1] != 1 || constI[1] != 1) {
return false;
}
return true;
}
static_assert(accessTest());
template <typename I>
constexpr bool mutationsTest(I i0, I i1) {
auto tmp = i0;
if (++tmp != i1) {
return false;
}
tmp = i0;
if (tmp++ != i0) {
return false;
}
if (tmp != i1) {
return false;
}
tmp = i1;
if (--tmp != i0) {
return false;
}
tmp = i1;
if (tmp-- != i1) {
return false;
}
if (tmp != i0) {
return false;
}
return true;
}
static constexpr std::array<int, 2> kA{0, 1};
static constexpr index_iterator<const std::array<int, 2>> kI0{kA, 0};
static constexpr index_iterator<const std::array<int, 2>> kI1{kA, 1};
static_assert(kI0 == kI0);
static_assert(kI0 != kI1);
static_assert(kI0 < kI1);
static_assert(kI0 <= kI0);
static_assert(kI0 <= kI1);
static_assert(kI1 >= kI1);
static_assert(kI1 >= kI0);
static_assert(kI1 > kI0);
static_assert(mutationsTest(kI0, kI1));
static_assert(kI0 + 1 == kI1);
static_assert(kI1 - 1 == kI0);
static_assert(kI1 - kI0 == 1);
static_assert(kI0 - kI1 == -1);
struct IndexedVector {
std::vector<int> v_;
std::pair<int, int&> operator[](std::size_t i) {
return {static_cast<int>(i), v_[i]};
}
std::pair<int, const int&> operator[](std::size_t i) const {
return {static_cast<int>(i), v_[i]};
}
using value_type = std::pair<int, int>;
using iterator = folly::index_iterator<IndexedVector>;
using const_iterator = folly::index_iterator<const IndexedVector>;
iterator begin() { return {*this, 0}; }
const_iterator begin() const { return cbegin(); }
const_iterator cbegin() const { return {*this, 0}; }
iterator end() { return {*this, v_.size()}; }
const_iterator end() const { return cend(); }
const_iterator cend() const { return {*this, v_.size()}; }
};
struct CustomMapWithIndexedIterator {
public:
using size_type = std::uint32_t;
using value_type = std::pair<int, int>;
public:
using iterator = folly::index_iterator<CustomMapWithIndexedIterator>;
using const_iterator =
folly::index_iterator<const CustomMapWithIndexedIterator>;
iterator begin() { return {*this, 0}; }
const_iterator cbegin() const { return {*this, 0}; }
std::vector<int> keys;
std::vector<int> values;
private:
friend std::pair<int, int> tag_invoke(
folly::cpo_t<index_iterator_access_at>,
const CustomMapWithIndexedIterator& self,
size_type idx) {
return {self.keys[idx], self.values[idx]};
}
};
} // namespace
TEST(IndexIterator, UseProxyReferences) {
IndexedVector iv{{0, 1, 2, 3}};
const IndexedVector& civ = iv;
using it = decltype(iv.begin());
using cit = decltype(civ.begin());
using it_ref_t = typename std::iterator_traits<it>::reference;
using it_cref_t = typename std::iterator_traits<cit>::reference;
static_assert(std::is_same<it_ref_t, std::pair<int, int&>>::value, "");
static_assert(std::is_same<it_cref_t, std::pair<int, const int&>>::value, "");
static_assert(std::is_same<decltype(it {} -> first), int>::value, "");
static_assert(std::is_same<decltype(it {} -> second), int&>::value, "");
static_assert(std::is_same<decltype(cit {} -> first), int>::value, "");
static_assert(
std::is_same<decltype(cit {} -> second), const int&>::value, "");
ASSERT_EQ(4, (std::count_if(civ.begin(), civ.end(), [](auto&& pair) {
return pair.first == pair.second;
})));
ASSERT_EQ(4, (std::count_if(iv.begin(), iv.end(), [](auto&& pair) {
return pair.first == pair.second;
})));
cit conversion = iv.begin() + 1;
ASSERT_EQ(&iv, conversion.get_container());
ASSERT_EQ(1, conversion.get_index());
static_assert(!std::is_convertible<cit, it>::value);
for (auto&& x : iv) {
x.second = -1;
}
std::vector<int> expected{-1, -1, -1, -1};
ASSERT_EQ(expected, iv.v_);
// testing pointer proxies
for (auto f = iv.begin(); f != iv.end(); ++f) {
f->second = 1;
}
expected = {1, 1, 1, 1};
ASSERT_EQ(expected, iv.v_);
}
TEST(IndexIterator, OperatorArrowForNonProxies) {
using v_t = std::vector<std::array<int, 2>>;
using iterator = folly::index_iterator<v_t>;
static_assert(std::is_same<iterator::pointer, v_t::pointer>::value, "");
v_t v;
v.resize(3);
iterator f{v, 0};
iterator l{v, v.size()};
f->at(0) = 1;
(f + 1)->at(0) = 2;
(f + 2)->at(0) = 3;
v_t expected{{1, 0}, {2, 0}, {3, 0}};
ASSERT_EQ(expected, v);
}
TEST(IndexIterator, NoOperatorIntegration) {
CustomMapWithIndexedIterator m{.keys{1, 2, 3}, .values{4, 5, 6}};
auto it = m.begin();
ASSERT_EQ((std::pair{1, 4}), it[0]);
ASSERT_EQ((std::pair{2, 5}), it[1]);
ASSERT_EQ((std::pair{3, 6}), it[2]);
auto cit = m.cbegin();
ASSERT_EQ((std::pair{1, 4}), cit[0]);
ASSERT_EQ((std::pair{2, 5}), cit[1]);
ASSERT_EQ((std::pair{3, 6}), cit[2]);
}