Commit 55ec604d authored by Steve O'Brien's avatar Steve O'Brien Committed by Facebook Github Bot

folly: iterator classes (1/3): add minimal facade/adaptor classes + test

Summary:
Iterator helper classes are used in several places around Folly; they're convenient because they take care of some of the details while letting your class provide only a few of the necessary pieces.

Boost has `iterator_facade` and `iterator_adaptor` to help in these cases, but the header for these classes are absurdly expensive to include (their transitive includee tree is quite large).

For this reason, we'll add similar helper classes, with minimal dependencies, under detail.  In subsequent diffs we'll migrate some existing code using these boost classes (or implementing iterators itself) to use these helpers.

Reviewed By: yfeldblum

Differential Revision: D8345073

fbshipit-source-id: 3e6656e544349fe228358074de30c89c805e2628
parent 3f12b5ad
...@@ -685,6 +685,7 @@ if (BUILD_TESTS) ...@@ -685,6 +685,7 @@ if (BUILD_TESTS)
TEST group_varint_test SOURCES GroupVarintTest.cpp TEST group_varint_test SOURCES GroupVarintTest.cpp
TEST group_varint_test_ssse3 SOURCES GroupVarintTest.cpp TEST group_varint_test_ssse3 SOURCES GroupVarintTest.cpp
TEST has_member_fn_traits_test SOURCES HasMemberFnTraitsTest.cpp TEST has_member_fn_traits_test SOURCES HasMemberFnTraitsTest.cpp
TEST iterators_test SOURCES IteratorsTest.cpp
TEST indestructible_test SOURCES IndestructibleTest.cpp TEST indestructible_test SOURCES IndestructibleTest.cpp
TEST indexed_mem_pool_test BROKEN TEST indexed_mem_pool_test BROKEN
SOURCES IndexedMemPoolTest.cpp SOURCES IndexedMemPoolTest.cpp
......
...@@ -93,6 +93,7 @@ nobase_follyinclude_HEADERS = \ ...@@ -93,6 +93,7 @@ nobase_follyinclude_HEADERS = \
detail/GroupVarintDetail.h \ detail/GroupVarintDetail.h \
detail/IPAddress.h \ detail/IPAddress.h \
detail/IPAddressSource.h \ detail/IPAddressSource.h \
detail/Iterators.h \
detail/MemoryIdler.h \ detail/MemoryIdler.h \
detail/MPMCPipelineDetail.h \ detail/MPMCPipelineDetail.h \
detail/PolyDetail.h \ detail/PolyDetail.h \
......
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cstddef>
#include <iterator>
#include <type_traits>
/*
* This contains stripped-down workalikes of some Boost classes:
*
* iterator_adaptor
* iterator_facade
*
* Rationale: the boost headers providing those classes are surprisingly large.
* The bloat comes from the headers themselves, but more so, their transitive
* includes.
*
* These implementations are simple and minimal. They may be missing features
* provided by the Boost classes mentioned above. Also at this time they only
* support forward-iterators. They provide just enough for the few uses within
* Folly libs; more features will be slapped in here if and when they're needed.
*
* These classes may possibly add features as well. Care is taken not to
* change functionality where it's expected to be the same (e.g. `dereference`
* will do the same thing).
*
* These are currently only intended for use within Folly, hence their living
* under detail. Use outside Folly is not recommended.
*
* To see how to use these classes, find the instances where this is used within
* Folly libs. Common use cases can also be found in `IteratorsTest.cpp`.
*/
namespace folly {
namespace detail {
/**
* Currently this only supports forward iteration. The derived class must
* must have definitions for these three methods:
*
* void increment();
* reference dereference() const;
* bool equal([appropriate iterator type] const& rhs) const;
*
* These names are consistent with those used by the Boost iterator
* facade / adaptor classes to ease migration classes in this file.
*
* Template parameters:
* D: the deriving class (CRTP)
* V: value type
*/
template <class D, class V>
class IteratorFacade {
public:
using value_type = V;
using reference = value_type&;
using pointer = value_type*;
using difference_type = ssize_t;
using iterator_category = std::forward_iterator_tag;
bool operator==(D const& rhs) const {
return asDerivedConst().equal(rhs);
}
bool operator!=(D const& rhs) const {
return !operator==(rhs);
}
/*
* Allow for comparisons between this and an iterator of some other class.
* (e.g. a const_iterator version of this, the probable use case).
* Does a conversion of D (or D reference) to D2, if one exists (otherwise
* this is disabled). Disabled if D and D2 are the same, to disambiguate
* this and the `operator==(D const&) const` method above.
*/
template <class D2>
typename std::enable_if<std::is_convertible<D, D2>::value, bool>::type
operator==(D2 const& rhs) const {
return D2(asDerivedConst()) == rhs;
}
template <class D2>
bool operator!=(D2 const& rhs) const {
return !operator==(rhs);
}
V& operator*() const {
return asDerivedConst().dereference();
}
V* operator->() const {
return std::addressof(operator*());
}
D& operator++() {
asDerived().increment();
return asDerived();
}
D operator++(int) {
auto ret = asDerived(); // copy
asDerived().increment();
return ret;
}
private:
D& asDerived() {
return static_cast<D&>(*this);
}
D const& asDerivedConst() const {
return static_cast<D const&>(*this);
}
};
/**
* Wrap one iterator while providing an interator interface with e.g. a
* different value_type.
*
* Template parameters:
* D: the deriving class (CRTP)
* I: the wrapper iterator type
* V: value type
*/
template <class D, class I, class V>
class IteratorAdaptor : public IteratorFacade<D, V> {
public:
using Super = IteratorFacade<D, V>;
using value_type = typename Super::value_type;
using iterator_category = typename Super::iterator_category;
using reference = typename Super::reference;
using pointer = typename Super::pointer;
using difference_type = typename Super::difference_type;
explicit IteratorAdaptor(I base) : base_(base) {}
void increment() {
++base_;
}
V& dereference() const {
return *base_;
}
bool equal(D const& rhs) const {
return base_ == rhs.base_;
}
I const& base() const {
return base_;
}
I& base() {
return base_;
}
private:
I base_;
};
} // namespace detail
} // namespace folly
/*
* Copyright 2016-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <map>
#include <folly/container/Array.h>
#include <folly/detail/Iterators.h>
#include <folly/portability/GTest.h>
using namespace folly::detail;
using namespace folly;
namespace {
struct IntArrayIterator : IteratorFacade<IntArrayIterator, int const> {
explicit IntArrayIterator(int const* a) : a_(a) {}
void increment() {
++a_;
}
int const& dereference() const {
return *a_;
}
bool equal(IntArrayIterator const& rhs) const {
return rhs.a_ == a_;
}
int const* a_;
};
} // namespace
TEST(IteratorsTest, IterFacadeHasCorrectTraits) {
using TR = std::iterator_traits<IntArrayIterator>;
static_assert(std::is_same<TR::value_type, int const>::value, "");
static_assert(std::is_same<TR::reference, int const&>::value, "");
static_assert(std::is_same<TR::pointer, int const*>::value, "");
static_assert(
std::is_same<TR::iterator_category, std::forward_iterator_tag>::value,
"");
static_assert(std::is_same<TR::difference_type, ssize_t>::value, "");
}
TEST(IteratorsTest, SimpleIteratorFacade) {
static const auto kArray = folly::make_array(42, 43, 44);
IntArrayIterator end(kArray.data() + kArray.size());
IntArrayIterator iter(kArray.data());
EXPECT_NE(iter, end);
EXPECT_EQ(42, *iter);
++iter;
EXPECT_NE(iter, end);
EXPECT_EQ(43, *iter);
++iter;
EXPECT_NE(iter, end);
EXPECT_EQ(44, *iter);
++iter;
EXPECT_EQ(iter, end);
}
namespace {
// Simple iterator adaptor: wraps an int pointer.
struct IntPointerIter : IteratorAdaptor<IntPointerIter, int const*, int const> {
using Super = IteratorAdaptor<IntPointerIter, int const*, int const>;
explicit IntPointerIter(int const* ptr) : Super(ptr) {}
};
} // namespace
TEST(IteratorsTest, IterAdaptorHasCorrectTraits) {
using TR = std::iterator_traits<IntPointerIter>;
static_assert(std::is_same<TR::value_type, int const>::value, "");
static_assert(std::is_same<TR::reference, int const&>::value, "");
static_assert(std::is_same<TR::pointer, int const*>::value, "");
static_assert(
std::is_same<TR::iterator_category, std::forward_iterator_tag>::value,
"");
static_assert(std::is_same<TR::difference_type, ssize_t>::value, "");
}
TEST(IteratorsTest, IterAdaptorWithPointer) {
static const auto kArray = folly::make_array(42, 43, 44);
IntPointerIter end(kArray.data() + kArray.size());
IntPointerIter iter(kArray.data());
EXPECT_NE(iter, end);
EXPECT_EQ(42, *iter);
++iter;
EXPECT_NE(iter, end);
EXPECT_EQ(43, *iter);
++iter;
EXPECT_NE(iter, end);
EXPECT_EQ(44, *iter);
++iter;
EXPECT_EQ(iter, end);
}
namespace {
// More complex case: wrap a map iterator, but these provide either the key or
// value.
struct IntMapKeyIter
: IteratorAdaptor<IntMapKeyIter, std::map<int, int>::iterator, int const> {
using Super =
IteratorAdaptor<IntMapKeyIter, std::map<int, int>::iterator, int const>;
explicit IntMapKeyIter(std::map<int, int>::iterator iter) : Super(iter) {}
int const& dereference() const {
return base()->first;
}
};
struct IntMapValueIter
: IteratorAdaptor<IntMapValueIter, std::map<int, int>::iterator, int> {
using Super =
IteratorAdaptor<IntMapValueIter, std::map<int, int>::iterator, int>;
explicit IntMapValueIter(std::map<int, int>::iterator iter) : Super(iter) {}
int& dereference() const {
return base()->second;
}
};
} // namespace
TEST(IteratorsTest, IterAdaptorOfOtherIter) {
std::map<int, int> m{{2, 42}, {3, 43}, {4, 44}};
IntMapKeyIter keyEnd(m.end());
IntMapKeyIter keyIter(m.begin());
EXPECT_NE(keyIter, keyEnd);
EXPECT_EQ(2, *keyIter);
++keyIter;
EXPECT_NE(keyIter, keyEnd);
EXPECT_EQ(3, *keyIter);
++keyIter;
EXPECT_NE(keyIter, keyEnd);
EXPECT_EQ(4, *keyIter);
++keyIter;
EXPECT_EQ(keyIter, keyEnd);
IntMapValueIter valueEnd(m.end());
IntMapValueIter valueIter(m.begin());
EXPECT_NE(valueIter, valueEnd);
EXPECT_EQ(42, *valueIter);
++valueIter;
EXPECT_NE(valueIter, valueEnd);
EXPECT_EQ(43, *valueIter);
++valueIter;
EXPECT_NE(valueIter, valueEnd);
EXPECT_EQ(44, *valueIter);
++valueIter;
EXPECT_EQ(valueIter, valueEnd);
}
namespace {
struct IntMapValueIterConst : IteratorAdaptor<
IntMapValueIterConst,
std::map<int, int>::const_iterator,
int const> {
using Super = IteratorAdaptor<
IntMapValueIterConst,
std::map<int, int>::const_iterator,
int const>;
explicit IntMapValueIterConst(std::map<int, int>::const_iterator iter)
: Super(iter) {}
/* implicit */ IntMapValueIterConst(IntMapValueIter const& rhs)
: IntMapValueIterConst(rhs.base()) {}
int const& dereference() const {
return base()->second;
}
};
} // namespace
TEST(IteratorsTest, MixedConstAndNonconstIters) {
std::map<int, int> m{{2, 42}, {3, 43}, {4, 44}};
IntMapValueIterConst cend(m.cend());
IntMapValueIter valueIter(m.begin());
EXPECT_NE(valueIter, cend);
++valueIter;
++valueIter;
++valueIter;
EXPECT_EQ(valueIter, cend);
}
...@@ -385,4 +385,8 @@ iterator_test_SOURCES = ../container/test/IteratorTest.cpp ...@@ -385,4 +385,8 @@ iterator_test_SOURCES = ../container/test/IteratorTest.cpp
iterator_test_LDADD = libfollytestmain.la iterator_test_LDADD = libfollytestmain.la
TESTS += iterator_test TESTS += iterator_test
detail_iterators_test_SOURCES = IteratorTests.cpp
detail_iterators_test_LDADD = libfollytestmain.la
TESTS += iterator_test
check_PROGRAMS += $(TESTS) 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