Vc  1.4.2
SIMD Vector Classes for C++
subscript.h
1 /* This file is part of the Vc library. {{{
2 Copyright © 2013-2015 Matthias Kretz <kretz@kde.org>
3 
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
6  * Redistributions of source code must retain the above copyright
7  notice, this list of conditions and the following disclaimer.
8  * Redistributions in binary form must reproduce the above copyright
9  notice, this list of conditions and the following disclaimer in the
10  documentation and/or other materials provided with the distribution.
11  * Neither the names of contributing organizations nor the
12  names of its contributors may be used to endorse or promote products
13  derived from this software without specific prior written permission.
14 
15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
19 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 }}}*/
27 
28 #ifndef VC_COMMON_SUBSCRIPT_H_
29 #define VC_COMMON_SUBSCRIPT_H_
30 
31 #include <initializer_list>
32 #include <type_traits>
33 #include <vector>
34 #include "types.h"
35 #include "macros.h"
36 #include <assert.h>
37 
38 namespace Vc_VERSIONED_NAMESPACE
39 {
40 namespace Common
41 {
42 // AdaptSubscriptOperator {{{
43 template <typename Base> class AdaptSubscriptOperator : public Base
44 {
45 public:
46  // perfect forward all Base constructors
47  template <typename... Args>
48  Vc_ALWAYS_INLINE AdaptSubscriptOperator(Args &&... arguments)
49  : Base(std::forward<Args>(arguments)...)
50  {
51  }
52 
53  // perfect forward all Base constructors
54  template <typename T>
55  Vc_ALWAYS_INLINE AdaptSubscriptOperator(std::initializer_list<T> l)
56  : Base(l)
57  {
58  }
59 
60  // explicitly enable Base::operator[] because the following would hide it
61  using Base::operator[];
62 
64  template <typename I,
65  typename = enable_if<!std::is_arithmetic<
66  typename std::decay<I>::type>::value> // arithmetic types
67  // should always use
68  // Base::operator[] and
69  // never match this one
70  >
71  Vc_ALWAYS_INLINE auto operator[](I &&arg_)
72  -> decltype(subscript_operator(*this, std::forward<I>(arg_)))
73  {
74  return subscript_operator(*this, std::forward<I>(arg_));
75  }
76 
77  // const overload of the above
78  template <typename I, typename = enable_if<
79  !std::is_arithmetic<typename std::decay<I>::type>::value>>
80  Vc_ALWAYS_INLINE auto operator[](I &&arg_) const
81  -> decltype(subscript_operator(*this, std::forward<I>(arg_)))
82  {
83  return subscript_operator(*this, std::forward<I>(arg_));
84  }
85 };
86 
87 // }}}
88 // is_valid_indexvector {{{
89 template <class T, class = decltype(convertIndexVector(std::declval<T>()))>
90 std::true_type is_valid_indexvector(T &&);
91 std::false_type is_valid_indexvector(...);
92 
93 template <class IndexVector, class Test = decltype(is_valid_indexvector(
94  std::declval<const IndexVector &>()))>
95 struct is_valid_indexvector_ : public std::integral_constant<bool, Test::value> {
96 };
97 static_assert(!is_valid_indexvector_<const int *>::value,
98  "Pointer is incorrectly classified as valid index vector type");
99 static_assert(is_valid_indexvector_<const int[4]>::value,
100  "C-Array is incorrectly classified as invalid index vector type");
101 
102 // }}}
103 // apply Scale (std::ratio) functions {{{1
104 template <typename Scale, typename T>
105 Vc_ALWAYS_INLINE enable_if<Scale::num == Scale::den, Traits::decay<T>> applyScale(T &&x)
106 {
107  return std::forward<T>(x);
108 }
109 
110 template <typename Scale, typename T>
111 Vc_ALWAYS_INLINE enable_if<
112  Scale::num != Scale::den && Traits::has_multiply_operator<T, int>::value,
113  Traits::decay<T>>
114  applyScale(T &&x)
115 {
116  static_assert(Scale::num % Scale::den == 0,
117  "Non-integral index scaling requested. This typically happens only for "
118  "Vc::Scalar on 32-bit for gathers on double. You can work around the "
119  "issue by ensuring that all doubles in the structure are aligned on 8 "
120  "Bytes.");
121  constexpr int value = Scale::num / Scale::den;
122  Vc_ASSERT(Vc::all_of((x * value) / value == x));
123  return std::forward<T>(x) * value;
124 }
125 
126 template <typename Scale, typename T>
127 Vc_ALWAYS_INLINE enable_if<
128  Scale::num != Scale::den && !Traits::has_multiply_operator<T, int>::value,
129  T>
130  applyScale(T x)
131 {
132  static_assert(Scale::num % Scale::den == 0,
133  "Non-integral index scaling requested. This typically happens only for "
134  "Vc::Scalar on 32-bit for gathers on double. You can work around the "
135  "issue by ensuring that all doubles in the structure are aligned on 8 "
136  "Bytes.");
137  constexpr int value = Scale::num / Scale::den;
138  for (size_t i = 0; i < x.size(); ++i) {
139  Vc_ASSERT((x[i] * value) / value == x[i]);
140  x[i] *= value;
141  }
142  return x;
143 }
144 
145 template <typename Scale, typename T, typename U,
146  typename = enable_if<Traits::has_multiply_operator<T, int>::value &&
147  Traits::has_addition_operator<T, U>::value>>
148 Vc_ALWAYS_INLINE typename std::decay<T>::type applyScaleAndAdd(T &&x, U &&y)
149 {
150  constexpr int value = Scale::num / Scale::den;
151  if (value == 1) { // static evaluation
152  return std::forward<T>(x) + std::forward<U>(y);
153  }
154  return std::forward<T>(x) * value + std::forward<U>(y);
155 }
156 
157 template <
158  typename Scale, typename T, typename U,
159  typename = enable_if<
160  !(Traits::has_multiply_operator<T &, int>::value &&
161  Traits::has_addition_operator<T &, decltype(std::declval<U>()[0])>::value) &&
162  Traits::has_subscript_operator<U>::value>>
163 Vc_ALWAYS_INLINE T applyScaleAndAdd(T x, U &&y)
164 {
165  constexpr int value = Scale::num / Scale::den;
166  for (size_t i = 0; i < x.size(); ++i) {
167  if (value == 1) { // static evaluation
168  x[i] = x[i] + y[i];
169  } else {
170  x[i] = x[i] * value + y[i];
171  }
172  }
173  return x;
174 }
175 
176 template <typename Scale, typename T, typename U>
177 Vc_ALWAYS_INLINE enable_if<!(Traits::has_multiply_operator<T &, int>::value &&
178  Traits::has_addition_operator<T &, U>::value) &&
179  !Traits::has_subscript_operator<U>::value,
180  T>
181  applyScaleAndAdd(T x, U &&y)
182 {
183  constexpr int value = Scale::num / Scale::den;
184  for (size_t i = 0; i < x.size(); ++i) {
185  if (value == 1) { // static evaluation
186  x[i] = x[i] + y;
187  } else {
188  x[i] = x[i] * value + y;
189  }
190  }
191  return x;
192 }
193 
194 // IndexVectorSizeMatches {{{1
195 template <std::size_t MinSize,
196  typename IndexT,
197  bool = Traits::is_simd_vector<IndexT>::value>
198 struct IndexVectorSizeMatches
199  : public std::true_type // you might expect this should be false_type here, but the point is
200  // that IndexT is a type where the size is not known at compile time.
201  // Thus it may be good but we cannot know from the type. The only check
202  // we could do is a runtime check, but the type is fine.
203 {
204 };
205 
206 template <std::size_t MinSize, typename V>
207 struct IndexVectorSizeMatches<MinSize,
208  V,
209  true> : public std::integral_constant<bool, (MinSize <= V::Size)>
210 {
211 };
212 
213 template <std::size_t MinSize, typename T, std::size_t ArraySize>
214 struct IndexVectorSizeMatches<MinSize,
215  T[ArraySize],
216  false> : public std::integral_constant<bool, (MinSize <= ArraySize)>
217 {
218 };
219 
220 template <std::size_t MinSize, typename T, std::size_t ArraySize>
221 struct IndexVectorSizeMatches<MinSize,
222  std::array<T, ArraySize>,
223  false> : public std::integral_constant<bool, (MinSize <= ArraySize)>
224 {
225 };
226 
227 template <std::size_t MinSize, typename T, std::size_t ArraySize>
228 struct IndexVectorSizeMatches<MinSize,
229  Vc::array<T, ArraySize>,
230  false> : public std::integral_constant<bool, (MinSize <= ArraySize)>
231 {
232 };
233 
234 template <std::size_t MinSize, typename T, std::ptrdiff_t N>
235 struct IndexVectorSizeMatches<MinSize, Vc::Common::span<T, N>, false>
236  : public std::integral_constant<bool, (N == -1 || MinSize <= N)> {
237 };
238 // SubscriptOperation {{{1
239 template <
240  typename T, typename IndexVector, typename Scale = std::ratio<1, 1>,
241  bool = is_valid_indexvector_<IndexVector>::value>
242 class SubscriptOperation
243 {
244  const IndexVector m_indexes;
245  T *const m_address;
246  using ScalarType = typename std::decay<T>::type;
247 
248  using IndexVectorScaled = Traits::decay<decltype(convertIndexVector(std::declval<const IndexVector &>()))>;
249 
250 public:
251  // try to stop the user from forming lvalues of this type
252  SubscriptOperation &operator=(const SubscriptOperation &) = delete;
253  SubscriptOperation(const SubscriptOperation &) = delete;
254 #ifndef __cpp_guaranteed_copy_elision
255  constexpr SubscriptOperation(SubscriptOperation &&) = default;
256 #endif
257 
258  template <typename U,
259  typename = enable_if<((std::is_convertible<const U &, IndexVector>::value ||
260  std::is_same<U, IndexVector>::value) &&
261  std::is_copy_constructible<IndexVector>::value)>>
262  constexpr Vc_ALWAYS_INLINE SubscriptOperation(T *address, const U &indexes)
263  : m_indexes(indexes), m_address(address)
264  {
265  }
266 
267  template <std::size_t... Indexes>
268  constexpr Vc_ALWAYS_INLINE SubscriptOperation(T *address, const IndexVector &indexes,
269  index_sequence<Indexes...>)
270  : m_indexes{indexes[Indexes]...}, m_address(address)
271  {}
272 
273  template <typename U>
274  constexpr Vc_ALWAYS_INLINE SubscriptOperation(
275  T *address, const U &indexes,
276  enable_if<((std::is_convertible<const U &, IndexVector>::value ||
277  std::is_same<U, IndexVector>::value) &&
278  !std::is_copy_constructible<IndexVector>::value &&
279  std::is_array<IndexVector>::value &&
280  std::extent<IndexVector>::value > 0)> = nullarg)
281  : SubscriptOperation(address, indexes,
282  make_index_sequence<std::extent<IndexVector>::value>())
283  {
284  }
285 
286  static constexpr bool need_explicit_scaling =
287  Scale::num % Scale::den != 0 || Scale::num / Scale::den * sizeof(T) > 8;
288 
289  Vc_ALWAYS_INLINE
290  GatherArguments<typename std::remove_cv<T>::type, IndexVectorScaled,
291  (need_explicit_scaling ? 1 : Scale::num / Scale::den)>
292  gatherArguments() &&
293  {
294  static_assert(std::is_arithmetic<ScalarType>::value,
295  "Incorrect type for a SIMD vector gather. Must be an arithmetic type.");
296  return {applyScale<typename std::conditional<need_explicit_scaling, Scale,
297  std::ratio<1, 1>>::type>(
298  convertIndexVector(m_indexes)),
299  m_address};
300  }
301 
302  Vc_ALWAYS_INLINE ScatterArguments<T, IndexVectorScaled> scatterArguments() &&
303  {
304  static_assert(std::is_arithmetic<ScalarType>::value,
305  "Incorrect type for a SIMD vector scatter. Must be an arithmetic type.");
306  return {applyScale<Scale>(convertIndexVector(m_indexes)), m_address};
307  }
308 
309  template <typename V,
310  typename = enable_if<(std::is_arithmetic<ScalarType>::value &&Traits::is_simd_vector<
311  V>::value &&IndexVectorSizeMatches<V::Size, IndexVector>::value)>>
312  Vc_INTRINSIC operator V() &&
313  {
314  return V(static_cast<SubscriptOperation &&>(*this).gatherArguments());
315  }
316 
317  template <typename V,
318  typename = enable_if<(std::is_arithmetic<ScalarType>::value &&Traits::is_simd_vector<
319  V>::value &&IndexVectorSizeMatches<V::Size, IndexVector>::value)>>
320  Vc_ALWAYS_INLINE SubscriptOperation &operator=(const V &rhs) &&
321  {
322  static_assert(std::is_arithmetic<ScalarType>::value,
323  "Incorrect type for a SIMD vector scatter. Must be an arithmetic type.");
324  const auto indexes = applyScale<Scale>(convertIndexVector(m_indexes));
325  rhs.scatter(m_address, indexes);
326  return *this;
327  }
328 
329  // precondition: m_address points to a struct/class/union
330  template <
331  typename U,
332  typename S, // S must be equal to T. Still we require this template parameter -
333  // otherwise instantiation of SubscriptOperation would only be valid for
334  // structs/unions.
335  typename = enable_if<std::is_same<S, typename std::remove_cv<T>::type>::value &&(
336  std::is_class<T>::value || std::is_union<T>::value)>>
337  Vc_ALWAYS_INLINE auto operator[](U S::*member) &&
338  -> SubscriptOperation<
339  typename std::conditional<std::is_const<T>::value,
340  const typename std::remove_reference<U>::type,
341  typename std::remove_reference<U>::type>::type,
342  IndexVector,
343  // By passing the scale factor as a fraction of integers in the template
344  // arguments the value does not lose information if the division yields a
345  // non-integral value. This could happen e.g. for a struct of struct (S2 {
346  // S1, char }, with sizeof(S1) = 16, sizeof(S2) = 20. Then scale would be
347  // 20/16)
348  std::ratio_multiply<Scale, std::ratio<sizeof(S), sizeof(U)>>>
349  {
350  static_assert(std::is_same<Traits::decay<decltype(m_address->*member)>,
351  Traits::decay<U>>::value,
352  "Type mismatch that should be impossible.");
353  // TODO: check whether scale really works for unions correctly
354  return {&(m_address->*member), m_indexes};
355  }
356 
357  /*
358  * The following functions allow subscripting of nested arrays. But
359  * there are two cases of containers and only one that we want to support:
360  * 1. actual arrays (e.g. T[N] or std::array<T, N>)
361  * 2. dynamically allocated vectors (e.g. std::vector<T>)
362  *
363  * For (1.) the offset calculation is straightforward.
364  * For (2.) the m_address pointer points to memory where pointers are
365  * stored to the actual data. Meaning the data can be scattered
366  * freely in memory (and far away from what m_address points to). Supporting this leads to
367  * serious trouble with the pointer (it does not really point to the start of a memory
368  * region anymore) and inefficient code. The user is better off to write a loop that assigns the
369  * scalars to the vector object sequentially.
370  */
371 
372 private:
373  // The following is a workaround for MSVC 2015 Update 2. Whenever the ratio
374  // in the return type of the following operator[] is encountered with a sizeof
375  // expression that fails, MSVC decides to substitute a 0 for the sizeof instead of
376  // just leaving the ratio instantiation alone via proper SFINAE. The make_ratio helper
377  // ensures that the 0 from the sizeof failure does not reach the denominator of
378  // std::ratio where it would hit a static_assert.
379  template <intmax_t N, intmax_t D> struct make_ratio {
380  using type = std::ratio<N, D == 0 ? 1 : D>;
381  };
382 
383 public:
384  // precondition: m_address points to a type that implements the subscript operator
385  template <typename U>
386  // U is only required to delay name lookup to the 2nd phase (on use).
387  // This is necessary because m_address[0][index] is only a correct
388  // expression if has_subscript_operator<T>::value is true.
389  Vc_ALWAYS_INLINE auto operator[](U index) && -> typename std::enable_if<
390 #ifndef Vc_IMPROVE_ERROR_MESSAGES
391  Traits::has_no_allocated_data<T>::value &&
392 #endif
393  std::is_convertible<U, size_t>::value,
394  SubscriptOperation<
395  // the following decltype expression must depend on index and cannot
396  // simply use [0][0] because it would yield an invalid expression in
397  // case m_address[0] returns a struct/union
398  typename std::remove_reference<decltype(m_address[0][index])>::type,
399  IndexVector,
400  std::ratio_multiply<
401  Scale,
402  typename make_ratio<sizeof(T), sizeof(m_address[0][index])>::type>>>::type
403  {
404  static_assert(Traits::has_subscript_operator<T>::value,
405  "The subscript operator was called on a type that does not implement it.\n");
406  static_assert(Traits::has_no_allocated_data<T>::value,
407  "Invalid container type in gather/scatter operation.\nYou may only use "
408  "nested containers that store the data inside the object (such as builtin "
409  "arrays or std::array) but not containers that store data in allocated "
410  "memory (such as std::vector).\nSince this feature cannot be queried "
411  "generically at compile time you need to spezialize the "
412  "Vc::Traits::has_no_allocated_data_impl<T> type-trait for custom types that "
413  "meet the requirements.\n");
414  static_assert(std::is_lvalue_reference<decltype(m_address[0][index])>::value,
415  "The container does not return an lvalue reference to the data at "
416  "the requested offset. This makes it impossible to execute a "
417  "gather operation.\n");
418  return {&(m_address[0][index]), m_indexes};
419  }
420 
421  // precondition: m_address points to a type that implements the subscript operator
422  template <typename IT>
423  Vc_ALWAYS_INLINE typename std::enable_if<
424 #ifndef Vc_IMPROVE_ERROR_MESSAGES
425  Traits::has_no_allocated_data<T>::value &&
426  Traits::has_subscript_operator<T>::value &&
427 #endif
428  Traits::has_subscript_operator<IT>::value,
429  SubscriptOperation<typename std::remove_reference<decltype(
430  m_address[0][std::declval<
431  const IT &>()[0]] // std::declval<IT>()[0] could
432  // be replaced with 0 if it
433  // were not for two-phase lookup. We need to make the
434  // m_address[0][0] expression dependent on IT
435  )>::type,
436  IndexVectorScaled,
437  std::ratio<1, 1> // reset Scale to 1 since it is applied below
438  >>::type
439  operator[](const IT &index) &&
440  {
441  static_assert(Traits::has_subscript_operator<T>::value,
442  "The subscript operator was called on a type that does not implement it.\n");
443  static_assert(Traits::has_no_allocated_data<T>::value,
444  "Invalid container type in gather/scatter operation.\nYou may only use "
445  "nested containers that store the data inside the object (such as builtin "
446  "arrays or std::array) but not containers that store data in allocated "
447  "memory (such as std::vector).\nSince this feature cannot be queried "
448  "generically at compile time you need to spezialize the "
449  "Vc::Traits::has_no_allocated_data_impl<T> type-trait for custom types that "
450  "meet the requirements.\n");
451  return {&(m_address[0][0]),
452  applyScaleAndAdd<std::ratio_multiply<
453  Scale, std::ratio<sizeof(T), sizeof(m_address[0][0])>>>(
454  convertIndexVector(m_indexes), index)};
455  }
456 };
457 
458 // specialization for invalid IndexVector type
459 template <typename T, typename IndexVector, typename Scale>
460 class SubscriptOperation<T, IndexVector, Scale, false>;
461 
462 // subscript_operator {{{1
463 template <
464  typename Container,
465  typename IndexVector,
466  typename = enable_if<
467  Traits::has_subscript_operator<IndexVector>::value // The index vector must provide [] for
468  // the implementations of gather/scatter
469  &&Traits::has_contiguous_storage<Container>::value // Container must use contiguous
470  // storage, otherwise the index vector
471  // cannot be used as memory offsets, which is required for efficient
472  // gather/scatter implementations
473  &&std::is_lvalue_reference<decltype(*begin(std::declval<
474  Container>()))>::value // dereferencing the begin iterator must yield an lvalue
475  // reference (const or non-const). Otherwise it is not possible
476  // to determine a pointer to the data storage (see above).
477  >>
478 Vc_ALWAYS_INLINE SubscriptOperation<
479  typename std::remove_reference<decltype(*begin(std::declval<Container>()))>::
480  type, // the type of the first value in the container is what the internal array pointer
481  // has to point to. But if the subscript operator of the container returns a
482  // reference we need to drop that part because it's useless information for us. But
483  // const and volatile, as well as array rank/extent are interesting and need not be
484  // dropped.
485  typename std::remove_const<typename std::remove_reference<
486  IndexVector>::type>::type // keep volatile and possibly the array extent, but the const and
487  // & parts of the type need to be removed because
488  // SubscriptOperation explicitly adds them for its member type
489  > subscript_operator(Container &&c, IndexVector &&indexes)
490 {
491  Vc_ASSERT(std::addressof(*begin(c)) + 1 ==
492  std::addressof(*(begin(c) + 1))); // runtime assertion for contiguous storage, this
493  // requires a RandomAccessIterator - but that
494  // should be given for a container with contiguous
495  // storage
496  return {std::addressof(*begin(c)), std::forward<IndexVector>(indexes)};
497 }
498 
508 template <typename Container, typename I>
509 Vc_ALWAYS_INLINE Vc::Common::SubscriptOperation<
510  typename std::remove_reference<decltype(std::declval<Container>()[0])>::type,
511  const std::initializer_list<I> &> subscript_operator(Container &&vec,
512  const std::initializer_list<I> &indexes)
513 {
514  return {&vec[0], indexes};
515 }
516 //}}}1
517 
518 } // namespace Common
519 
520 using Common::subscript_operator;
521 
522 } // namespace Vc
523 
524 #endif // VC_COMMON_SUBSCRIPT_H_
525 
526 // vim: foldmethod=marker
Vc::all_of
constexpr bool all_of(const Mask &m)
Definition: mask.h:397