| /* |
| * Copyright 2019 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #ifndef SkZip_DEFINED |
| #define SkZip_DEFINED |
| |
| #include <cstddef> |
| #include <iterator> |
| #include <tuple> |
| #include <type_traits> |
| |
| #include "include/core/SkTypes.h" |
| #include "include/private/SkTemplates.h" |
| #include "include/private/SkTo.h" |
| #include "src/core/SkSpan.h" |
| |
| // Take a list of things that can be pointers, and use them all in parallel. The iterators and |
| // accessor operator[] for the class produce a tuple of the items. |
| template<typename... Ts> |
| class SkZip { |
| using ReturnTuple = std::tuple<Ts&...>; |
| |
| class Iterator { |
| public: |
| using value_type = ReturnTuple; |
| using difference_type = ptrdiff_t; |
| using pointer = value_type*; |
| using reference = value_type; |
| using iterator_category = std::input_iterator_tag; |
| constexpr Iterator(const SkZip* zip, size_t index) : fZip{zip}, fIndex{index} { } |
| constexpr Iterator(const Iterator& that) : Iterator{ that.fZip, that.fIndex } { } |
| constexpr Iterator& operator++() { ++fIndex; return *this; } |
| constexpr Iterator operator++(int) { Iterator tmp(*this); operator++(); return tmp; } |
| constexpr bool operator==(const Iterator& rhs) const { return fIndex == rhs.fIndex; } |
| constexpr bool operator!=(const Iterator& rhs) const { return fIndex != rhs.fIndex; } |
| constexpr reference operator*() { return (*fZip)[fIndex]; } |
| friend constexpr difference_type operator-(Iterator lhs, Iterator rhs) { |
| return lhs.fIndex - rhs.fIndex; |
| } |
| |
| private: |
| const SkZip* const fZip = nullptr; |
| size_t fIndex = 0; |
| }; |
| |
| template<typename T> |
| using make_nullptr = std::integral_constant<std::nullptr_t, nullptr>; |
| |
| public: |
| constexpr SkZip() : fPointers{make_nullptr<Ts*>::value...}, fSize{0} {} |
| constexpr SkZip(size_t) = delete; |
| constexpr SkZip(size_t size, Ts*... ts) |
| : fPointers{ts...} |
| , fSize{size} {} |
| constexpr SkZip(const SkZip& that) = default; |
| |
| // Check to see if U can be used for const T or is the same as T |
| template <typename U, typename T> |
| using CanConvertToConst = typename std::integral_constant<bool, |
| std::is_convertible<U*, T*>::value && sizeof(U) == sizeof(T)>::type; |
| |
| // Allow SkZip<const T> to be constructed from SkZip<T>. |
| template<typename... Us, |
| typename = std::enable_if<skstd::conjunction<CanConvertToConst<Us, Ts>...>::value>> |
| constexpr SkZip(const SkZip<Us...>& that) |
| : fPointers(that.data()) |
| , fSize{that.size()} { } |
| |
| constexpr ReturnTuple operator[](size_t i) const { return this->index(i);} |
| constexpr size_t size() const { return fSize; } |
| constexpr bool empty() const { return this->size() == 0; } |
| constexpr ReturnTuple front() const { return this->index(0); } |
| constexpr ReturnTuple back() const { return this->index(this->size() - 1); } |
| constexpr Iterator begin() const { return Iterator{this, 0}; } |
| constexpr Iterator end() const { return Iterator{this, this->size()}; } |
| template<size_t I> constexpr auto get() const { |
| return SkMakeSpan(std::get<I>(fPointers), fSize); |
| } |
| constexpr std::tuple<Ts*...> data() const { return fPointers; } |
| constexpr SkZip first(size_t n) const { |
| SkASSERT(n <= this->size()); |
| return SkZip{n, fPointers}; |
| } |
| |
| private: |
| constexpr SkZip(size_t n, const std::tuple<Ts*...>& pointers) |
| : fPointers{pointers} |
| , fSize{n} {} |
| |
| constexpr ReturnTuple index(size_t i) const { |
| SkASSERT(this->size() > 0); |
| SkASSERT(i < this->size()); |
| return indexDetail(i, skstd::make_index_sequence<sizeof...(Ts)>{}); |
| } |
| |
| template<std::size_t... Is> |
| constexpr ReturnTuple indexDetail(size_t i, skstd::index_sequence<Is...>) const { |
| return ReturnTuple((std::get<Is>(fPointers))[i]...); |
| } |
| |
| std::tuple<Ts*...> fPointers; |
| size_t fSize; |
| }; |
| |
| class SkMakeZipDetail { |
| template<typename T> struct DecayPointer{ |
| using U = typename std::remove_cv<typename std::remove_reference<T>::type>::type; |
| using type = typename std::conditional<std::is_pointer<U>::value, U, T>::type; |
| }; |
| template<typename T> using DecayPointerT = typename DecayPointer<T>::type; |
| |
| template<typename C> struct ContiguousMemory { }; |
| template<typename T> struct ContiguousMemory<T*> { |
| using value_type = T; |
| static constexpr value_type* Data(T* t) { return t; } |
| static constexpr size_t Size(T* s) { return SIZE_MAX; } |
| }; |
| template<typename T, size_t N> struct ContiguousMemory<T(&)[N]> { |
| using value_type = T; |
| static constexpr value_type* Data(T(&t)[N]) { return t; } |
| static constexpr size_t Size(T(&)[N]) { return N; } |
| }; |
| // In general, we don't want r-value collections, but SkSpans are ok, because they are a view |
| // onto an actual container. |
| template<typename T> struct ContiguousMemory<SkSpan<T>> { |
| using value_type = T; |
| static constexpr value_type* Data(SkSpan<T> s) { return s.data(); } |
| static constexpr size_t Size(SkSpan<T> s) { return s.size(); } |
| }; |
| // Only accept l-value references to collections. |
| template<typename C> struct ContiguousMemory<C&> { |
| using value_type = typename std::remove_pointer<decltype(std::declval<C>().data())>::type; |
| static constexpr value_type* Data(C& c) { return c.data(); } |
| static constexpr size_t Size(C& c) { return c.size(); } |
| }; |
| template<typename C> using Span = ContiguousMemory<DecayPointerT<C>>; |
| template<typename C> using ValueType = typename Span<C>::value_type; |
| |
| template<typename C, typename... Ts> struct PickOneSize { }; |
| template <typename T, typename... Ts> struct PickOneSize<T*, Ts...> { |
| static constexpr size_t Size(T* t, Ts... ts) { |
| return PickOneSize<Ts...>::Size(std::forward<Ts>(ts)...); |
| } |
| }; |
| template <typename T, typename... Ts, size_t N> struct PickOneSize<T(&)[N], Ts...> { |
| static constexpr size_t Size(T(&)[N], Ts...) { return N; } |
| }; |
| template<typename T, typename... Ts> struct PickOneSize<SkSpan<T>, Ts...> { |
| static constexpr size_t Size(SkSpan<T> s, Ts...) { return s.size(); } |
| }; |
| template<typename C, typename... Ts> struct PickOneSize<C&, Ts...> { |
| static constexpr size_t Size(C& c, Ts...) { return c.size(); } |
| }; |
| |
| public: |
| template<typename... Ts> |
| static constexpr auto MakeZip(Ts&& ... ts) { |
| |
| // Pick the first collection that has a size, and use that for the size. |
| size_t size = PickOneSize<DecayPointerT<Ts>...>::Size(std::forward<Ts>(ts)...); |
| |
| #ifdef SK_DEBUG |
| // Check that all sizes are the same. |
| size_t minSize = SIZE_MAX; |
| size_t maxSize = 0; |
| for (size_t s : {Span<Ts>::Size(std::forward<Ts>(ts))...}) { |
| if (s != SIZE_MAX) { |
| minSize = SkTMin(minSize, s); |
| maxSize = SkTMax(maxSize, s); |
| } |
| } |
| SkASSERT(minSize == maxSize); |
| #endif |
| |
| return SkZip<ValueType<Ts>...>{size, Span<Ts>::Data(std::forward<Ts>(ts))...}; |
| } |
| }; |
| |
| template<typename... Ts> |
| inline constexpr auto SkMakeZip(Ts&& ... ts) { |
| return SkMakeZipDetail::MakeZip(std::forward<Ts>(ts)...); |
| } |
| #endif //SkZip_DEFINED |