[ADT] Add zip_longest iterators

Like the already existing zip_shortest/zip_first iterators, zip_longest
iterates over multiple iterators at once, but has as many iterations as
the longest sequence.

This means some iterators may reach the end before others do.
zip_longest uses llvm::Optional's None value to mark a
past-the-end value.

zip_longest is not reverse-iteratable because the tuples iterated over
would be different for different length sequences (IMHO for the same
reason neither zip_shortest nor zip_first should be reverse-iteratable;
one can still reverse the ranges individually if that's the expected
behavior).

In contrast to zip_shortest/zip_first, zip_longest tuples contain
rvalues instead of references. This is because llvm::Optional cannot
contain reference types and the value-initialized default does not have
a memory location a reference could point to.

The motivation for these iterators is to use C++ foreach to compare two
lists of ordered attributes in D48100 (SemaOverload.cpp and
ASTReaderDecl.cpp).

Idea by @hfinkel.

Differential Revision: https://reviews.llvm.org/D48348

llvm-svn: 348301
This commit is contained in:
Michael Kruse 2018-12-04 21:06:16 +00:00
parent 24fb296589
commit e6899bf002
2 changed files with 155 additions and 1 deletions

View File

@ -508,9 +508,11 @@ make_early_inc_range(RangeT &&Range) {
EarlyIncIteratorT(std::end(std::forward<RangeT>(Range))));
}
// forward declarations required by zip_shortest/zip_first
// forward declarations required by zip_shortest/zip_first/zip_longest
template <typename R, typename UnaryPredicate>
bool all_of(R &&range, UnaryPredicate P);
template <typename R, typename UnaryPredicate>
bool any_of(R &&range, UnaryPredicate P);
template <size_t... I> struct index_sequence;
@ -661,6 +663,128 @@ detail::zippy<detail::zip_first, T, U, Args...> zip_first(T &&t, U &&u,
std::forward<T>(t), std::forward<U>(u), std::forward<Args>(args)...);
}
namespace detail {
template <typename Iter>
static Iter next_or_end(const Iter &I, const Iter &End) {
if (I == End)
return End;
return std::next(I);
}
template <typename Iter>
static auto deref_or_none(const Iter &I, const Iter &End)
-> llvm::Optional<typename std::remove_const<
typename std::remove_reference<decltype(*I)>::type>::type> {
if (I == End)
return None;
return *I;
}
template <typename... Iters> struct ZipLongestValueType {
using type = std::tuple<
llvm::Optional<typename std::remove_const<typename std::remove_reference<
decltype(*std::declval<Iters>())>::type>::type>...>;
};
template <typename... Iters>
class zip_longest_iterator
: public iterator_facade_base<
zip_longest_iterator<Iters...>,
typename std::common_type<
std::forward_iterator_tag,
typename std::iterator_traits<Iters>::iterator_category...>::type,
typename ZipLongestValueType<Iters...>::type,
typename std::iterator_traits<typename std::tuple_element<
0, std::tuple<Iters...>>::type>::difference_type,
typename ZipLongestValueType<Iters...>::type *,
typename ZipLongestValueType<Iters...>::type> {
public:
using value_type = typename ZipLongestValueType<Iters...>::type;
private:
std::tuple<Iters...> iterators;
std::tuple<Iters...> end_iterators;
template <size_t... Ns>
bool test(const zip_longest_iterator<Iters...> &other,
index_sequence<Ns...>) const {
return llvm::any_of(
std::initializer_list<bool>{std::get<Ns>(this->iterators) !=
std::get<Ns>(other.iterators)...},
identity<bool>{});
}
template <size_t... Ns> value_type deref(index_sequence<Ns...>) const {
return value_type(
deref_or_none(std::get<Ns>(iterators), std::get<Ns>(end_iterators))...);
}
template <size_t... Ns>
decltype(iterators) tup_inc(index_sequence<Ns...>) const {
return std::tuple<Iters...>(
next_or_end(std::get<Ns>(iterators), std::get<Ns>(end_iterators))...);
}
public:
zip_longest_iterator(std::pair<Iters &&, Iters &&>... ts)
: iterators(std::forward<Iters>(ts.first)...),
end_iterators(std::forward<Iters>(ts.second)...) {}
value_type operator*() { return deref(index_sequence_for<Iters...>{}); }
value_type operator*() const { return deref(index_sequence_for<Iters...>{}); }
zip_longest_iterator<Iters...> &operator++() {
iterators = tup_inc(index_sequence_for<Iters...>{});
return *this;
}
bool operator==(const zip_longest_iterator<Iters...> &other) const {
return !test(other, index_sequence_for<Iters...>{});
}
};
template <typename... Args> class zip_longest_range {
public:
using iterator =
zip_longest_iterator<decltype(adl_begin(std::declval<Args>()))...>;
using iterator_category = typename iterator::iterator_category;
using value_type = typename iterator::value_type;
using difference_type = typename iterator::difference_type;
using pointer = typename iterator::pointer;
using reference = typename iterator::reference;
private:
std::tuple<Args...> ts;
template <size_t... Ns> iterator begin_impl(index_sequence<Ns...>) const {
return iterator(std::make_pair(adl_begin(std::get<Ns>(ts)),
adl_end(std::get<Ns>(ts)))...);
}
template <size_t... Ns> iterator end_impl(index_sequence<Ns...>) const {
return iterator(std::make_pair(adl_end(std::get<Ns>(ts)),
adl_end(std::get<Ns>(ts)))...);
}
public:
zip_longest_range(Args &&... ts_) : ts(std::forward<Args>(ts_)...) {}
iterator begin() const { return begin_impl(index_sequence_for<Args...>{}); }
iterator end() const { return end_impl(index_sequence_for<Args...>{}); }
};
} // namespace detail
/// Iterate over two or more iterators at the same time. Iteration continues
/// until all iterators reach the end. The llvm::Optional only contains a value
/// if the iterator has not reached the end.
template <typename T, typename U, typename... Args>
detail::zip_longest_range<T, U, Args...> zip_longest(T &&t, U &&u,
Args &&... args) {
return detail::zip_longest_range<T, U, Args...>(
std::forward<T>(t), std::forward<U>(u), std::forward<Args>(args)...);
}
/// Iterator wrapper that concatenates sequences together.
///
/// This can concatenate different iterators, even with different types, into

