/*
* This file is part of the DXX-Rebirth project .
* It is copyright by its individual contributors, as recorded in the
* project's Git history. See COPYING.txt at the top level for license
* terms and a link to the Git history.
*/
#pragma once
#include
#include
#include
#include "dxxsconf.h"
#include "ephemeral_range.h"
#include
namespace d_zip {
namespace detail {
template
void discard_arguments(T &&...)
{
}
template
void increment_iterator(std::tuple &iterator, std::index_sequence)
{
/* Order of evaluation is irrelevant, so pass the results to a
* discarding function. This permits the compiler to evaluate the
* expression elements in any order.
*/
discard_arguments(++(std::get(iterator))...);
}
template
auto dereference_iterator(const std::tuple &iterator, std::index_sequence)
{
/* std::make_tuple is not appropriate here, because the result of
* dereferencing the iterator may be a reference, and the resulting
* tuple should store the reference, not the underlying object.
* Calling std::make_tuple would decay such references into the
* underlying type.
*
* std::tie is not appropriate here, because it captures all
* arguments as `T &`, so it fails to compile if the result of
* dereferencing the iterator is not a reference.
*/
return std::tuple<
decltype(*(std::get(iterator)))...
>(*(std::get(iterator))...);
}
}
}
/* This iterator terminates when the first zipped range terminates. The
* caller is responsible for ensuring that use of the zip_iterator does
* not increment past the end of any zipped range. This can be done by
* ensuring that the first zipped range is not longer than any other
* zipped range, or by ensuring that external logic stops the traversal
* before the zip_iterator increments past the end.
*
* There is no initialization time check that the below loop would be
* safe, since a check external to the zip_iterator could stop before
* undefined behaviour occurs.
for (auto i = zip_range.begin(), e = zip_range.end(); i != e; ++i)
{
if (condition())
break;
}
*/
template
class zip_iterator : std::tuple
{
using base_type = std::tuple;
protected:
/* Prior to C++17, range-based for insisted on the same type for
* `begin` and `end`, so method `end_internal` must return a full iterator,
* even though most of it is a waste. To save some work, values that are
* used for ignored fields are default-constructed (if possible)
* instead of copy-constructed from the begin iterator.
*/
template
static typename std::enable_if::value, T>::type end_construct_ignored_element()
{
return T();
}
template
typename std::enable_if::value, T>::type end_construct_ignored_element() const
{
return std::get(*this);
}
template
zip_iterator end_internal(const E0 &e0, std::index_sequence<0, N...>) const
{
return zip_iterator(e0, this->template end_construct_ignored_element::type>()...);
}
using index_type = std::make_index_sequence;
public:
using base_type::base_type;
auto operator*() const
{
return d_zip::detail::dereference_iterator(*this, index_type());
}
zip_iterator &operator++()
{
d_zip::detail::increment_iterator(*this, index_type());
return *this;
}
bool operator!=(const zip_iterator &i) const
{
return std::get<0>(*this) != std::get<0>(i);
}
};
template
class zipped_range : zip_iterator
{
range0_iterator_type m_end;
public:
using range_owns_iterated_storage = std::false_type;
using iterator = zip_iterator;
zipped_range(iterator &&i, range0_iterator_type &&e) :
iterator(std::move(i)), m_end(std::move(e))
{
}
__attribute_warn_unused_result
iterator begin() const { return *this; }
__attribute_warn_unused_result
iterator end() const
{
return this->end_internal(m_end, typename iterator::index_type());
}
};
template
static auto zip(range0 &&r0, rangeN &&... rN)
{
using std::begin;
using std::end;
using R = zipped_range;
static_assert((!any_ephemeral_range::value), "cannot zip storage of ephemeral ranges");
return R(typename R::iterator(begin(r0), begin(rN)...), end(r0));
}