pstore2
chunked_sequence.hpp
Go to the documentation of this file.
1 //===- include/pstore/adt/chunked_sequence.hpp ------------*- mode: C++ -*-===//
2 //* _ _ _ *
3 //* ___| |__ _ _ _ __ | | _____ __| | *
4 //* / __| '_ \| | | | '_ \| |/ / _ \/ _` | *
5 //* | (__| | | | |_| | | | | < __/ (_| | *
6 //* \___|_| |_|\__,_|_| |_|_|\_\___|\__,_| *
7 //* *
8 //* *
9 //* ___ ___ __ _ _ _ ___ _ __ ___ ___ *
10 //* / __|/ _ \/ _` | | | |/ _ \ '_ \ / __/ _ \ *
11 //* \__ \ __/ (_| | |_| | __/ | | | (_| __/ *
12 //* |___/\___|\__, |\__,_|\___|_| |_|\___\___| *
13 //* |_| *
14 //===----------------------------------------------------------------------===//
15 //
16 // Part of the pstore project, under the Apache License v2.0 with LLVM Exceptions.
17 // See https://github.com/SNSystems/pstore/blob/master/LICENSE.txt for license
18 // information.
19 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
20 //
21 //===----------------------------------------------------------------------===//
27 #ifndef PSTORE_ADT_CHUNKED_SEQUENCE_HPP
28 #define PSTORE_ADT_CHUNKED_SEQUENCE_HPP
29 
30 #include <algorithm>
31 #include <array>
32 #include <cassert>
33 #include <list>
34 
38 
39 namespace pstore {
40 
56  //-MARK: chunked_sequence
57  template <typename T,
58  std::size_t ElementsPerChunk = std::max (4096 / sizeof (T), std::size_t{1}),
59  std::size_t ActualSize = sizeof (T), std::size_t ActualAlign = alignof (T)>
61  static_assert (ElementsPerChunk > 0, "Must be at least 1 element per chunk");
62  static_assert (ActualSize >= sizeof (T), "ActualSize must be at least sizeof(T)");
63  static_assert (ActualAlign >= alignof (T), "ActualAlign must be at least alignof(T)");
64 
65  template <bool Const = true>
66  class iterator_base;
67 
68  public:
69  using value_type = T;
70  using size_type = std::size_t;
71  using difference_type = std::ptrdiff_t;
72  using reference = value_type &;
73  using const_reference = value_type const &;
74  using pointer = T *;
75  using const_pointer = T const *;
76 
77  using iterator = iterator_base<false>;
78  using const_iterator = iterator_base<true>;
79  // using reverse_iterator =
80  // using const_reverse_iterator =
81 
82  class chunk;
83  using chunk_list = std::list<chunk>;
84 
86  static constexpr std::size_t elements_per_chunk = ElementsPerChunk;
87 
89  chunked_sequence (chunked_sequence const &) = delete;
90  chunked_sequence (chunked_sequence && other) noexcept;
91 
92  ~chunked_sequence () noexcept = default;
93 
94  chunked_sequence & operator= (chunked_sequence const & other) noexcept = delete;
95  chunked_sequence & operator= (chunked_sequence && other) noexcept;
96 
97  constexpr bool empty () const noexcept { return size_ == 0; }
98  constexpr size_type size () const noexcept { return size_; }
99 
100  iterator begin () noexcept { return {chunks_.begin (), 0U}; }
101  const_iterator begin () const noexcept {
102  PSTORE_ASSERT (empty () || chunks_.front ().size () > 0);
103  return {chunks_.begin (), 0U};
104  }
105  const_iterator cbegin () const noexcept { return begin (); }
106 
107  iterator end () noexcept { return end_impl (*this); }
108  const_iterator end () const noexcept { return end_impl (*this); }
109  const_iterator cend () const noexcept { return end (); }
110 
111 
112 
113  typename chunk_list::iterator chunks_begin () noexcept { return chunks_.begin (); }
114  typename chunk_list::const_iterator chunks_begin () const noexcept {
115  return chunks_.begin ();
116  }
117  typename chunk_list::const_iterator chunks_cbegin () noexcept { return chunks_.cbegin (); }
118  typename chunk_list::iterator chunks_end () noexcept { return chunks_.end (); }
119  typename chunk_list::const_iterator chunks_end () const noexcept { return chunks_.end (); }
120  typename chunk_list::const_iterator chunks_cend () noexcept { return chunks_.cend (); }
121 
122  std::size_t chunks_size () const noexcept { return chunks_.size (); }
123 
124 
125 
126  void clear () {
127  chunks_.clear ();
128  // Ensure that there's always at least one chunk.
129  chunks_.emplace_back ();
130  size_ = 0;
131  }
132  void reserve (std::size_t const /*size*/) {
133  // TODO: Not currently implemented.
134  }
135 
139  std::size_t capacity () const noexcept { return chunks_.size () * ElementsPerChunk; }
140 
148  void resize (size_type count) {
149  if (count > size_) {
150  return this->resize_grow (count);
151  }
152  if (count < size_) {
153  this->resize_shrink (count);
154  }
155  }
156 
157  template <typename... Args>
158  reference emplace_back (Args &&... args);
159 
160  reference push_back (T const & value) { return emplace_back (value); }
161  reference push_back (T && value) { return emplace_back (std::move (value)); }
162 
165  reference front () {
166  PSTORE_ASSERT (size_ > 0);
167  return chunks_.front ().front ();
168  }
171  const_reference front () const {
172  PSTORE_ASSERT (size_ > 0);
173  return chunks_.front ().front ();
174  }
175 
178  reference back () {
179  PSTORE_ASSERT (size_ > 0);
180  return chunks_.back ().back ();
181  }
184  const_reference back () const {
185  PSTORE_ASSERT (size_ > 0);
186  return chunks_.back ().back ();
187  }
188 
189  void swap (chunked_sequence & other) noexcept {
190  std::swap (chunks_, other.chunks_);
191  std::swap (size_, other.size_);
192  }
193 
194  void splice (chunked_sequence && other) {
195  // If the other CV is empty then do nothing.
196  if (other.empty ()) {
197  return;
198  }
199  // If this CV is empty then we need to replace the pre-allocated chunk rather than
200  // splice onto the end of our chunk list.
201  if (empty ()) {
202  size_ = other.size ();
203  chunks_ = std::move (other.chunks_);
204  return;
205  }
206  size_ += other.size ();
207  chunks_.splice (chunks_.end (), std::move (other.chunks_));
208  }
209 
210  private:
211  template <typename ChunkedSequence, typename Result = typename inherit_const<
212  ChunkedSequence, iterator, const_iterator>::type>
213  static Result end_impl (ChunkedSequence & cv) noexcept;
214 
219  void resize_grow (size_type count);
220 
224  void resize_shrink (size_type count);
225 
226  chunk_list chunks_;
227  size_type size_ = 0;
228  };
229 
230  namespace details {
231 
233  template <typename T, bool IsConst>
234  struct value_type {
235  using type = typename std::conditional<IsConst, T const, T>::type;
236  };
237 
238  } // end namespace details
239 
240  // (ctor)
241  // ~~~~~~
242  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
243  std::size_t ActualAlign>
245  // Create an initial, empty chunk. This avoids checking whether the chunk list is empty
246  // in the (performance sensitive) append function.
247  chunks_.emplace_back ();
248  }
249 
250  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
251  std::size_t ActualAlign>
253  chunked_sequence && other) noexcept
254  : chunks_{std::move (other.chunks_)}
255  , size_{other.size_} {
256  other.size_ = 0;
257  }
258 
259  // operator=
260  // ~~~~~~~~~
261  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
262  std::size_t ActualAlign>
264  chunked_sequence && other) noexcept -> chunked_sequence & {
265  if (this != &other) {
266  chunks_ = std::move (other.chunks_);
267  size_ = other.size_;
268  other.size_ = 0;
269  }
270  return *this;
271  }
272 
273  // emplace back
274  // ~~~~~~~~~~~~
275  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
276  std::size_t ActualAlign>
277  template <typename... Args>
278  auto
280  -> reference {
281  PSTORE_ASSERT (chunks_.size () > 0U);
282  auto * tail = &chunks_.back ();
283  if (PSTORE_UNLIKELY (tail->size () >= ElementsPerChunk)) {
284  // Append a new chunk.
285  chunks_.emplace_back ();
286  tail = &chunks_.back ();
287  }
288  // Append a new instance to the tail chunk.
289  reference result = tail->emplace_back (std::forward<Args> (args)...);
290  ++size_;
291  return result;
292  }
293 
294  // end impl
295  // ~~~~~~~~
296  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
297  std::size_t ActualAlign>
298  template <typename ChunkedSequence, typename Result>
300  ChunkedSequence & cv) noexcept {
301  // We always have at least 1 chunk. If the container is empty then return the begin iterator
302  // otherwise an iterator referencing end and of the last member of the last chunk.
303  PSTORE_ASSERT (cv.chunks_.size () > 0U);
304  if (cv.size () > 0U) {
305  return {cv.chunks_.end (), 0U};
306  }
307  return cv.begin ();
308  }
309 
310  // resize grow
311  // ~~~~~~~~~~~
312  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
313  std::size_t ActualAlign>
314  void
316  auto const grow_chunk = [this] (chunk * const c, std::size_t const limit) {
317  assert (limit <= c->capacity ());
318  for (auto ctr = limit; ctr > 0U; --ctr) {
319  c->emplace_back ();
320  // Increment size each time in case emplace_back() throws.
321  ++size_;
322  }
323  return limit;
324  };
325 
326  if (count <= size_) {
327  return; // Nothing to do.
328  }
329 
330  count -= size_;
331  {
332  // First we must fill the current tail chunk with new default-constructed elements.
333  auto & tail = chunks_.back ();
334  std::size_t const tc = tail.capacity ();
335  // If the last chunk partially occupied? If so, fill the rest of it.
336  std::size_t const limit = std::min (count, tc);
337  count -= grow_chunk (&tail, limit);
338  assert (tail.capacity () == tc - limit);
339  }
340 
341  // Allocate as many chunks as necessary filling them with the correct number of
342  // default-initialized members.
343  while (count > 0U) {
344  // Append a new chunk.
345  chunks_.emplace_back ();
346  // Fill the chunk with default-constructed elements.
347  count -= grow_chunk (&chunks_.back (), std::min (ElementsPerChunk, count));
348  }
349  }
350 
351  // resize shrink
352  // ~~~~~~~~~~~~~
353  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
354  std::size_t ActualAlign>
356  size_type count) {
357  if (count >= size_) {
358  return; // Nothing to do.
359  }
360 
361  // 'count' becomes the number of elements to remove.
362  count = size_ - count;
363  auto & head = chunks_.front ();
364  while (count > 0U) {
365  auto & tail = chunks_.back ();
366  auto in_chunk = tail.size ();
367  // Note that we don't delete head: there is always at least one chunk.
368  if (count >= in_chunk && &tail != &head) {
369  chunks_.pop_back ();
370  } else {
371  assert (in_chunk >= count);
372  tail.shrink (in_chunk - count);
373  in_chunk = count;
374  }
375  count -= in_chunk;
376  size_ -= in_chunk;
377  }
378  }
379 
380  //* _ _ *
381  //* __| |_ _ _ _ _ | |__ *
382  //* / _| ' \ || | ' \| / / *
383  //* \__|_||_\_,_|_||_|_\_\ *
384  //* *
385  //-MARK: chunk
386  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
387  std::size_t ActualAlign>
388  class chunked_sequence<T, ElementsPerChunk, ActualSize, ActualAlign>::chunk {
389  public:
392 
393  chunk () noexcept { // NOLINT
394  // Don't use "=default" here. We don't want the zero initializer for membs_ to run.
395  }
396  chunk (chunk const &) = delete;
397  chunk (chunk &&) noexcept = delete;
398 
399  ~chunk () noexcept;
400 
401  chunk & operator= (chunk const &) = delete;
402  chunk & operator= (chunk &&) noexcept = delete;
403 
404  T * data () noexcept { return membs_.data (); }
405  T const * data () const noexcept { return membs_.data (); }
406 
407  T & operator[] (std::size_t const index) noexcept {
408  PSTORE_ASSERT (index < ElementsPerChunk);
409  return reinterpret_cast<T &> (membs_[index]);
410  }
411  T const & operator[] (std::size_t const index) const noexcept {
412  PSTORE_ASSERT (index < ElementsPerChunk);
413  return reinterpret_cast<T const &> (membs_[index]);
414  }
415  constexpr std::size_t size () const noexcept { return size_; }
416  constexpr bool empty () const noexcept { return size_ == 0U; }
417 
418  constexpr std::size_t capacity () const noexcept {
419  return ElementsPerChunk - this->size ();
420  }
421 
422  reference front () { return (*this)[0]; }
423  const_reference front () const { return (*this)[0]; }
424  reference back () { return (*this)[size_ - 1U]; }
425  const_reference back () const { return (*this)[size_ - 1U]; }
426 
427 
428  auto begin () noexcept { return iterator{&(*this)[0]}; }
429  auto begin () const noexcept { return const_iterator{&(*this)[0]}; }
430  auto end () noexcept { return iterator{begin () + size_}; }
431  auto end () const noexcept { return const_iterator{begin () + size_}; }
432 
433  template <typename... Args>
434  reference emplace_back (Args &&... args);
435 
436  void shrink (std::size_t new_size) noexcept;
437 
438  private:
439  std::size_t size_ = 0U;
440  std::array<typename std::aligned_storage<ActualSize, ActualAlign>::type, ElementsPerChunk>
441  membs_;
442  };
443 
444  // (dtor)
445  // ~~~~~~
446  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
447  std::size_t ActualAlign>
449  this->shrink (0U);
450  }
451 
452  // emplace back
453  // ~~~~~~~~~~~~
454  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
455  std::size_t ActualAlign>
456  template <typename... Args>
458  Args &&... args) -> reference {
459  PSTORE_ASSERT (size_ < membs_.size ());
460  T & place = (*this)[size_];
461  new (&place) T (std::forward<Args> (args)...);
462  ++size_;
463  return place;
464  }
465 
466  // shrink
467  // ~~~~~~
468  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
469  std::size_t ActualAlign>
471  std::size_t const new_size) noexcept {
472  assert (new_size <= size_);
473  for (auto ctr = new_size; ctr < size_; ++ctr) {
474  (*this)[ctr].~T ();
475  }
476  size_ = new_size;
477  }
478 
479  //* _ _ _ _ *
480  //* (_) |_ ___ _ _ __ _| |_ ___ _ _ | |__ __ _ ___ ___ *
481  //* | | _/ -_) '_/ _` | _/ _ \ '_| | '_ \/ _` (_-</ -_) *
482  //* |_|\__\___|_| \__,_|\__\___/_| |_.__/\__,_/__/\___| *
483  //* *
484  // -MARK: iterator base
485  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
486  std::size_t ActualAlign>
487  template <bool IsConst>
488  class chunked_sequence<T, ElementsPerChunk, ActualSize, ActualAlign>::iterator_base {
489  using list_iterator =
490  typename std::conditional<IsConst, typename chunk_list::const_iterator,
491  typename chunk_list::iterator>::type;
492 
493  friend class iterator_base<true>;
494 
495  public:
496  using iterator_category = std::bidirectional_iterator_tag;
497  using value_type = typename details::value_type<T, IsConst>::type;
498  using difference_type = std::ptrdiff_t;
499  using pointer = value_type *;
500  using reference = value_type &;
501 
502  iterator_base (list_iterator const it, std::size_t const index)
503  : it_{it}
504  , index_{index} {}
505  // Note that we want to allow implicit conversions from non-const to const iterators.
506  iterator_base (iterator_base<false> const & rhs) // NOLINT(hicpp-explicit-conversions)
507  : it_{rhs.it_}
508  , index_{rhs.index_} {}
509  iterator_base (iterator_base<false> && rhs) // NOLINT(hicpp-explicit-conversions)
510  : it_{std::move (rhs.it_)}
511  , index_{std::move (rhs.index_)} {}
512 
513  iterator_base & operator= (iterator_base<false> const & rhs) {
514  it_ = rhs.it_;
515  index_ = rhs.index_;
516  return *this;
517  }
518 
519  iterator_base & operator= (iterator_base<false> && rhs) {
520  if (&rhs != this) {
521  it_ = std::move (rhs.it_);
522  index_ = std::move (rhs.index_);
523  }
524  return *this;
525  }
526 
527  bool operator== (iterator_base const & other) const {
528  return it_ == other.it_ && index_ == other.index_;
529  }
530  bool operator!= (iterator_base const & other) const { return !operator== (other); }
531 
535  reference operator* () const noexcept { return (*it_)[index_]; }
536  pointer operator-> () const noexcept { return &(*it_)[index_]; }
537 
538  iterator_base & operator++ ();
539  iterator_base operator++ (int);
540  iterator_base & operator-- ();
541  iterator_base operator-- (int);
542 
543  private:
544  list_iterator it_;
545  std::size_t index_;
546  };
547 
548  // Prefix increment
549  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
550  std::size_t ActualAlign>
551  template <bool IsConst>
552  auto chunked_sequence<T, ElementsPerChunk, ActualSize,
553  ActualAlign>::iterator_base<IsConst>::operator++ ()
554  -> iterator_base<IsConst> & {
555  if (++index_ >= it_->size ()) {
556  ++it_;
557  index_ = 0;
558  }
559  return *this;
560  }
561 
562  // Postfix increment operator (e.g., it++)
563  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
564  std::size_t ActualAlign>
565  template <bool IsConst>
566  auto chunked_sequence<T, ElementsPerChunk, ActualSize,
567  ActualAlign>::iterator_base<IsConst>::operator++ (int)
568  -> iterator_base<IsConst> {
569  auto const old = *this;
570  ++(*this);
571  return old;
572  }
573 
574  // Prefix decrement
575  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
576  std::size_t ActualAlign>
577  template <bool IsConst>
578  auto chunked_sequence<T, ElementsPerChunk, ActualSize,
579  ActualAlign>::iterator_base<IsConst>::operator-- ()
580  -> iterator_base<IsConst> & {
581  if (index_ > 0U) {
582  --index_;
583  } else {
584  --it_;
585  PSTORE_ASSERT (it_->size () > 0);
586  index_ = it_->size () - 1U;
587  }
588  return *this;
589  }
590 
591  // Postfix decrement
592  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
593  std::size_t ActualAlign>
594  template <bool IsConst>
595  auto chunked_sequence<T, ElementsPerChunk, ActualSize,
596  ActualAlign>::iterator_base<IsConst>::operator-- (int)
597  -> iterator_base<IsConst> {
598  auto old = *this;
599  --(*this);
600  return old;
601  }
602 
603 } // end namespace pstore
604 
605 // std::swap may be specialized in namespace std for program-defined types
606 // NOLINTNEXTLINE(cert-dcl58-cpp)
607 namespace std {
608 
609  template <typename T, std::size_t ElementsPerChunk, std::size_t ActualSize,
610  std::size_t ActualAlign>
611  void
614  lhs.swap (rhs);
615  }
616 
617 } // end namespace std
618 
619 #endif // PSTORE_ADT_CHUNKED_SEQUENCE_HPP
const_reference back() const
Returns a reference to the last element in the container.
Definition: chunked_sequence.hpp:184
Definition: chunked_sequence.hpp:607
reference back()
Returns a reference to the last element in the container.
Definition: chunked_sequence.hpp:178
transaction< transaction_lock > begin(database &db, transaction_lock &lock)
Creates a new transaction. Every operation performed on a transaction instance can be potentially und...
Definition: transaction.hpp:311
Definition: print.cpp:18
Yields either &#39;T&#39; or &#39;T const&#39; depending on the value is IsConst.
Definition: chunked_sequence.hpp:234
void resize(size_type count)
Resizes the container to contain count elements.
Definition: chunked_sequence.hpp:148
A utility template intended to simplify the writing of the const- and non-const variants of member fu...
Definition: pointer_based_iterator.hpp:53
Definition: nonpod2.cpp:40
Provides a member typedef inherit_const::type, which is defined as R const if T is a const type and R...
Definition: inherit_const.hpp:108
Definition: chunked_sequence.hpp:388
An implementation of the standard assert() macro with the exception that it will, on failure...
const_reference front() const
Returns a reference to the first element in the container.
Definition: chunked_sequence.hpp:171
Chunked-sequence is a sequence-container which uses a list of large blocks ("chunks") to ensure very ...
Definition: chunked_sequence.hpp:60
Provides pointer_based_iterator<> an iterator wrapper for pointers.
reference front()
Returns a reference to the first element in the container.
Definition: chunked_sequence.hpp:165
std::size_t capacity() const noexcept
Returns the number of elements that the container has currently allocated space for.
Definition: chunked_sequence.hpp:139