piranha  0.10
small_vector.hpp
1 /* Copyright 2009-2017 Francesco Biscani (bluescarni@gmail.com)
2 
3 This file is part of the Piranha library.
4 
5 The Piranha library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8  * the GNU Lesser General Public License as published by the Free
9  Software Foundation; either version 3 of the License, or (at your
10  option) any later version.
11 
12 or
13 
14  * the GNU General Public License as published by the Free Software
15  Foundation; either version 3 of the License, or (at your option) any
16  later version.
17 
18 or both in parallel, as here.
19 
20 The Piranha library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the Piranha library. If not,
27 see https://www.gnu.org/licenses/. */
28 
29 #ifndef PIRANHA_SMALL_VECTOR_HPP
30 #define PIRANHA_SMALL_VECTOR_HPP
31 
32 #include <algorithm>
33 #include <cstddef>
34 #include <cstdint>
35 #include <initializer_list>
36 #include <iterator>
37 #include <limits>
38 #include <new>
39 #include <stdexcept>
40 #include <tuple>
41 #include <type_traits>
42 #include <utility>
43 #include <vector>
44 
45 #include <piranha/config.hpp>
46 #include <piranha/detail/small_vector_fwd.hpp>
47 #include <piranha/detail/vector_hasher.hpp>
48 #include <piranha/exceptions.hpp>
49 #include <piranha/math.hpp>
50 #include <piranha/memory.hpp>
51 #include <piranha/s11n.hpp>
52 #include <piranha/safe_cast.hpp>
53 #include <piranha/static_vector.hpp>
54 #include <piranha/type_traits.hpp>
55 
56 namespace piranha
57 {
58 
59 namespace detail
60 {
61 
62 // This is essentially a reduced std::vector replacement that should use less storage on most implementations
63 // (e.g., it has a size of 16 on Linux 64-bit and it _should_ have a size of 8 on many 32-bit archs).
64 // NOTE: earlier versions of this class used to have support for custom allocation. See, e.g., commit
65 // 6e1dd235c729ed05eb6e872dceece2f20a624f32
66 // It seems however that there are some defects currently in the standard C++ library that make it quite hard
67 // to implement reasonably strong exception safety and nothrow guarantees. See, e.g.,
68 // http://cplusplus.github.io/LWG/lwg-defects.html#2103
69 // It seems like in the current standard it is not possible to implemement noexcept move assignment not even
70 // for std::alloc. The crux of the matter is that if propagate_on_container_move_assignment is not defined,
71 // we will need in general a memory allocation (that can fail).
72 // Another, less important, problem is that for copy assignment the presence of
73 // propagate_on_container_copy_assignment seems to force one to nuke pre-emptively the content of the container
74 // before doing anything (thus preventing strong forms of exception safety).
75 // The approach taken here is thus to just hard-code std::allocator, which is stateless, and force nothrow
76 // behaviour when doing assignments. This should work with all stateless allocators (including our own allocator).
77 // Unfortunately it is not clear if the extension allocators from GCC are stateless, so better not to muck
78 // with them (apart from experimentation).
79 // See also:
80 // http://stackoverflow.com/questions/12332772/why-arent-container-move-assignment-operators-noexcept
81 // NOTE: now we do not use the allocator at all, as we cannot guarantee it has a standard layout. Keep the comments
82 // above as an historical reference, might come useful in the future :)
83 template <typename T>
84 class dynamic_storage
85 {
86  PIRANHA_TT_CHECK(is_container_element, T);
87 
88 public:
89  using size_type = unsigned char;
90  using value_type = T;
91 
92 private:
93  using pointer = value_type *;
94  using const_pointer = value_type const *;
95  // NOTE: this bit of TMP is to avoid checking an always-false condition on reserve() on most platforms, which
96  // triggers a compiler warning on GCC 4.7.
97  static const std::size_t max_alloc_size = std::numeric_limits<std::size_t>::max() / sizeof(value_type);
98  static const bool need_reserve_check = std::numeric_limits<size_type>::max() > max_alloc_size;
99  static bool reserve_check_size(const size_type &, const std::false_type &)
100  {
101  return false;
102  }
103  static bool reserve_check_size(const size_type &new_capacity, const std::true_type &)
104  {
105  return new_capacity > max_alloc_size;
106  }
107 
108 public:
109  static const size_type max_size = std::numeric_limits<size_type>::max();
110  using iterator = pointer;
111  using const_iterator = const_pointer;
112  dynamic_storage() : m_tag(0u), m_size(0u), m_capacity(0u), m_ptr(nullptr)
113  {
114  }
115  dynamic_storage(dynamic_storage &&other) noexcept
116  : m_tag(0u), m_size(other.m_size), m_capacity(other.m_capacity), m_ptr(other.m_ptr)
117  {
118  // Erase the other.
119  other.m_size = 0u;
120  other.m_capacity = 0u;
121  other.m_ptr = nullptr;
122  }
123  dynamic_storage(const dynamic_storage &other)
124  : m_tag(0u), m_size(0u),
125  m_capacity(other.m_size), // NOTE: when copying, we set the capacity to the same value of the size.
126  m_ptr(nullptr)
127  {
128  // Obtain storage. Will just return nullptr if requested size is zero.
129  m_ptr = allocate(other.m_size);
130  // Attempt to copy-construct the elements from other.
131  try {
132  for (; m_size < other.m_size; ++m_size) {
133  construct(m_ptr + m_size, other[m_size]);
134  }
135  } catch (...) {
136  // Roll back the construction and deallocate before re-throwing.
137  destroy_and_deallocate();
138  throw;
139  }
140  }
141  ~dynamic_storage()
142  {
143  // NOTE: here we should replace with bidirectional tt, if we ever implement it.
144  PIRANHA_TT_CHECK(is_forward_iterator, iterator);
145  PIRANHA_TT_CHECK(is_forward_iterator, const_iterator);
146  piranha_assert(m_tag == 0u);
147  destroy_and_deallocate();
148  }
149  dynamic_storage &operator=(dynamic_storage &&other) noexcept
150  {
151  if (likely(this != &other)) {
152  // Destroy and deallocate this.
153  destroy_and_deallocate();
154  // Just pilfer the resources.
155  m_size = other.m_size;
156  m_capacity = other.m_capacity;
157  m_ptr = other.m_ptr;
158  // Nuke the other.
159  other.m_size = 0u;
160  other.m_capacity = 0u;
161  other.m_ptr = nullptr;
162  }
163  return *this;
164  }
165  dynamic_storage &operator=(const dynamic_storage &other)
166  {
167  if (likely(this != &other)) {
168  dynamic_storage tmp(other);
169  *this = std::move(tmp);
170  }
171  return *this;
172  }
173  bool empty() const
174  {
175  return m_size == 0u;
176  }
177  void push_back(const value_type &x)
178  {
179  push_back_impl(x);
180  }
181  void push_back(value_type &&x)
182  {
183  push_back_impl(std::move(x));
184  }
185  value_type &operator[](const size_type &n)
186  {
187  return m_ptr[n];
188  }
189  const value_type &operator[](const size_type &n) const
190  {
191  return m_ptr[n];
192  }
193  size_type size() const
194  {
195  return m_size;
196  }
197  iterator begin()
198  {
199  return m_ptr;
200  }
201  iterator end()
202  {
203  // NOTE: in case m_size is zero, this is guaranteed to return
204  // the original pointer (5.7/7).
205  return m_ptr + m_size;
206  }
207  const_iterator begin() const
208  {
209  return m_ptr;
210  }
211  const_iterator end() const
212  {
213  return m_ptr + m_size;
214  }
215  void reserve(const size_type &new_capacity)
216  {
217  piranha_assert(consistency_checks());
218  // No need to do anything if we already have enough capacity.
219  if (new_capacity <= m_capacity) {
220  return;
221  }
222  // Check that we are not asking too much.
223  // NOTE: the first check is redundant right now, just keep it around in case max_size changes in the future.
224  if (unlikely(/*new_capacity > max_size ||*/ reserve_check_size(
225  new_capacity, std::integral_constant<bool, need_reserve_check>()))) {
226  piranha_throw(std::bad_alloc, );
227  }
228  // Start by allocating the new storage. New capacity is at least one at this point.
229  pointer new_storage = allocate(new_capacity);
230  assert(new_storage != nullptr);
231  // Move in existing elements. Consistency checks ensure
232  // that m_size is not greater than m_capacity and, by extension, new_capacity.
233  for (size_type i = 0u; i < m_size; ++i) {
234  construct(new_storage + i, std::move((*this)[i]));
235  }
236  // Destroy and deallocate original content.
237  destroy_and_deallocate();
238  // Move in the new pointer and capacity.
239  m_capacity = new_capacity;
240  m_ptr = new_storage;
241  }
242  size_type capacity() const
243  {
244  return m_capacity;
245  }
246  std::size_t hash() const
247  {
248  return detail::vector_hasher(*this);
249  }
250  void resize(const size_type &new_size)
251  {
252  // NOTE: another redundant size check at the moment.
253  // if (unlikely(new_size > max_size)) {
254  // piranha_throw(std::bad_alloc,);
255  // }
256  if (new_size == m_size) {
257  return;
258  }
259  // The storage we are going to operate on is either the old one, if it has enough capacity,
260  // or new storage.
261  const bool new_storage = (m_capacity < new_size);
262  pointer storage = new_storage ? allocate(new_size) : m_ptr;
263  // NOTE: storage cannot be nullptr:
264  // - if new storage, new_size has to be at least 1 (new_size > m_capacity);
265  // - if not new storage, new_size <= m_capacity; m_ptr can be null only if capacity is 0, but then
266  // m_size is zero and new_size is 0 as well, and the function never arrived here because of the check above.
267  piranha_assert(storage != nullptr);
268  // Default-construct excess elements. We need to do this regardless of where the storage is coming from.
269  // This is also the only place we care about exception handling.
270  size_type i = m_size;
271  try {
272  for (; i < new_size; ++i) {
273  construct(storage + i);
274  }
275  } catch (...) {
276  // Roll back and dealloc.
277  for (size_type j = m_size; j < i; ++j) {
278  destroy(storage + j);
279  }
280  deallocate(storage);
281  throw;
282  }
283  // NOTE: no more exceptions thrown after this point.
284  if (new_storage) {
285  // Move in old elements into the new storage. As we had to increase the capacity,
286  // we know that new_size has to be greater than the old one, hence all old elements
287  // need to be moved over.
288  for (size_type j = 0u; j < m_size; ++j) {
289  construct(storage + j, std::move((*this)[j]));
290  }
291  // Erase the old content and assign new.
292  destroy_and_deallocate();
293  m_capacity = new_size;
294  m_ptr = storage;
295  } else {
296  // Destroy excess elements in the old storage.
297  for (size_type j = new_size; j < m_size; ++j) {
298  destroy(storage + j);
299  }
300  }
301  // In any case, we need to update the size.
302  m_size = new_size;
303  }
304  // Erase element. See the cooresponding code in static_vector.
305  iterator erase(const_iterator it)
306  {
307  piranha_assert(it != end());
308  auto retval = const_cast<iterator>(it);
309  const auto it_f = end() - 1;
310  for (; it != it_f; ++it) {
311  it->~T();
312  ::new (static_cast<void *>(const_cast<iterator>(it))) value_type(std::move(*(it + 1)));
313  }
314  it->~T();
315  m_size = static_cast<size_type>(m_size - 1u);
316  return retval;
317  }
318 
319 private:
320  template <typename... Args>
321  static void construct(pointer p, Args &&... args)
322  {
323  ::new (static_cast<void *>(p)) value_type(std::forward<Args>(args)...);
324  }
325  static void destroy(pointer p)
326  {
327  p->~value_type();
328  }
329  // Obtain new storage, and throw an error in case something goes wrong.
330  // NOTE: no need to check for zero, will aready return nullptr in that case.
331  static pointer allocate(const size_type &s)
332  {
333  return static_cast<pointer>(aligned_palloc(0u, static_cast<std::size_t>(s * sizeof(value_type))));
334  }
335  static void deallocate(pointer p)
336  {
337  aligned_pfree(0u, p);
338  }
339  // Common implementation of push_back().
340  template <typename U>
341  void push_back_impl(U &&x)
342  {
343  piranha_assert(consistency_checks());
344  if (unlikely(m_capacity == m_size)) {
345  increase_capacity();
346  }
347  construct(m_ptr + m_size, std::forward<U>(x));
348  ++m_size;
349  }
350  void destroy_and_deallocate()
351  {
352  piranha_assert(consistency_checks());
353  for (size_type i = 0u; i < m_size; ++i) {
354  // NOTE: could use POD optimisations here in principle.
355  destroy(m_ptr + i);
356  }
357  // NOTE: no need to check for nullptr, aligned_pfree already does it.
358  deallocate(m_ptr);
359  }
360  // Will try to double the capacity, or, in case this is not possible,
361  // will set the capacity to max_size. If the initial capacity is already max,
362  // then will throw an error.
363  void increase_capacity()
364  {
365  if (unlikely(m_capacity == max_size)) {
366  piranha_throw(std::bad_alloc, );
367  }
368  // NOTE: capacity should double, but without going past max_size, and in case it is zero it should go to 1.
369  const size_type new_capacity
370  = (m_capacity > max_size / 2u) ? max_size : ((m_capacity != 0u) ? static_cast<size_type>(m_capacity * 2u)
371  : static_cast<size_type>(1u));
372  reserve(new_capacity);
373  }
374  bool consistency_checks()
375  {
376  // Size cannot be greater than capacity.
377  if (unlikely(m_size > m_capacity)) {
378  return false;
379  }
380  // If we allocated something, capacity cannot be zero.
381  if (unlikely(m_ptr != nullptr && m_capacity == 0u)) {
382  return false;
383  }
384  // If we have nothing allocated, capacity must be zero.
385  if (unlikely(m_ptr == nullptr && m_capacity != 0u)) {
386  return false;
387  }
388  return true;
389  }
390 
391 private:
392  unsigned char m_tag;
393  size_type m_size;
394  size_type m_capacity;
395  pointer m_ptr;
396 };
397 
398 template <typename T>
399 const typename dynamic_storage<T>::size_type dynamic_storage<T>::max_size;
400 
401 template <typename T>
402 const std::size_t dynamic_storage<T>::max_alloc_size;
403 
404 // TMP to determine automatically the size of the static storage in small_vector.
405 template <typename T, std::size_t Size = 1u, typename = void>
406 struct auto_static_size {
407  static const std::size_t value = Size;
408 };
409 
410 template <typename T, std::size_t Size>
411 struct auto_static_size<T, Size,
412  typename std::enable_if<(sizeof(dynamic_storage<T>) > sizeof(static_vector<T, Size>))>::type> {
413  static_assert(Size < std::numeric_limits<std::size_t>::max(), "Overflow error in auto_static_size.");
414  static const std::size_t value = auto_static_size<T, Size + 1u>::value;
415 };
416 
417 template <typename T>
418 struct check_integral_constant {
419  static const bool value = false;
420 };
421 
422 template <std::size_t Size>
423 struct check_integral_constant<std::integral_constant<std::size_t, Size>> {
424  static const bool value = true;
425 };
426 
427 // NOTE: here we are using a special rule from 9.2. If the union is standard-layout, and it contains two or more
428 // standard-layout classes, it will be possible to inspect the value of the common initial part of any of them. That is,
429 // we can access the initial m_tag member of the static and dynamic storage via both st and dy, and use it to detect
430 // which class of the union is active.
431 // See also:
432 // http://stackoverflow.com/questions/18564497/writing-into-the-last-byte-of-a-class
433 // http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=556
434 // http://en.wikipedia.org/wiki/C%2B%2B11#Unrestricted_unions
435 template <typename T, typename S>
436 union small_vector_union {
437  using s_storage = typename std::conditional<S::value == 0u, static_vector<T, auto_static_size<T>::value>,
438  static_vector<T, S::value>>::type;
439  using d_storage = dynamic_storage<T>;
440  // NOTE: each constructor must be invoked explicitly.
441  small_vector_union() : m_st()
442  {
443  }
444  small_vector_union(const small_vector_union &other)
445  {
446  if (other.is_static()) {
447  ::new (static_cast<void *>(&m_st)) s_storage(other.g_st());
448  } else {
449  ::new (static_cast<void *>(&m_dy)) d_storage(other.g_dy());
450  }
451  }
452  small_vector_union(small_vector_union &&other) noexcept
453  {
454  if (other.is_static()) {
455  ::new (static_cast<void *>(&m_st)) s_storage(std::move(other.g_st()));
456  } else {
457  ::new (static_cast<void *>(&m_dy)) d_storage(std::move(other.g_dy()));
458  }
459  }
460  ~small_vector_union()
461  {
462  PIRANHA_TT_CHECK(std::is_standard_layout, s_storage);
463  PIRANHA_TT_CHECK(std::is_standard_layout, d_storage);
464  PIRANHA_TT_CHECK(std::is_standard_layout, small_vector_union);
465  if (is_static()) {
466  g_st().~s_storage();
467  } else {
468  g_dy().~d_storage();
469  }
470  }
471  small_vector_union &operator=(small_vector_union &&other) noexcept
472  {
473  if (unlikely(this == &other)) {
474  return *this;
475  }
476  if (is_static() == other.is_static()) {
477  if (is_static()) {
478  g_st() = std::move(other.g_st());
479  } else {
480  g_dy() = std::move(other.g_dy());
481  }
482  } else {
483  if (is_static()) {
484  // static vs dynamic.
485  // Destroy static.
486  g_st().~s_storage();
487  // Move construct dynamic from other.
488  ::new (static_cast<void *>(&m_dy)) d_storage(std::move(other.g_dy()));
489  } else {
490  // dynamic vs static.
491  g_dy().~d_storage();
492  ::new (static_cast<void *>(&m_st)) s_storage(std::move(other.g_st()));
493  }
494  }
495  return *this;
496  }
497  small_vector_union &operator=(const small_vector_union &other)
498  {
499  if (likely(this != &other)) {
500  *this = small_vector_union(other);
501  }
502  return *this;
503  }
504  bool is_static() const
505  {
506  return static_cast<bool>(m_st.m_tag);
507  }
508  // Getters.
509  const s_storage &g_st() const
510  {
511  piranha_assert(is_static());
512  return m_st;
513  }
514  s_storage &g_st()
515  {
516  piranha_assert(is_static());
517  return m_st;
518  }
519  const d_storage &g_dy() const
520  {
521  piranha_assert(!is_static());
522  return m_dy;
523  }
524  d_storage &g_dy()
525  {
526  piranha_assert(!is_static());
527  return m_dy;
528  }
529  s_storage m_st;
530  d_storage m_dy;
531 };
532 }
533 
535 
555 // NOTE: some possible improvements:
556 // - the m_size member of dynamic and static could be made a signed integer, the sign establishing the storage type
557 // and drop the m_tag member. This in principle would allow to squeeze some extra space from the static vector
558 // but not sure this is worth it;
559 // - POD optimisations in dynamic storage;
560 // - in the dynamic storage, it looks like on 64bit we can bump up the size member to 16 bit without changing size,
561 // thus we could store ~16000 elements. BUT on 32bit this will change the size.
562 template <typename T, typename S = std::integral_constant<std::size_t, 0u>>
563 class small_vector
564 {
565  PIRANHA_TT_CHECK(is_container_element, T);
566  PIRANHA_TT_CHECK(detail::check_integral_constant, S);
567  using u_type = detail::small_vector_union<T, S>;
568  using s_storage = typename u_type::s_storage;
569  using d_storage = typename u_type::d_storage;
570  // The size type will be the one with most range among the two storages.
571  using size_type_impl = max_int<typename s_storage::size_type, typename d_storage::size_type>;
572  template <typename U>
573  static constexpr U get_max(const U &a, const U &b)
574  {
575  return (a > b) ? a : b;
576  }
577 
578 public:
580  static const typename std::decay<decltype(s_storage::max_size)>::type max_static_size = s_storage::max_size;
582  static const typename std::decay<decltype(d_storage::max_size)>::type max_dynamic_size = d_storage::max_size;
584  using size_type = size_type_impl;
585 
586 private:
587  // If the size type can assume values larger than d_storage::max_size, then we need to run a check in resize().
588  static const bool need_resize_check = std::numeric_limits<size_type>::max() > d_storage::max_size;
589  static bool resize_check_size(const size_type &, const std::false_type &)
590  {
591  return false;
592  }
593  static bool resize_check_size(const size_type &size, const std::true_type &)
594  {
595  return size > d_storage::max_size;
596  }
597 
598 public:
600  static const size_type max_size = get_max<size_type>(max_static_size, max_dynamic_size);
602  using value_type = T;
604  using iterator = value_type *;
606  using const_iterator = value_type const *;
607 
608 private:
609  // NOTE: here we should replace with bidirectional tt, if we ever implement it.
610  PIRANHA_TT_CHECK(is_forward_iterator, iterator);
611  PIRANHA_TT_CHECK(is_forward_iterator, const_iterator);
612  // Enabler for ctor from init list.
613  template <typename U>
614  using init_list_enabler = typename std::enable_if<has_safe_cast<T, U>::value, int>::type;
615  // Enabler for equality operator.
616  template <typename U>
617  using equality_enabler = typename std::enable_if<is_equality_comparable<U>::value, int>::type;
618  // Enabler for hash.
619  template <typename U>
620  using hash_enabler = typename std::enable_if<is_hashable<U>::value, int>::type;
621  // Enabler for the addition.
622  template <typename U>
623  using add_enabler = typename std::enable_if<has_add3<U>::value, int>::type;
624  // Enabler for the subtraction.
625  template <typename U>
626  using sub_enabler = typename std::enable_if<has_sub3<U>::value, int>::type;
627  // Serialization support.
628  friend class boost::serialization::access;
629  template <class Archive>
630  void save(Archive &ar, unsigned) const
631  {
632  boost_save_vector(ar, *this);
633  }
634  template <class Archive>
635  void load(Archive &ar, unsigned)
636  {
637  boost_load_vector(ar, *this);
638  }
639  BOOST_SERIALIZATION_SPLIT_MEMBER()
640 public:
642 
645  small_vector() = default;
647 
655  small_vector(const small_vector &other) = default;
657 
662  small_vector(small_vector &&other) = default;
664 
675  template <typename U, init_list_enabler<U> = 0>
676  explicit small_vector(std::initializer_list<U> l)
677  {
678  // NOTE: push_back has strong exception safety.
679  for (const U &x : l) {
680  push_back(safe_cast<T>(x));
681  }
682  }
684 
692  explicit small_vector(const size_type &size, const T &value)
693  {
694  for (size_type i = 0u; i < size; ++i) {
695  push_back(value);
696  }
697  }
699  ~small_vector() = default;
701 
706  small_vector &operator=(const small_vector &) = default;
708 
711  small_vector &operator=(small_vector &&) = default;
713 
718  const value_type &operator[](const size_type &n) const
719  {
720  return begin()[n];
721  }
723 
729  {
730  return begin()[n];
731  }
733 
741  void push_back(const value_type &x)
742  {
743  push_back_impl(x);
744  }
746 
754  {
755  push_back_impl(std::move(x));
756  }
758 
762  {
763  if (m_union.is_static()) {
764  return m_union.g_st().begin();
765  } else {
766  return m_union.g_dy().begin();
767  }
768  }
770 
774  {
775  if (m_union.is_static()) {
776  return m_union.g_st().end();
777  } else {
778  return m_union.g_dy().end();
779  }
780  }
782 
786  {
787  if (m_union.is_static()) {
788  return m_union.g_st().begin();
789  } else {
790  return m_union.g_dy().begin();
791  }
792  }
794 
798  {
799  if (m_union.is_static()) {
800  return m_union.g_st().end();
801  } else {
802  return m_union.g_dy().end();
803  }
804  }
806 
809  size_type size() const
810  {
811  if (m_union.is_static()) {
812  return m_union.g_st().size();
813  } else {
814  return m_union.g_dy().size();
815  }
816  }
818 
821  bool is_static() const
822  {
823  return m_union.is_static();
824  }
826 
837  template <typename U = value_type, equality_enabler<U> = 0>
838  bool operator==(const small_vector &other) const
839  {
840  // NOTE: it seems like in C++14 the check on equal sizes is embedded in std::equal
841  // when using the new algorithm signature:
842  // http://en.cppreference.com/w/cpp/algorithm/equal
843  // Just keep it in mind for the future.
844  const unsigned mask
845  = static_cast<unsigned>(m_union.is_static()) + (static_cast<unsigned>(other.m_union.is_static()) << 1u);
846  switch (mask) {
847  case 0u:
848  return m_union.g_dy().size() == other.m_union.g_dy().size()
849  && std::equal(m_union.g_dy().begin(), m_union.g_dy().end(), other.m_union.g_dy().begin());
850  case 1u:
851  return m_union.g_st().size() == other.m_union.g_dy().size()
852  && std::equal(m_union.g_st().begin(), m_union.g_st().end(), other.m_union.g_dy().begin());
853  case 2u:
854  return m_union.g_dy().size() == other.m_union.g_st().size()
855  && std::equal(m_union.g_dy().begin(), m_union.g_dy().end(), other.m_union.g_st().begin());
856  }
857  return m_union.g_st().size() == other.m_union.g_st().size()
858  && std::equal(m_union.g_st().begin(), m_union.g_st().end(), other.m_union.g_st().begin());
859  }
861 
871  template <typename U = value_type, equality_enabler<U> = 0>
872  bool operator!=(const small_vector &other) const
873  {
874  return !(this->operator==(other));
875  }
877 
883  template <typename U = value_type, hash_enabler<U> = 0>
884  std::size_t hash() const
885  {
886  if (m_union.is_static()) {
887  return m_union.g_st().hash();
888  } else {
889  return m_union.g_dy().hash();
890  }
891  }
893 
896  bool empty() const
897  {
898  if (m_union.is_static()) {
899  return m_union.g_st().empty();
900  } else {
901  return m_union.g_dy().empty();
902  }
903  }
905 
914  void resize(const size_type &size)
915  {
916  if (m_union.is_static()) {
917  if (size <= max_static_size) {
918  m_union.g_st().resize(static_cast<typename s_storage::size_type>(size));
919  } else {
920  // NOTE: this check is a repetition of an existing check in dynamic storage's
921  // resize. The reason for putting it here as well is to ensure the safety
922  // of the static_cast below.
923  if (unlikely(resize_check_size(size, std::integral_constant<bool, need_resize_check>()))) {
924  piranha_throw(std::bad_alloc, );
925  }
926  // Move the existing elements into new dynamic storage.
927  const auto d_size = static_cast<typename d_storage::size_type>(size);
928  d_storage tmp_d;
929  tmp_d.reserve(d_size);
930  // NOTE: this will not throw, as tmp_d is guaranteed to be of adequate size
931  // and thus push_back() will not fail.
932  std::move(m_union.g_st().begin(), m_union.g_st().end(), std::back_inserter(tmp_d));
933  // Fill in the missing elements.
934  tmp_d.resize(d_size);
935  // Destroy static, move in dynamic.
936  m_union.g_st().~s_storage();
937  ::new (static_cast<void *>(&(m_union.m_dy))) d_storage(std::move(tmp_d));
938  }
939  } else {
940  if (unlikely(resize_check_size(size, std::integral_constant<bool, need_resize_check>()))) {
941  piranha_throw(std::bad_alloc, );
942  }
943  m_union.g_dy().resize(static_cast<typename d_storage::size_type>(size));
944  }
945  }
947 
966  template <typename U = value_type, add_enabler<U> = 0>
967  void add(small_vector &retval, const small_vector &other) const
968  {
969  const auto sbe1 = size_begin_end(), sbe2 = other.size_begin_end();
970  if (unlikely(std::get<0u>(sbe1) != std::get<0u>(sbe2))) {
971  piranha_throw(std::invalid_argument, "vector size mismatch");
972  }
973  // NOTE: if retval coincides with this and/or other, this will be a no-op as
974  // the resize methods don't do anything if the new size is the same as the old one.
975  // Thus, we are not risking of invalidating sbe1/sbe2 with this resize.
976  retval.resize(std::get<0u>(sbe1));
977  auto sbe_out = retval.size_begin_end();
978  for (size_type i = 0u; i < std::get<0u>(sbe1); ++i) {
979  math::add3(*(std::get<1u>(sbe_out) + i), *(std::get<1u>(sbe1) + i), *(std::get<1u>(sbe2) + i));
980  }
981  }
983 
1002  template <typename U = value_type, sub_enabler<U> = 0>
1003  void sub(small_vector &retval, const small_vector &other) const
1004  {
1005  const auto sbe1 = size_begin_end(), sbe2 = other.size_begin_end();
1006  if (unlikely(std::get<0u>(sbe1) != std::get<0u>(sbe2))) {
1007  piranha_throw(std::invalid_argument, "vector size mismatch");
1008  }
1009  retval.resize(std::get<0u>(sbe1));
1010  auto sbe_out = retval.size_begin_end();
1011  for (size_type i = 0u; i < std::get<0u>(sbe1); ++i) {
1012  math::sub3(*(std::get<1u>(sbe_out) + i), *(std::get<1u>(sbe1) + i), *(std::get<1u>(sbe2) + i));
1013  }
1014  }
1016 
1028  {
1029  if (m_union.is_static()) {
1030  return m_union.g_st().erase(it);
1031  } else {
1032  return m_union.g_dy().erase(it);
1033  }
1034  }
1036 
1039  std::tuple<size_type, iterator, iterator> size_begin_end()
1040  {
1041  if (m_union.is_static()) {
1042  return std::make_tuple(size_type(m_union.g_st().size()), m_union.g_st().begin(), m_union.g_st().end());
1043  } else {
1044  return std::make_tuple(size_type(m_union.g_dy().size()), m_union.g_dy().begin(), m_union.g_dy().end());
1045  }
1046  }
1048 
1051  std::tuple<size_type, const_iterator, const_iterator> size_begin_end() const
1052  {
1053  if (m_union.is_static()) {
1054  return std::make_tuple(size_type(m_union.g_st().size()), m_union.g_st().begin(), m_union.g_st().end());
1055  } else {
1056  return std::make_tuple(size_type(m_union.g_dy().size()), m_union.g_dy().begin(), m_union.g_dy().end());
1057  }
1058  }
1059 
1060 private:
1061  template <typename U>
1062  void push_back_impl(U &&x)
1063  {
1064  if (m_union.is_static()) {
1065  if (m_union.g_st().size() == m_union.g_st().max_size) {
1066  // Create a new dynamic vector, and move in the current
1067  // elements from static storage.
1068  using d_size_type = typename d_storage::size_type;
1069  // NOTE: this check ensures that the d_storage::max_size is strictly greater than
1070  // the current (static) size (equal to max static size). This means we can compute safely
1071  // current size + 1 while casting to dynamic storage size type and try to allocate enough space for
1072  // the std::move and push_back() below.
1073  // NOTE: this check is analogous to current_size + 1 > d_storage::max_size, i.e., the same
1074  // check in resize(), but it will use static const variables written as this.
1075  if (unlikely(s_storage::max_size >= d_storage::max_size)) {
1076  piranha_throw(std::bad_alloc, );
1077  }
1078  d_storage tmp_d;
1079  tmp_d.reserve(static_cast<d_size_type>(static_cast<d_size_type>(m_union.g_st().max_size) + 1u));
1080  std::move(m_union.g_st().begin(), m_union.g_st().end(), std::back_inserter(tmp_d));
1081  // Push back the new element.
1082  tmp_d.push_back(std::forward<U>(x));
1083  // Now destroy the current static storage, and move-construct new dynamic storage.
1084  // NOTE: in face of custom allocators here we should be ok, as move construction
1085  // of custom alloc will not throw and it will preserve the ownership of the moved-in elements.
1086  m_union.g_st().~s_storage();
1087  ::new (static_cast<void *>(&(m_union.m_dy))) d_storage(std::move(tmp_d));
1088  } else {
1089  m_union.g_st().push_back(std::forward<U>(x));
1090  }
1091  } else {
1092  // In case we are already in dynamic storage, don't do anything special.
1093  m_union.g_dy().push_back(std::forward<U>(x));
1094  }
1095  }
1096 
1097 private:
1098  u_type m_union;
1099 };
1100 
1101 template <typename T, typename S>
1102 const typename std::decay<decltype(small_vector<T, S>::s_storage::max_size)>::type small_vector<T, S>::max_static_size;
1103 
1104 template <typename T, typename S>
1105 const typename std::decay<decltype(small_vector<T, S>::d_storage::max_size)>::type small_vector<T, S>::max_dynamic_size;
1106 
1107 template <typename T, typename S>
1109 
1111 
1118 template <typename Archive, typename T, std::size_t Size>
1119 struct boost_save_impl<Archive, small_vector<T, std::integral_constant<std::size_t, Size>>,
1120  boost_save_vector_enabler<Archive, small_vector<T, std::integral_constant<std::size_t, Size>>>>
1121  : boost_save_via_boost_api<Archive, small_vector<T, std::integral_constant<std::size_t, Size>>> {
1122 };
1123 
1125 
1136 template <typename Archive, typename T, std::size_t Size>
1137 struct boost_load_impl<Archive, small_vector<T, std::integral_constant<std::size_t, Size>>,
1138  boost_load_vector_enabler<Archive, small_vector<T, std::integral_constant<std::size_t, Size>>>>
1139  : boost_load_via_boost_api<Archive, small_vector<T, std::integral_constant<std::size_t, Size>>> {
1140 };
1141 
1142 #if defined(PIRANHA_WITH_MSGPACK)
1143 
1145 
1152 template <typename Stream, typename T, std::size_t Size>
1153 struct msgpack_pack_impl<Stream, small_vector<T, std::integral_constant<std::size_t, Size>>,
1154  msgpack_pack_vector_enabler<Stream,
1155  small_vector<T, std::integral_constant<std::size_t, Size>>>> {
1157 
1169  void operator()(msgpack::packer<Stream> &packer,
1170  const small_vector<T, std::integral_constant<std::size_t, Size>> &v, msgpack_format f) const
1171  {
1172  msgpack_pack_vector(packer, v, f);
1173  }
1174 };
1175 
1177 
1183 template <typename T, std::size_t Size>
1184 struct msgpack_convert_impl<small_vector<T, std::integral_constant<std::size_t, Size>>,
1185  msgpack_convert_array_enabler<small_vector<T, std::integral_constant<std::size_t, Size>>>> {
1187 
1201  void operator()(small_vector<T, std::integral_constant<std::size_t, Size>> &v, const msgpack::object &o,
1202  msgpack_format f) const
1203  {
1204  msgpack_convert_array(o, v, f);
1205  }
1206 };
1207 
1208 #endif
1209 }
1210 
1211 #endif
const_iterator end() const
Const end iterator.
void * aligned_palloc(const std::size_t &alignment, const std::size_t &size)
Allocate memory aligned to a specific value.
Definition: memory.hpp:134
Small vector class.
static const std::decay< decltype(d_storage::max_size)>::type max_dynamic_size
Maximum number of elements that can be stored in dynamic storage.
Default implementation of piranha::boost_load().
Definition: s11n.hpp:391
void push_back(const value_type &x)
Copy-add element at the end.
iterator begin()
Mutable begin iterator.
bool operator!=(const small_vector &other) const
Inequality operator.
auto add3(T &a, const T &b, const T &c) -> decltype(add3_impl< T >()(a, b, c))
Ternary addition.
Definition: math.hpp:2582
bool is_static() const
Static storage flag.
Implementation of piranha::boost_load() via the Boost API.
Definition: s11n.hpp:363
std::tuple< size_type, const_iterator, const_iterator > size_begin_end() const
Size, begin and end (const version).
Exceptions.
Default implementation of piranha::boost_save().
Definition: s11n.hpp:245
small_vector()=default
Default constructor.
STL namespace.
~small_vector()=default
Destructor.
value_type const * const_iterator
Const iterator type.
static const size_type max_size
Maximum number of elements that can be stored.
Forward iterator type trait.
T value_type
Alias for T.
size_type size() const
Size.
Default functor for the implementation of piranha::msgpack_convert().
Definition: s11n.hpp:867
#define piranha_throw(exception_type,...)
Exception-throwing macro.
Definition: exceptions.hpp:118
iterator end()
Mutable end iterator.
Default functor for the implementation of piranha::msgpack_pack().
Definition: s11n.hpp:686
msgpack_format
Serialization format for msgpack.
Definition: s11n.hpp:673
iterator erase(const_iterator it)
Erase element.
value_type * iterator
Iterator type.
const_iterator begin() const
Const begin iterator.
void operator()(small_vector< T, std::integral_constant< std::size_t, Size >> &v, const msgpack::object &o, msgpack_format f) const
Call operator.
void resize(const size_type &size)
Resize.
Implementation of piranha::boost_save() via the Boost API.
Definition: s11n.hpp:217
bool empty() const
Empty test.
void push_back(value_type &&x)
Move-add element at the end.
Root piranha namespace.
Definition: array_key.hpp:52
void aligned_pfree(const std::size_t &alignment, void *ptr)
Free memory allocated via piranha::aligned_alloc.
Definition: memory.hpp:182
Type traits.
small_vector(const size_type &size, const T &value)
Constructor from size and value.
bool operator==(const small_vector &other) const
Equality operator.
std::size_t hash() const
Hash method.
small_vector & operator=(const small_vector &)=default
Copy assignment operator.
const value_type & operator[](const size_type &n) const
Const subscript operator.
auto sub3(T &a, const T &b, const T &c) -> decltype(sub3_impl< T >()(a, b, c))
Ternary subtraction.
Definition: math.hpp:2654
size_type_impl size_type
A fundamental unsigned integer type representing the number of elements stored in the vector...
static const std::decay< decltype(s_storage::max_size)>::type max_static_size
Maximum number of elements that can be stored in static storage.
void operator()(msgpack::packer< Stream > &packer, const small_vector< T, std::integral_constant< std::size_t, Size >> &v, msgpack_format f) const
Call operator.
#define PIRANHA_TT_CHECK(tt,...)
Macro for static type trait checks.
value_type & operator[](const size_type &n)
Subscript operator.
std::tuple< size_type, iterator, iterator > size_begin_end()
Size, begin and end.
void sub(small_vector &retval, const small_vector &other) const
Vector subtraction.
Low-level memory management functions.
void add(small_vector &retval, const small_vector &other) const
Vector addition.