/* * 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 #include "backports-ranges.h" enum class zip_sequence_length_selector : uint32_t; 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 requires(requires { typename std::tuple_size::type; }) typename std::tuple_size::type get_static_size(const T &); std::nullptr_t get_static_size(...); /* Given a zip_sequence_length_selector and an index N in a sequence, evaluate * to std::true_type if the element at index N is examined, and otherwise * std::false_type. * * This is a convenience type to keep the test in one place, rather than * duplicating the cast+mask everywhere it is needed. */ template using examine_zip_element = std::integral_constant(examine_end_range) & (1u << N)))>; /* Given a zip_sequence_length_selector and a sequence of static sizes In, * return a new sequence of static sizes On where: * - If index N is selected, On=In. * - Otherwise, On=std::nullptr_t. * * The output sequence discards static size information for elements that will * not be selected at runtime. */ template std::tuple::value, sizeN, std::nullptr_t>::type ...> get_examined_static_size(std::index_sequence, std::tuple); template struct minimum_static_size; /* A tuple of one element is trivially the size from that element, regardless * of the value of size. */ template struct minimum_static_size> { using type = size; }; /* If the first element is indeterminate, drop it and use the remainder of the * sequence. */ template requires(sizeof...(sizeN) > 0) struct minimum_static_size> : minimum_static_size> { }; /* If the second element is indeterminate, drop it and use the remainder of the * sequence. */ template requires(requires { /* Disable size0 == std::nullptr_t, since that would make * minimum_static_size ambiguous between * this definition and the preceding one. */ size0::value; }) struct minimum_static_size> : minimum_static_size> { }; /* If both of the first two elements are determinate, use a sequence consisting * of the lesser of those two, and all the other values passed through without * change. Subsequent iterations of the template will fold down the remaining * values until the base case is reached. */ template requires(requires { size0::value < size1::value; }) struct minimum_static_size> : minimum_static_size::type, sizeN...>> { }; 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 *); template requires( examine_end_range != zip_sequence_length_selector{} && sizeof...(range_sentinel_type) > 0 ) struct zip_sentinel : public std::tuple { using std::tuple::tuple; }; /* Given as inputs: * - a selector mask * - a tuple of iterator types * - a std::index_sequence of the same length as the tuple * * Produce a type defined as zip_sentinel where On=In if the iterator is * selected in by the mask, and On=std::ignore if the iterator is not selected. * The resulting tuple will then collapse down, avoiding the need to save * non-selected types In in the end iterator. */ template requires(mask != zip_sequence_length_selector{} && sizeof...(range_index) == sizeof...(range_type)) zip_sentinel::value, decltype(std::ranges::end(std::declval())), decltype(std::ignore)>::type ...> iterator_end_type(std::tuple, std::index_sequence); template static constexpr auto capture_end_iterator(range &&r) { if constexpr (std::is_same::value) return std::ignore; else return std::ranges::end(r); } template requires(sizeof...(N) == sizeof...(range)) static constexpr auto capture_end_iterators(std::index_sequence, range &&...r) { return sentinel_type(capture_end_iterator(std::declval()))>(r)...); } template static constexpr auto compare_zip_iterator(const zip_iterator &l, const zip_sentinel &r) { if constexpr (mask) /* For each included member of the iterator, check if the member compares * equal between the two instances. Excluded members use the else * block. */ return std::get(l) == std::get(r); else { /* For each excluded member of the iterator, do nothing. Excluded members * are not checked for equality, because excluded members are omitted from * the sentinel, instead of being set to the value of `rangeN.end()`, * so examining such members is not possible. */ (void)l; (void)r; return std::false_type{}; } } template constexpr bool compare_zip_iterators(const zip_iterator &l, const zip_sentinel &r, std::index_sequence) { /* Note the use of `||`, which is atypical for an equality comparison. By * design, a zip iterator should terminate when any of the component * sequences reaches its end, so use of `||` is correct for that purpose. */ return (compare_zip_iterator::value, N>(l, r) || ... || false); } } } /* This iterator terminates when the selected zipped range(s) terminate. 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 selected zipped range(s) are not longer than any ignored * 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 any selected 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 shortest selected 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 : range_iterator_type { protected: using index_sequence_type = std::make_index_sequence::value>; public: using range_iterator_type::range_iterator_type; using difference_type = decltype(std::get<0>(std::declval()) - std::get<0>(std::declval())); using value_type = decltype(d_zip::detail::dereference_iterator(std::declval(), index_sequence_type())); using pointer = value_type *; using reference = value_type &; using iterator_category = std::forward_iterator_tag; auto operator*() const { return d_zip::detail::dereference_iterator(*this, index_sequence_type()); } zip_iterator &operator++() { d_zip::detail::increment_iterator(*this, index_sequence_type()); return *this; } /* operator++(int) is currently unused, but is required to satisfy * the concept check on forward iterator. */ zip_iterator operator++(int) { auto result = *this; d_zip::detail::increment_iterator(*this, index_sequence_type()); return result; } auto operator-(const zip_iterator &i) const { return std::get<0>(static_cast(*this)) - std::get<0>(static_cast(i)); } constexpr bool operator==(const range_sentinel_type &i) const { return d_zip::detail::compare_zip_iterators(*this, i, index_sequence_type()); } }; template < zip_sequence_length_selector examine_end_range, /* Tuple types in range_all_static_sizes are either an * integral_constant reporting the static size of the corresponding * range, or std::nullptr_t if the range has no known static size. */ typename range_all_static_sizes, /* Type minimum_all_static_size is an integral_constant reporting the * static size of the shortest static range, or std::nullptr_t if no * ranges have a known static size. */ typename minimum_all_static_size = typename d_zip::detail::minimum_static_size::type, /* Filter range_all_static_sizes to discard static sizes for elements * that will not be examined at runtime. */ typename range_examined_static_sizes = decltype(d_zip::detail::get_examined_static_size(std::make_index_sequence::value>(), std::declval())), /* Type minimum_examined_static_size is an integral_constant reporting * the static size of the shortest examined static range, or * std::nullptr_t if no examined ranges have a known static size. */ typename minimum_examined_static_size = typename d_zip::detail::minimum_static_size::type > concept zip_static_size_bounds_check = ( /* If no range has a static size, then minimum_examined_static_size * is nullptr_t and this requirement is skipped. Otherwise, at * least one examined range has a static size, so check that it is * not longer than an unexamined static range. */ (std::is_same::value || (minimum_examined_static_size::value <= minimum_all_static_size::value)) ); template < zip_sequence_length_selector examine_end_range, typename... rangeN_type > concept zip_input_constraints = ( zip_static_size_bounds_check< examine_end_range, std::tuple()))...> > ); template class zip : zip_iterator { range_sentinel_type m_end; public: using index_type = range_index_type; using iterator = zip_iterator; template requires( sizeof...(rangeN_type) > 0 && (std::ranges::borrowed_range && ...) ) constexpr zip(rangeN_type &&... rN) : iterator(std::ranges::begin(rN)...), m_end(d_zip::detail::capture_end_iterators(typename iterator::index_sequence_type(), rN...)) { } template constexpr zip(std::integral_constant, rangeN_type &&... rN) : zip(std::forward(rN)...) { } [[nodiscard]] iterator begin() const { return *this; } [[nodiscard]] range_sentinel_type end() const { return m_end; } }; /* zip() is always a view onto another range, and never owns the storage that * its iterators reference. */ template inline constexpr bool std::ranges::enable_borrowed_range> = true; template using zip_sequence_selector = std::integral_constant; template requires( zip_input_constraints ) zip(zip_sequence_selector, rangeN &&... rN) -> zip< /* range_index_type = */ decltype(d_zip::detail::range_index_type(static_cast *>(nullptr))), /* range_iterator_type = */ std::tuple()))...>, /* range_sentinel_type = */ decltype( d_zip::detail::iterator_end_type( std::declval>(), std::make_index_sequence() ) ) >; /* Store the default selector as a default template argument, so that the * template-id specified by the deduction guide can use the same text in both * the explicit-selector and implicit-selector guides. */ template requires( zip_input_constraints ) zip(rangeN &&... rN) -> zip< /* range_index_type = */ decltype(d_zip::detail::range_index_type(static_cast *>(nullptr))), /* range_iterator_type = */ std::tuple()))...>, /* range_sentinel_type = */ decltype( d_zip::detail::iterator_end_type( std::declval>(), std::make_index_sequence() ) ) >;