piranha  0.10
static_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_STATIC_VECTOR_HPP
30 #define PIRANHA_STATIC_VECTOR_HPP
31 
32 #include <algorithm>
33 #include <cstddef>
34 #include <cstdint>
35 #include <cstring>
36 #include <iostream>
37 #include <limits>
38 #include <new>
39 #include <tuple>
40 #include <type_traits>
41 
42 #include <piranha/config.hpp>
43 #include <piranha/detail/small_vector_fwd.hpp>
44 #include <piranha/detail/vector_hasher.hpp>
45 #include <piranha/exceptions.hpp>
46 #include <piranha/s11n.hpp>
47 #include <piranha/safe_cast.hpp>
48 #include <piranha/type_traits.hpp>
49 
50 namespace piranha
51 {
52 
53 namespace detail
54 {
55 
56 // TMP to determine size type big enough to represent Size. The candidate types are the fundamental unsigned integer
57 // types.
58 using static_vector_size_types = std::tuple<unsigned char, unsigned short, unsigned, unsigned long, unsigned long long>;
59 
60 template <std::size_t Size, std::size_t Index = 0u>
61 struct static_vector_size_type {
62  using candidate_type = typename std::tuple_element<Index, static_vector_size_types>::type;
63  using type = typename std::
64  conditional<(std::numeric_limits<candidate_type>::max() >= Size), candidate_type,
65  typename static_vector_size_type<Size, static_cast<std::size_t>(Index + 1u)>::type>::type;
66 };
67 
68 template <std::size_t Size>
69 struct static_vector_size_type<Size, static_cast<std::size_t>(std::tuple_size<static_vector_size_types>::value - 1u)> {
70  using type =
71  typename std::tuple_element<static_cast<std::size_t>(std::tuple_size<static_vector_size_types>::value - 1u),
72  static_vector_size_types>::type;
73  static_assert(std::numeric_limits<type>::max() >= Size, "Cannot determine size type for static vector.");
74 };
75 }
76 
78 
98 template <typename T, std::size_t MaxSize>
100 {
101 #if !defined(PIRANHA_DOXYGEN_INVOKED)
102  static_assert(MaxSize > 0u, "Maximum size must be strictly positive.");
103 #endif
104  template <typename, typename>
105  friend union detail::small_vector_union;
106 
107 public:
109 
112  using size_type = typename detail::static_vector_size_type<MaxSize>::type;
113 
114 private:
115 #if !defined(PIRANHA_DOXYGEN_INVOKED)
117  // This check is against overflows when using memcpy.
118  static_assert(std::numeric_limits<size_type>::max() <= std::numeric_limits<std::size_t>::max() / sizeof(T),
119  "The size type for static_vector might overflow.");
120  // Check for overflow in the definition of the storage type.
121  static_assert(MaxSize < std::numeric_limits<std::size_t>::max() / sizeof(T),
122  "Overflow in the computation of storage size.");
123  // NOTE: some notes about the use of raw storage, after some research in the standard:
124  // - storage type is guaranteed to be a POD able to hold any object whose size is at most Len
125  // and alignment a divisor of Align (20.9.7.6). Thus, we can store on object of type T at the
126  // beginning of the storage;
127  // - POD types are trivially copyable, and thus they occupy contiguous bytes of storage (1.8/5);
128  // - the meaning of "contiguous" seems not to be explicitly stated, but it can be inferred
129  // (e.g., 23.3.2.1/1) that it has to do with the fact that one can do pointer arithmetics
130  // to move around in the storage;
131  // - the footnote in 5.7/6 implies that casting around to different pointer types and then doing
132  // pointer arithmetics has the expected behaviour of moving about with discrete offsets equal to the sizeof
133  // the pointed-to object (i.e., it seems like pointer arithmetics does not suffer from
134  // strict aliasing and type punning issues - at least as long as one goest through void *
135  // conversions to get the address of the storage - note also 3.9.2/3). This supposedly applies to arrays only,
136  // but, again, it is implied in other places that it also holds true for contiguous storage;
137  // - the no-padding guarantee for arrays (which also consist of contiguous storage) implies that sizeof must be
138  // a multiple of the alignment for a given type;
139  // - the definition of alignment (3.11) implies that if one has contiguous storage starting at an address in
140  // which T can be constructed, the offsetting the initial address by multiples of the alignment value will
141  // still produce addresses at which the object can be constructed;
142  // - in general, we are assuming here that we can handle contiguous storage the same way arrays can be handled
143  // (e.g., to get the end() iterator we get one past the last element);
144  // - note that placement new will work as expected (i.e., it will construct the object exactly at the address passed
145  // in as parameter).
146  using storage_type = typename std::aligned_storage<sizeof(T) * MaxSize, alignof(T)>::type;
147 #endif
148  // Serialization support.
149  friend class boost::serialization::access;
150  template <class Archive>
151  void save(Archive &ar, unsigned) const
152  {
153  boost_save_vector(ar, *this);
154  }
155  template <class Archive>
156  void load(Archive &ar, unsigned)
157  {
158  boost_load_vector(ar, *this);
159  }
160  BOOST_SERIALIZATION_SPLIT_MEMBER()
161  // This is a small helper to default-init a range via positional new.
162  // We use this in various POD optimisations below in order to make sure
163  // objects of type T exist in the raw storage before doing anything with them.
164  static void default_init(T *begin, T *end)
165  {
166  for (; begin != end; ++begin) {
167  ::new (static_cast<void *>(begin)) T;
168  }
169  }
170 
171 public:
173 
176  static const size_type max_size = MaxSize;
178  typedef T value_type;
182  typedef value_type const *const_iterator;
184 
187  static_vector() : m_tag(1u), m_size(0u)
188  {
189  }
191 
196  static_vector(const static_vector &other) : m_tag(1u), m_size(0u)
197  {
198  const auto size = other.size();
199  // NOTE: here and elsewhere, the standard implies (3.9/2) that we can use this optimisation
200  // for trivially copyable types. GCC does not support the type trait yet, so we restrict the
201  // optimisation to POD types (which are trivially copyable).
202  if (std::is_pod<T>::value) {
203  // NOTE: we need to def init objects of type T inside the buffer. Unlike in C, objects with trivial default
204  // constructors cannot be created by simply reinterpreting suitably aligned storage,
205  // such as memory allocated with std::malloc: placement-new is required to formally introduce a new objects
206  // and avoid potential undefined behaviour. This is likely to be optimised away by the compiler.
207  default_init(ptr(), ptr() + size);
208  std::memcpy(vs(), other.vs(), size * sizeof(T));
209  m_size = size;
210  } else {
211  try {
212  for (size_type i = 0u; i < size; ++i) {
213  push_back(other[i]);
214  }
215  } catch (...) {
216  // Roll back the constructed items before re-throwing.
217  destroy_items();
218  throw;
219  }
220  }
221  }
223 
226  static_vector(static_vector &&other) noexcept : m_tag(1u), m_size(0u)
227  {
228  const auto size = other.size();
229  if (std::is_pod<T>::value) {
230  default_init(ptr(), ptr() + size);
231  std::memcpy(vs(), other.vs(), size * sizeof(T));
232  m_size = size;
233  } else {
234  // NOTE: here no need for rollback, as we assume move ctors do not throw.
235  for (size_type i = 0u; i < size; ++i) {
236  push_back(std::move(other[i]));
237  }
238  }
239  // Nuke other.
240  other.destroy_items();
241  other.m_size = 0u;
242  }
244 
252  explicit static_vector(const size_type &n, const value_type &x) : m_tag(1u), m_size(0u)
253  {
254  try {
255  for (size_type i = 0u; i < n; ++i) {
256  push_back(x);
257  }
258  } catch (...) {
259  destroy_items();
260  throw;
261  }
262  }
264 
268  {
269  // NOTE: here we should replace with bidirectional tt, if we ever implement it.
272  piranha_assert(m_tag == 1u);
273  destroy_items();
274  }
276 
284  {
285  if (likely(this != &other)) {
286  if (std::is_pod<T>::value) {
287  if (other.m_size > m_size) {
288  // If other is larger, we need to make sure we have created the excess objects
289  // before writing into them.
290  default_init(ptr() + m_size, ptr() + other.m_size);
291  }
292  std::memcpy(vs(), other.vs(), other.m_size * sizeof(T));
293  m_size = other.m_size;
294  } else {
295  static_vector tmp(other);
296  *this = std::move(tmp);
297  }
298  }
299  return *this;
300  }
302 
308  {
309  if (likely(this != &other)) {
310  if (std::is_pod<T>::value) {
311  if (other.m_size > m_size) {
312  default_init(ptr() + m_size, ptr() + other.m_size);
313  }
314  std::memcpy(vs(), other.vs(), other.m_size * sizeof(T));
315  } else {
316  const size_type old_size = m_size, new_size = other.m_size;
317  if (old_size <= new_size) {
318  // Move assign new content into old content.
319  for (size_type i = 0u; i < old_size; ++i) {
320  (*this)[i] = std::move(other[i]);
321  }
322  // Move construct remaining elements.
323  for (size_type i = old_size; i < new_size; ++i) {
324  ::new (static_cast<void *>(ptr() + i)) value_type(std::move(other[i]));
325  }
326  } else {
327  // Move assign new content into old content.
328  for (size_type i = 0u; i < new_size; ++i) {
329  (*this)[i] = std::move(other[i]);
330  }
331  // Destroy excess content.
332  for (size_type i = new_size; i < old_size; ++i) {
333  ptr()[i].~T();
334  }
335  }
336  }
337  m_size = other.m_size;
338  // Nuke the other.
339  other.destroy_items();
340  other.m_size = 0u;
341  }
342  return *this;
343  }
345 
350  const value_type &operator[](const size_type &n) const
351  {
352  return ptr()[n];
353  }
355 
361  {
362  return ptr()[n];
363  }
365 
369  {
370  return ptr();
371  }
373 
377  {
378  return ptr() + m_size;
379  }
381 
385  {
386  return ptr();
387  }
389 
393  {
394  return ptr() + m_size;
395  }
397 
405  bool operator==(const static_vector &other) const
406  {
407  return (m_size == other.m_size && std::equal(begin(), end(), other.begin()));
408  }
410 
417  bool operator!=(const static_vector &other) const
418  {
419  return !operator==(other);
420  }
422 
430  void push_back(const value_type &x)
431  {
432  if (unlikely(m_size == MaxSize)) {
433  piranha_throw(std::bad_alloc, );
434  }
435  ::new (static_cast<void *>(ptr() + m_size)) value_type(x);
436  ++m_size;
437  }
439 
447  {
448  if (unlikely(m_size == MaxSize)) {
449  piranha_throw(std::bad_alloc, );
450  }
451  ::new (static_cast<void *>(ptr() + m_size)) value_type(std::move(x));
452  ++m_size;
453  }
454 
455 private:
456  template <typename... Args>
457  using emplace_enabler = enable_if_t<std::is_constructible<value_type, Args &&...>::value, int>;
458 
459 public:
461 
472  template <typename... Args, emplace_enabler<Args...> = 0>
473  void emplace_back(Args &&... params)
474  {
475  if (unlikely(m_size == MaxSize)) {
476  piranha_throw(std::bad_alloc, );
477  }
478  ::new (static_cast<void *>(ptr() + m_size)) value_type(std::forward<Args>(params)...);
479  ++m_size;
480  }
482 
485  size_type size() const
486  {
487  return m_size;
488  }
490 
493  bool empty() const
494  {
495  return m_size == 0u;
496  }
498 
509  void resize(const size_type &new_size)
510  {
511  if (unlikely(new_size > MaxSize)) {
512  piranha_throw(std::bad_alloc, );
513  }
514  const auto old_size = m_size;
515  if (new_size == old_size) {
516  return;
517  } else if (new_size > old_size) {
518  size_type i = old_size;
519  // Construct new in case of larger size.
520  try {
521  for (; i < new_size; ++i) {
522  // NOTE: placement-new syntax for value initialization, the same as performed by
523  // std::vector (see 23.3.6.2 and 23.3.6.3/9).
524  // http://en.cppreference.com/w/cpp/language/value_initialization
525  ::new (static_cast<void *>(ptr() + i)) value_type();
526  piranha_assert(m_size != MaxSize);
527  ++m_size;
528  }
529  } catch (...) {
530  for (size_type j = old_size; j < i; ++j) {
531  ptr()[j].~T();
532  piranha_assert(m_size);
533  --m_size;
534  }
535  throw;
536  }
537  } else {
538  // Destroy in case of smaller size.
539  for (size_type i = new_size; i < old_size; ++i) {
540  ptr()[i].~T();
541  piranha_assert(m_size);
542  --m_size;
543  }
544  }
545  }
547 
559  {
560  // NOTE: on the use of const_cast: the object 'it' points to is *not* const by definition
561  // (it is an element of a vector). So the const casting should be ok. See these links:
562  // http://stackoverflow.com/questions/30770944/is-this-a-valid-usage-of-const-cast
563  // http://en.cppreference.com/w/cpp/language/const_cast
564  // All of this is noexcept.
565  piranha_assert(it != end());
566  // The return value is the original iterator, which will eventually contain the value
567  // following the original 'it'. If 'it' pointed to the last element, then retval
568  // will be the new end().
569  auto retval = const_cast<iterator>(it);
570  const auto it_f = end() - 1;
571  // NOTE: POD optimisation is possible here, but requires pointer diff.
572  for (; it != it_f; ++it) {
573  // Destroy the current element.
574  it->~T();
575  // Move-init the current iterator content with the next element.
576  ::new (static_cast<void *>(const_cast<iterator>(it))) value_type(std::move(*(it + 1)));
577  }
578  // Destroy the last element.
579  it->~T();
580  // Update the size.
581  m_size = static_cast<size_type>(m_size - 1u);
582  return retval;
583  }
585 
588  void clear()
589  {
590  destroy_items();
591  m_size = 0u;
592  }
593 
594 private:
595  template <typename U>
596  using hash_enabler = enable_if_t<is_hashable<U>::value, int>;
597 
598 public:
600 
616  template <typename U = T, hash_enabler<U> = 0>
617  std::size_t hash() const
618  {
619  return detail::vector_hasher(*this);
620  }
621 
622 private:
623  template <typename U>
624  using ostream_enabler = enable_if_t<is_ostreamable<U>::value, int>;
625 
626 public:
628 
641  template <typename U = T, ostream_enabler<U> = 0>
642  friend std::ostream &operator<<(std::ostream &os, const static_vector &v)
643  {
644  os << '[';
645  for (decltype(v.size()) i = 0u; i < v.size(); ++i) {
646  os << v[i];
647  if (i != v.size() - 1u) {
648  os << ',';
649  }
650  }
651  os << ']';
652  return os;
653  }
654 
655 private:
656  void destroy_items()
657  {
658  // NOTE: no need to destroy anything in this case:
659  // http://en.cppreference.com/w/cpp/language/destructor
660  // NOTE: here we could have the destructor defined in a base class to be specialised
661  // if T is trivially destructible. Then we can default the dtor here and static_vector
662  // will be trivially destructible if T is.
663  if (std::is_trivially_destructible<T>::value) {
664  return;
665  }
666  const auto size = m_size;
667  for (size_type i = 0u; i < size; ++i) {
668  ptr()[i].~T();
669  }
670  }
671  // Getters for storage cast to void.
672  void *vs()
673  {
674  return static_cast<void *>(&m_storage);
675  }
676  const void *vs() const
677  {
678  return static_cast<const void *>(&m_storage);
679  }
680  // Getters for T pointer.
681  T *ptr()
682  {
683  return static_cast<T *>(vs());
684  }
685  const T *ptr() const
686  {
687  return static_cast<const T *>(vs());
688  }
689 
690 private:
691  unsigned char m_tag;
692  size_type m_size;
693  storage_type m_storage;
694 };
695 
696 template <typename T, std::size_t MaxSize>
698 
700 
707 template <typename Archive, typename T, std::size_t S>
708 struct boost_save_impl<Archive, static_vector<T, S>, boost_save_vector_enabler<Archive, static_vector<T, S>>>
709  : boost_save_via_boost_api<Archive, static_vector<T, S>> {
710 };
711 
713 
724 template <typename Archive, typename T, std::size_t S>
725 struct boost_load_impl<Archive, static_vector<T, S>, boost_load_vector_enabler<Archive, static_vector<T, S>>>
726  : boost_load_via_boost_api<Archive, static_vector<T, S>> {
727 };
728 
729 #if defined(PIRANHA_WITH_MSGPACK)
730 
732 
739 template <typename Stream, typename T, std::size_t S>
740 struct msgpack_pack_impl<Stream, static_vector<T, S>, msgpack_pack_vector_enabler<Stream, static_vector<T, S>>> {
742 
754  void operator()(msgpack::packer<Stream> &p, const static_vector<T, S> &v, msgpack_format f) const
755  {
756  msgpack_pack_vector(p, v, f);
757  }
758 };
759 
761 
767 template <typename T, std::size_t S>
768 struct msgpack_convert_impl<static_vector<T, S>, msgpack_convert_array_enabler<static_vector<T, S>>> {
770 
784  void operator()(static_vector<T, S> &v, const msgpack::object &o, msgpack_format f) const
785  {
786  msgpack_convert_array(o, v, f);
787  }
788 };
789 
790 #endif
791 }
792 
793 #endif
const_iterator end() const
Const end iterator.
typename detail::static_vector_size_type< MaxSize >::type size_type
Size type.
iterator begin()
Begin iterator.
value_type * iterator
Iterator type.
static_vector(const static_vector &other)
Copy constructor.
Default implementation of piranha::boost_load().
Definition: s11n.hpp:391
void operator()(static_vector< T, S > &v, const msgpack::object &o, msgpack_format f) const
Call operator.
Implementation of piranha::boost_load() via the Boost API.
Definition: s11n.hpp:363
Exceptions.
static_vector & operator=(const static_vector &other)
Copy assignment operator.
Default implementation of piranha::boost_save().
Definition: s11n.hpp:245
STL namespace.
void emplace_back(Args &&... params)
Construct in-place at the end of the vector.
void resize(const size_type &new_size)
Resize.
const value_type & operator[](const size_type &n) const
Const subscript operator.
void operator()(msgpack::packer< Stream > &p, const static_vector< T, S > &v, msgpack_format f) const
Call operator.
Forward iterator type trait.
iterator erase(const_iterator it)
Erase element.
bool empty() const
Empty test.
Default functor for the implementation of piranha::msgpack_convert().
Definition: s11n.hpp:867
value_type const * const_iterator
Const iterator type.
#define piranha_throw(exception_type,...)
Exception-throwing macro.
Definition: exceptions.hpp:118
iterator end()
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
T value_type
Contained type.
static_vector & operator=(static_vector &&other) noexcept
Move assignment operator.
bool operator==(const static_vector &other) const
Equality operator.
void push_back(value_type &&x)
Move-add element at the end of the vector.
Static vector class.
std::size_t hash() const
Hash value.
static_vector()
Default constructor.
Implementation of piranha::boost_save() via the Boost API.
Definition: s11n.hpp:217
Root piranha namespace.
Definition: array_key.hpp:52
value_type & operator[](const size_type &n)
Subscript operator.
void push_back(const value_type &x)
Copy-add element at the end of the vector.
Type traits.
~static_vector()
Destructor.
friend std::ostream & operator<<(std::ostream &os, const static_vector &v)
Stream operator overload for piranha::static_vector.
static const size_type max_size
Maximum size.
Type trait for well-behaved container elements.
#define PIRANHA_TT_CHECK(tt,...)
Macro for static type trait checks.
bool operator!=(const static_vector &other) const
Inequality operator.
static_vector(const size_type &n, const value_type &x)
Constructor from multiple copies.
static_vector(static_vector &&other) noexcept
Move constructor.
size_type size() const
Size.
const_iterator begin() const
Const begin iterator.