libcruft-util/iterator/zip.hpp

236 lines
7.5 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2010-2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "iota.hpp"
#include "../concepts.hpp"
#include "../tuple/value.hpp"
#include "../variadic.hpp"
#include "../functor.hpp"
namespace cruft::iterator {
///////////////////////////////////////////////////////////////////////////
namespace detail::zip {
/// A container that holds multiple iterators and returns a tuple of
/// their results when dereferenced.
///
/// \tparam IteratorT A tuple-like object that contains iterators
template <
typename IteratorT,
typename = std::make_index_sequence<std::tuple_size_v<IteratorT>>
>
struct zipped_iterator;
template <typename IteratorT, std::size_t ...Indices>
struct zipped_iterator<IteratorT, std::index_sequence<Indices...>> {
public:
// We can't declare ourselves as a forward_iterator because we're
// unable to supply references to our value_type when we get
// dereferenced unless we store the supplied values/references
// inside ourself.
//
// This complicates implementation too much given we have no
// pressing need for this functionality.
using iterator_category = std::input_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = std::tuple<
decltype(
*std::get<Indices> (
std::declval<IteratorT> ()
)
)...
>;
using reference = value_type;
using pointer = value_type*;
zipped_iterator (IteratorT _iterators):
m_iterators (_iterators)
{ ; }
zipped_iterator&
operator++ (void)
{
(++std::get<Indices> (m_iterators), ...);
return *this;
}
zipped_iterator operator++ (int);
decltype(auto)
operator* (void)
{
// We deliberately construct a tuple manually here to reduce
// the risk of dangling references. `forward_as_tuple` and
// `make_tuple` have resulted in references to locals in the
// past.
return std::tuple<
decltype(*std::get<Indices> (m_iterators))...
> (
*std::get<Indices> (m_iterators)...
);
}
decltype (auto)
operator* (void) const
{
// We deliberately construct a tuple manually here to reduce
// the risk of dangling references. `forward_as_tuple` and
// `make_tuple` have resulted in references to locals in the
// past.
return std::tuple<
decltype(*std::get<Indices> (m_iterators))...
> (
*std::get<Indices> (m_iterators)...
);
}
bool
operator== (const zipped_iterator &rhs) const
{
return m_iterators == rhs.m_iterators;
}
bool
operator!= (const zipped_iterator &rhs) const
{
return !(*this == rhs);
}
private:
IteratorT m_iterators;
};
/// A simple container for multiple collections that returns a
/// wrapped multi-iterator for begin and end.
///
/// It is up to the user to ensure StoreT does not contain dangling
/// references.
///
/// \tparam StoreT A tuple-like object of collections (or references
/// to collections).
template <typename ...StoreT>
class collection {
public:
collection (StoreT&&... _store):
m_store (std::forward<StoreT> (_store)...)
{ ; }
using inner_t = std::tuple<decltype(std::begin (std::declval<StoreT> ()))...>;
using indices_t = std::make_index_sequence<sizeof...(StoreT)>;
using iterator = zipped_iterator<inner_t, indices_t>;
iterator begin (void)&
{
return iterator (
tuple::value::map (::cruft::functor::begin {}, m_store)
);
}
iterator begin (void) const&
{
return iterator (
tuple::value::map (::cruft::functor::begin {}, m_store)
);
}
iterator end (void)&
{
return iterator (
tuple::value::map (cruft::functor::end {}, m_store)
);
}
iterator end (void) const &
{
return iterator (
tuple::value::map (cruft::functor::end {}, m_store)
);
}
/// Returns the number of elements in the sequence.
decltype (auto)
size (void) const
{
// All stores should have the same size so we arbitrarily pick
// the first to query.
return std::size (std::get<0> (m_store));
}
private:
std::tuple<StoreT...> m_store;
};
}
///------------------------------------------------------------------------
/// Takes a variable number of container arguments and returns an interable
/// object with a value_type that is a tuple of the each container's
/// value_type.
///
/// The returned iterator value_type is suitable for using in range-for
/// and structured bindings (and really, that's the entire point here).
///
/// eg, cruft::zip ({1,2,3}, {4,5,6}) ~= {{1,4},{2,5},{3,6}}
template <typename ...ContainerT>
requires (cruft::concepts::iterable<ContainerT> && ...)
decltype(auto)
zip (ContainerT&&... data)
{
CHECK (((std::size (data) == std::size (variadic::get<0> (data...))) && ...));
return detail::zip::collection<ContainerT...> (
std::forward<ContainerT> (data)...
);
}
///------------------------------------------------------------------------
/// Takes a variable number of containers and returns a zipped iterable
/// object where the first of the iterator's value_types is the index of
/// that iterator. ie, It combines container offsets with value_types.
///
/// The supplied containers _must_ not change size while the izip object
/// is live. The size of the containers may be cached for efficiency.
///
/// eg, cruft::izip ("abc") ~= {{0,'a'},{1,'b'},{2,'c'}}
template <typename ...ContainerT>
requires (cruft::concepts::iterable<ContainerT> && ...)
decltype(auto)
izip (ContainerT&&... data)
{
// Assume that all containers are the same size and create a range
// that will supply integral indices over the first container.
//
// The call to `zip` will peform more rigorous tests on the sizes
// of the container collection.
return zip (
iota (
std::size (::cruft::variadic::get<0> (data...))
),
std::forward<ContainerT> (data)...
);
}
}