View File

@ -328,6 +328,36 @@ TEST(ZipIteratorTest, ZipFirstBasic) {
EXPECT_EQ(iters, 4u);
}
TEST(ZipIteratorTest, ZipLongestBasic) {
using namespace std;
const vector<unsigned> pi{3, 1, 4, 1, 5, 9};
const vector<StringRef> e{"2", "7", "1", "8"};
{
// Check left range longer than right.
const vector<tuple<Optional<unsigned>, Optional<StringRef>>> expected{
{3, {"2"}}, {1, {"7"}}, {4, {"1"}}, {1, {"8"}}, {5, None}, {9, None}};
size_t iters = 0;
for (auto tup : zip_longest(pi, e)) {
EXPECT_EQ(tup, expected[iters]);
iters += 1;
}
EXPECT_EQ(iters, expected.size());
}
{
// Check right range longer than left.
const vector<tuple<Optional<StringRef>, Optional<unsigned>>> expected{
{{"2"}, 3}, {{"7"}, 1}, {{"1"}, 4}, {{"8"}, 1}, {None, 5}, {None, 9}};
size_t iters = 0;
for (auto tup : zip_longest(e, pi)) {
EXPECT_EQ(tup, expected[iters]);
iters += 1;
}
EXPECT_EQ(iters, expected.size());
}
}
TEST(ZipIteratorTest, Mutability) {
using namespace std;
const SmallVector<unsigned, 4> pi{3, 1, 4, 1, 5, 9};