Turi Create  4.0
Span.hpp
1 /* Copyright © 2020 Apple Inc. All rights reserved.
2  *
3  * Use of this source code is governed by a BSD-3-clause license that can
4  * be found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause
5  */
6 
7 #pragma once
8 
9 #include <array>
10 #include <limits>
11 #include <type_traits>
12 #include <vector>
13 
14 #include <core/util/Verify.hpp>
15 
16 namespace turi {
17 
18 constexpr std::size_t DynamicExtent = std::numeric_limits<std::size_t>::max();
19 
20 namespace span_helpers {
21 
22 //----------------------------------------------------------------------
23 // helper traits
24 //----------------------------------------------------------------------
25 
26 template <size_t Extent>
27 struct IsDynamicExtent {
28  static constexpr bool value = false;
29 };
30 
31 template <>
32 struct IsDynamicExtent<DynamicExtent> {
33  static constexpr bool value = true;
34 };
35 
36 template <size_t Index, size_t Extent>
37 struct IsIndexValid {
38  static constexpr bool value = (Index < Extent);
39 };
40 
41 template <size_t Index>
42 struct IsIndexValid<Index, DynamicExtent> {
43  static constexpr bool value = false;
44 };
45 
46 //----------------------------------------------------------------------
47 // helper storage size
48 //----------------------------------------------------------------------
49 
50 template <size_t Extent>
51 class SpanSize final {
52  public:
53  SpanSize() = default;
54  ~SpanSize() = default;
55 
56  constexpr size_t Size() const noexcept { return size_; }
57 
58  private:
59  static constexpr size_t size_ = Extent;
60 };
61 
62 template <>
63 class SpanSize<DynamicExtent> final {
64  public:
65  SpanSize() = delete;
66  ~SpanSize() = default;
67 
68  SpanSize(size_t size)
69  : size_(size)
70  {
71  }
72 
73  size_t Size() const noexcept { return size_; }
74 
75  private:
76  size_t size_;
77 };
78 
79 } // namespace span_helpers
80 
81 //----------------------------------------------------------------------
82 // Span<T, Extent> is a custom implementation of an array view, similar
83 // to std::span introduced in C++20.
84 //
85 // If Extent is specified, Span supports compile-time bounds checking
86 // when the Get<> method is used.
87 //
88 // This version of Span also supports iterating slices and dimensions
89 // of multi-dimensional contiguous memory blocks.
90 //----------------------------------------------------------------------
91 
92 template <typename T, size_t Extent = DynamicExtent>
93 class Span final {
94  public:
95  using value_type = T;
96  using pointer = typename std::add_pointer<value_type>::type;
97  using reference = typename std::add_lvalue_reference<value_type>::type;
98  using iterator = pointer;
99 
100  using const_value_type = typename std::add_const<value_type>::type;
101  using const_pointer = typename std::add_pointer<const_value_type>::type;
102  using const_iterator = const_pointer;
103 
104  template <size_t Extent_>
105  using SpanSize = span_helpers::SpanSize<Extent_>;
106 
107  template <size_t Extent_>
108  using IsDynamicExtent = span_helpers::IsDynamicExtent<Extent_>;
109 
110  template <size_t Index, size_t Extent_>
111  using IsIndexValid = span_helpers::IsIndexValid<Index, Extent_>;
112 
113  class SliceIterator final {
114  public:
115  SliceIterator(pointer p, size_t stride)
116  : ptr_(p)
117  , stride_(stride)
118  {
119  }
120 
121  bool operator==(const SliceIterator& other) const noexcept
122  {
123  return ptr_ == other.ptr_ && stride_ == other.stride_;
124  }
125 
126  bool operator!=(const SliceIterator& other) const noexcept { return !(*this == other); }
127 
128  SliceIterator& operator++() noexcept
129  {
130  ptr_ += stride_;
131  return *this;
132  }
133 
134  SliceIterator operator++(int) const noexcept { return SliceIterator(ptr_ + stride_, stride_); }
135 
136  Span<T> operator*() const { return Span<T>(ptr_, stride_); }
137 
138  private:
139  pointer ptr_;
140  size_t stride_;
141  };
142 
143  template <size_t Stride>
144  class StaticSliceIterator final {
145  public:
146  StaticSliceIterator(pointer p)
147  : ptr_(p)
148  {
149  }
150 
151  bool operator==(const StaticSliceIterator<Stride>& other) const noexcept
152  {
153  return ptr_ == other.ptr_;
154  }
155 
156  bool operator!=(const StaticSliceIterator<Stride>& other) const noexcept
157  {
158  return !(*this == other);
159  }
160 
161  StaticSliceIterator& operator++() noexcept
162  {
163  ptr_ += Stride;
164  return *this;
165  }
166 
167  StaticSliceIterator operator++(int) const noexcept
168  {
169  return StaticSliceIterator<Stride>(ptr_ + Stride);
170  }
171 
172  Span<T, Stride> operator*() const { return Span<T, Stride>(ptr_); }
173 
174  private:
175  pointer ptr_;
176  };
177 
178  template <typename Iterator>
179  class IteratorProvider final {
180  public:
181  IteratorProvider(Iterator begin, Iterator end)
182  : begin_(begin)
183  , end_(end)
184  {
185  }
186 
187  Iterator begin() const { return begin_; }
188 
189  Iterator end() const { return end_; }
190 
191  private:
192  Iterator begin_;
193  Iterator end_;
194  };
195 
196  ~Span() = default;
197 
198  Span(const Span<T, Extent>&) = default;
199  Span(Span<T, Extent>&&) = default;
200 
201  Span<T, Extent>& operator=(const Span<T, Extent>&) = default;
202  Span<T, Extent>& operator=(Span<T, Extent>&&) = default;
203 
204  template <size_t Extent__ = Extent,
205  typename std::enable_if<IsDynamicExtent<Extent__>::value, int>::type = 0>
206  Span()
207  : ptr_(nullptr)
208  , size_(0)
209  {
210  }
211 
212  template <size_t Extent__ = Extent,
213  typename std::enable_if<!IsDynamicExtent<Extent__>::value, int>::type = 0>
214  Span(pointer p)
215  : ptr_(p)
216  {
217  }
218 
219  template <size_t Extent__ = Extent,
220  typename std::enable_if<IsDynamicExtent<Extent__>::value, int>::type = 0>
221  Span(pointer p, size_t size)
222  : ptr_(size == 0 ? nullptr : p)
223  , size_(size)
224  {
225  }
226 
227  //
228  // properties
229  //
230 
231  pointer Data() const noexcept { return ptr_; }
232 
233  size_t Size() const noexcept { return size_.Size(); }
234 
235  constexpr bool IsEmpty() const noexcept { return Size() == 0; }
236 
237  //
238  // random access
239  //
240 
241  reference operator[](size_t index) const
242  {
243  VerifyDebugIsTrue(index < Size(), TuriErrorCode::IndexOutOfBounds);
244  return ptr_[index];
245  }
246 
247  reference At(size_t index) const
248  {
249  VerifyIsTrue(index < Size(), TuriErrorCode::IndexOutOfBounds);
250  return ptr_[index];
251  }
252 
253  // Get<N>() returns a reference to the value at index N.
254  // This method only exists for fixed-sized Span instantiations.
255  // The bounds of N are compile-time checked.
256  template <size_t Index, typename std::enable_if<!IsDynamicExtent<Extent>::value &&
257  IsIndexValid<Index, Extent>::value,
258  int>::type = 0>
259  reference Get() const
260  {
261  return (*this)[Index];
262  }
263 
264  //
265  // slicing
266  //
267 
268  Span<T> Slice(size_t index) const
269  {
270  VerifyIsTrue(index < Size(), TuriErrorCode::IndexOutOfBounds);
271  return Span<T>(Data() + index, Size() - index);
272  }
273 
274  Span<T> Slice(size_t index, size_t size) const
275  {
276  VerifyIsTrue(size > 0 && index < Size() && index + size <= Size(),
277  TuriErrorCode::IndexOutOfBounds);
278  return Span<T>(Data() + index, size);
279  }
280 
281  Span<T> SliceByDimension(size_t num_slices, size_t slice_index) const
282  {
283  VerifyIsTrue(Size() % num_slices == 0, TuriErrorCode::IndexOutOfBounds);
284  size_t stride = Size() / num_slices;
285  return Slice(slice_index * stride, stride);
286  }
287 
288  //
289  // reinterpreting data
290  //
291 
292  template <size_t NewExtent>
293  Span<T, NewExtent> StaticResize() const
294  {
295  VerifyIsTrue(NewExtent <= Size(), TuriErrorCode::IndexOutOfBounds);
296  return Span<T, NewExtent>(Data());
297  }
298 
299  //
300  // basic C++ iterators
301  //
302 
303  iterator begin() const noexcept { return Data(); }
304 
305  iterator end() const noexcept { return Data() + Size(); }
306 
307  const_iterator cbegin() const noexcept { return Data(); }
308 
309  const_iterator cend() const noexcept { return Data() + Size(); }
310 
311  //
312  // complex C++ iterators
313  //
314 
315  IteratorProvider<SliceIterator> IterateSlices(size_t sliceSize) const
316  {
317  VerifyIsTrue(Size() % sliceSize == 0, TuriErrorCode::IndexOutOfBounds);
318 
319  return IteratorProvider<SliceIterator>(SliceIterator(Data(), sliceSize),
320  SliceIterator(Data() + Size(), sliceSize));
321  }
322 
323  template <size_t SliceSize>
324  IteratorProvider<StaticSliceIterator<SliceSize>> IterateSlices() const
325  {
326  VerifyIsTrue(Size() % SliceSize == 0, TuriErrorCode::IndexOutOfBounds);
327 
328  return IteratorProvider<StaticSliceIterator<SliceSize>>(
329  StaticSliceIterator<SliceSize>(Data()), StaticSliceIterator<SliceSize>(Data() + Size()));
330  }
331 
332  IteratorProvider<SliceIterator> IterateByDimension(size_t dim) const
333  {
334  return IterateSlices(Size() / dim);
335  }
336 
337  private:
338  pointer ptr_;
339  SpanSize<Extent> size_;
340 };
341 
342 // MakeSpan for std::vector<T> yields Span<T, DynamicExtent>
343 // Examples:
344 // (1) create a mutable span
345 // std::vector<int> v = { 1, 2, 3 };
346 // auto span = MakeSpan(v); // span is Span<int>
347 // (2) create an immutable span from a mutable vector
348 // std::vector<int> v = { 1, 2, 3 };
349 // auto span = MakeSpan<const int>(v); // span is Span<const int>
350 // (3) create an immutable span
351 // const std::vector<int> v = { 1, 2, 3 };
352 // auto span = MakeSpan(v); // span is Span<const int>
353 
354 template <typename T>
355 Span<T> MakeSpan(std::vector<T>& v) noexcept
356 {
357  return Span<T>(v.data(), v.size());
358 }
359 
360 template <typename T, typename MutableT = typename std::remove_const<T>::type>
361 Span<T> MakeSpan(const std::vector<MutableT>& v) noexcept
362 {
363  return Span<T>(v.data(), v.size());
364 }
365 
366 template <typename T, typename ConstT = typename std::add_const<T>::type>
367 Span<ConstT> MakeSpan(const std::vector<T>& v) noexcept
368 {
369  return Span<ConstT>(v.data(), v.size());
370 }
371 
372 // MakeSpan for std::array<T, N> yields Span<T, N>.
373 // Examples:
374 // (1) create a mutable span
375 // std::array<int, 3> v = { 1, 2, 3 };
376 // auto span = MakeSpan(v); // span is Span<int, 3>
377 // (2) create an immutable span from a mutable vector
378 // std::array<int, 3> v = { 1, 2, 3 };
379 // auto span = MakeSpan<const int>(v); // span is Span<const int, 3>
380 // (3) create an immutable span
381 // const std::array<int, 3> v = { 1, 2, 3 };
382 // auto span = MakeSpan(v); // span is Span<const int, 3>
383 
384 template <typename T, size_t N>
385 Span<T, N> MakeSpan(std::array<T, N>& v) noexcept
386 {
387  return Span<T, N>(v.data());
388 }
389 
390 template <typename T, size_t N, typename MutableT = typename std::remove_const<T>::type>
391 Span<T, N> MakeSpan(const std::array<MutableT, N>& v) noexcept
392 {
393  return Span<T, N>(v.data());
394 }
395 
396 template <typename T, size_t N, typename ConstT = typename std::add_const<T>::type>
397 Span<ConstT, N> MakeSpan(const std::array<T, N>& v) noexcept
398 {
399  return Span<ConstT, N>(v.data());
400 }
401 
402 } // namespace turi