/* * 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))...); } /* The overloads of get_static_size are declared, but never defined. They * exist only for use in decltype() expressions. */ template std::integral_constant get_static_size(const T (&)[N]); template std::integral_constant get_static_size(const std::array &); std::nullptr_t get_static_size(...); template ())), typename rangeN_extent = decltype(get_static_size(std::declval()))> struct check_static_size_pair : std::true_type { static_assert(range0_extent::value <= rangeN_extent::value, "first range is longer than a later range"); }; template struct check_static_size_pair : std::true_type { }; void range_index_type(...); template < typename... range_type, /* If any `range_type::index_type` is not defined, fail. * If there is no common_type among all the * `range_type::index_type`, fail. */ typename index_type = typename std::common_type::type::index_type &>::type...>::type, /* If the common_type `index_type` is not a suitable argument to all * `range_type::operator[]()`, fail. */ typename = std::void_t().operator[](std::declval()))...> > index_type range_index_type(std::tuple *); } } /* 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 runtime check that the below loop would be * safe, since a check external to the zip_iterator could stop before * undefined behaviour occurs. * * However, if the first range is convertible to a C array of known * length or to a C++ std::array, then there is a compile-time check * that the first range is not longer than any other range that is * likewise convertible. If a range cannot be converted to an array, * then its length is unknown and is not checked. If the first range is * not convertible, then no ranges are checked. 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; /* 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); } protected: 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<1 + sizeof...(rangeN_iterator_type)>; public: using difference_type = typename std::iterator_traits::difference_type; using value_type = decltype(d_zip::detail::dereference_iterator(std::declval(), index_type())); using pointer = value_type *; using reference = value_type &; using iterator_category = std::forward_iterator_tag; 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; } difference_type operator-(const zip_iterator &i) const { return std::get<0>(*this) - std::get<0>(i); } bool operator!=(const zip_iterator &i) const { return std::get<0>(*this) != std::get<0>(i); } bool operator==(const zip_iterator &i) const { return !(*this != i); } }; template class zip : zip_iterator { range0_iterator_type m_end; public: using range_owns_iterated_storage = std::false_type; using iterator = zip_iterator; using index_type = range_index_type; template constexpr zip(range0 &&r0, rangeN &&... rN) : iterator(std::begin(r0), std::begin(rN)...), m_end(std::end(r0)) { using size_r0 = decltype(d_zip::detail::get_static_size(std::declval())); if constexpr (!std::is_same::value) { /* If the first range cannot be measured, then no static * checks are done. */ static_assert( std::conjunction< d_zip::detail::check_static_size_pair< range0, rangeN > ... >::value); } static_assert((!any_ephemeral_range::value), "cannot zip storage of ephemeral ranges"); } [[nodiscard]] iterator begin() const { return *this; } [[nodiscard]] iterator end() const { return this->end_internal(m_end, typename iterator::index_type()); } }; template zip(range0 &&r0, rangeN &&... rN) -> zip *>(nullptr))), decltype(std::begin(r0)), decltype(std::begin(rN))...>;