blob: 7d688ec094b2850ad68c56de03672252d554878d [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TYPES_ZIP_H_
#define BASE_TYPES_ZIP_H_
#include <algorithm>
#include <iterator>
#include <tuple>
#include <utility>
#include "base/check.h"
#include "base/compiler_specific.h"
namespace base {
namespace internal {
template <typename... Ranges>
class Zipper {
public:
constexpr explicit Zipper(Ranges&... ranges LIFETIME_BOUND) noexcept
: ranges_(ranges...) {}
struct ZipEnd;
class iterator {
public:
using value_type = std::tuple<
std::remove_cv_t<decltype(*std::begin(std::declval<Ranges&>()))>...>;
using reference = value_type;
using element_type = value_type;
using difference_type = std::ptrdiff_t;
using pointer = void;
// TODO(https://crbug.com/377940847): This could be improved going forward
// to select a better iterator category, based on the common denominator of
// the union of iterators, for instance output iterators, etc.
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::input_iterator_tag;
constexpr iterator& operator++() noexcept LIFETIME_BOUND {
advance(std::index_sequence_for<Ranges...>{});
return *this;
}
constexpr iterator operator++(int) noexcept LIFETIME_BOUND {
auto it = *this;
advance(std::index_sequence_for<Ranges...>{});
return it;
}
constexpr auto operator*() const noexcept LIFETIME_BOUND {
return deref(std::index_sequence_for<Ranges...>{});
}
// Determines if the iterator has reached the end, so a for-range loop bails
// out.
constexpr bool operator!=(ZipEnd) const noexcept LIFETIME_BOUND {
return has_more(std::index_sequence_for<Ranges...>{});
}
private:
friend class Zipper;
constexpr explicit iterator(
std::tuple<decltype(std::begin(std::declval<Ranges&>()))...> begin
LIFETIME_BOUND,
std::tuple<decltype(std::end(std::declval<Ranges&>()))...> end
LIFETIME_BOUND) noexcept
: begin_(begin), end_(end) {}
// Checks if any range has reached the end.
template <std::size_t... Is>
constexpr bool has_more(std::index_sequence<Is...>) const {
return (... && (std::get<Is>(begin_) != std::get<Is>(end_)));
}
template <std::size_t... Is>
constexpr void advance(std::index_sequence<Is...>) {
CHECK(operator!=(ZipEnd()));
// SAFETY: The increment is safe as it has been just CHECKed so it is
// guaranteed to be inside [begin_, end_).
UNSAFE_BUFFERS((++std::get<Is>(begin_), ...));
}
template <size_t... Is>
constexpr value_type deref(std::index_sequence<Is...>) const
LIFETIME_BOUND {
return {*std::get<Is>(begin_)...};
}
std::tuple<decltype(std::begin(std::declval<Ranges&>()))...> begin_;
std::tuple<decltype(std::end(std::declval<Ranges&>()))...> end_;
};
// A sentinel used by the iterator to constrain the comparison to make sure it
// has the proper end of each range.
struct ZipEnd {
constexpr bool operator==(iterator it) const { return it == *this; }
};
constexpr iterator begin() noexcept LIFETIME_BOUND {
return begin_impl(std::index_sequence_for<Ranges...>{});
}
constexpr ZipEnd end() noexcept { return ZipEnd(); }
private:
template <size_t... Is>
constexpr iterator begin_impl(std::index_sequence<Is...>) LIFETIME_BOUND {
return iterator(std::make_tuple(std::begin(std::get<Is>(ranges_))...),
std::make_tuple(std::end(std::get<Is>(ranges_))...));
}
std::tuple<Ranges&...> ranges_;
};
} // namespace internal
// Zipping utility that allows iterating over multiple ranges in lockstep.
//
// Example:
//
// std::vector<int> a = {1, 2, 3};
// std::vector<double> b = {4.5, 5.5, 6.5};
// std::vector<std::string> c = {"x", "y", "z"};
// for (auto [x, y, z] : zip(a, b, c)) {
// LOG(INFO) << x << " " << y << " " << z;
// }
//
// Zipping will carry on until one of the ranges run out, at which the loop will
// bail.
template <typename... Ranges>
constexpr internal::Zipper<Ranges...> zip(Ranges&... ranges LIFETIME_BOUND) {
return internal::Zipper<Ranges...>(ranges...);
}
} // namespace base
#endif // BASE_TYPES_ZIP_H_