Imported Upstream version 1.72.0
[platform/upstream/boost.git] / boost / histogram / storage_adaptor.hpp
1 // Copyright 2018-2019 Hans Dembinski
2 //
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE_1_0.txt
5 // or copy at http://www.boost.org/LICENSE_1_0.txt)
6
7 #ifndef BOOST_HISTOGRAM_STORAGE_ADAPTOR_HPP
8 #define BOOST_HISTOGRAM_STORAGE_ADAPTOR_HPP
9
10 #include <algorithm>
11 #include <boost/core/nvp.hpp>
12 #include <boost/histogram/detail/array_wrapper.hpp>
13 #include <boost/histogram/detail/detect.hpp>
14 #include <boost/histogram/detail/iterator_adaptor.hpp>
15 #include <boost/histogram/detail/safe_comparison.hpp>
16 #include <boost/histogram/fwd.hpp>
17 #include <boost/mp11/utility.hpp>
18 #include <boost/throw_exception.hpp>
19 #include <stdexcept>
20 #include <type_traits>
21
22 namespace boost {
23 namespace histogram {
24 namespace detail {
25
26 template <class T>
27 struct vector_impl : T {
28   using allocator_type = typename T::allocator_type;
29
30   static constexpr bool has_threading_support =
31       accumulators::is_thread_safe<typename T::value_type>::value;
32
33   vector_impl(const allocator_type& a = {}) : T(a) {}
34   vector_impl(const vector_impl&) = default;
35   vector_impl& operator=(const vector_impl&) = default;
36   vector_impl(vector_impl&&) = default;
37   vector_impl& operator=(vector_impl&&) = default;
38
39   explicit vector_impl(T&& t) : T(std::move(t)) {}
40   explicit vector_impl(const T& t) : T(t) {}
41
42   template <class U, class = requires_iterable<U>>
43   explicit vector_impl(const U& u, const allocator_type& a = {})
44       : T(std::begin(u), std::end(u), a) {}
45
46   template <class U, class = requires_iterable<U>>
47   vector_impl& operator=(const U& u) {
48     T::resize(u.size());
49     auto it = T::begin();
50     for (auto&& x : u) *it++ = x;
51     return *this;
52   }
53
54   void reset(std::size_t n) {
55     using value_type = typename T::value_type;
56     const auto old_size = T::size();
57     T::resize(n, value_type());
58     std::fill_n(T::begin(), (std::min)(n, old_size), value_type());
59   }
60
61   template <class Archive>
62   void serialize(Archive& ar, unsigned /* version */) {
63     ar& make_nvp("vector", static_cast<T&>(*this));
64   }
65 };
66
67 template <class T>
68 struct array_impl : T {
69   static constexpr bool has_threading_support =
70       accumulators::is_thread_safe<typename T::value_type>::value;
71
72   array_impl() = default;
73   array_impl(const array_impl& t) : T(t), size_(t.size_) {}
74   array_impl& operator=(const array_impl& t) {
75     T::operator=(t);
76     size_ = t.size_;
77     return *this;
78   }
79
80   explicit array_impl(T&& t) : T(std::move(t)) {}
81   explicit array_impl(const T& t) : T(t) {}
82
83   template <class U, class = requires_iterable<U>>
84   explicit array_impl(const U& u) : size_(u.size()) {
85     using std::begin;
86     using std::end;
87     std::copy(begin(u), end(u), this->begin());
88   }
89
90   template <class U, class = requires_iterable<U>>
91   array_impl& operator=(const U& u) {
92     if (u.size() > T::max_size()) // for std::arra
93       BOOST_THROW_EXCEPTION(std::length_error("argument size exceeds maximum capacity"));
94     size_ = u.size();
95     using std::begin;
96     using std::end;
97     std::copy(begin(u), end(u), T::begin());
98     return *this;
99   }
100
101   void reset(std::size_t n) {
102     using value_type = typename T::value_type;
103     if (n > T::max_size()) // for std::array
104       BOOST_THROW_EXCEPTION(std::length_error("argument size exceeds maximum capacity"));
105     std::fill_n(T::begin(), n, value_type());
106     size_ = n;
107   }
108
109   typename T::iterator end() noexcept { return T::begin() + size_; }
110   typename T::const_iterator end() const noexcept { return T::begin() + size_; }
111
112   std::size_t size() const noexcept { return size_; }
113
114   template <class Archive>
115   void serialize(Archive& ar, unsigned /* version */) {
116     ar& make_nvp("size", size_);
117     auto w = detail::make_array_wrapper(T::data(), size_);
118     ar& make_nvp("array", w);
119   }
120
121   std::size_t size_ = 0;
122 };
123
124 template <class T>
125 struct map_impl : T {
126   static_assert(std::is_same<typename T::key_type, std::size_t>::value,
127                 "requires std::size_t as key_type");
128
129   using value_type = typename T::mapped_type;
130   using const_reference = const value_type&;
131
132   static constexpr bool has_threading_support = false;
133   static_assert(
134       !accumulators::is_thread_safe<value_type>::value,
135       "std::map and std::unordered_map do not support thread-safe element access. "
136       "If you have a map with thread-safe element access, please file an issue and"
137       "support will be added.");
138
139   struct reference {
140     reference(map_impl* m, std::size_t i) noexcept : map(m), idx(i) {}
141
142     reference(const reference&) noexcept = default;
143     reference& operator=(const reference& o) {
144       if (this != &o) operator=(static_cast<const_reference>(o));
145       return *this;
146     }
147
148     operator const_reference() const noexcept {
149       return static_cast<const map_impl*>(map)->operator[](idx);
150     }
151
152     reference& operator=(const_reference u) {
153       auto it = map->find(idx);
154       if (u == value_type{}) {
155         if (it != static_cast<T*>(map)->end()) { map->erase(it); }
156       } else {
157         if (it != static_cast<T*>(map)->end()) {
158           it->second = u;
159         } else {
160           map->emplace(idx, u);
161         }
162       }
163       return *this;
164     }
165
166     template <class U, class V = value_type,
167               class = std::enable_if_t<has_operator_radd<V, U>::value>>
168     reference& operator+=(const U& u) {
169       auto it = map->find(idx);
170       if (it != static_cast<T*>(map)->end()) {
171         it->second += u;
172       } else {
173         map->emplace(idx, u);
174       }
175       return *this;
176     }
177
178     template <class U, class V = value_type,
179               class = std::enable_if_t<has_operator_rsub<V, U>::value>>
180     reference& operator-=(const U& u) {
181       auto it = map->find(idx);
182       if (it != static_cast<T*>(map)->end()) {
183         it->second -= u;
184       } else {
185         map->emplace(idx, -u);
186       }
187       return *this;
188     }
189
190     template <class U, class V = value_type,
191               class = std::enable_if_t<has_operator_rmul<V, U>::value>>
192     reference& operator*=(const U& u) {
193       auto it = map->find(idx);
194       if (it != static_cast<T*>(map)->end()) it->second *= u;
195       return *this;
196     }
197
198     template <class U, class V = value_type,
199               class = std::enable_if_t<has_operator_rdiv<V, U>::value>>
200     reference& operator/=(const U& u) {
201       auto it = map->find(idx);
202       if (it != static_cast<T*>(map)->end()) {
203         it->second /= u;
204       } else if (!(value_type{} / u == value_type{})) {
205         map->emplace(idx, value_type{} / u);
206       }
207       return *this;
208     }
209
210     template <class V = value_type,
211               class = std::enable_if_t<has_operator_preincrement<V>::value>>
212     reference operator++() {
213       auto it = map->find(idx);
214       if (it != static_cast<T*>(map)->end()) {
215         ++it->second;
216       } else {
217         value_type tmp{};
218         ++tmp;
219         map->emplace(idx, tmp);
220       }
221       return *this;
222     }
223
224     template <class V = value_type,
225               class = std::enable_if_t<has_operator_preincrement<V>::value>>
226     value_type operator++(int) {
227       const value_type tmp = *this;
228       operator++();
229       return tmp;
230     }
231
232     template <class U, class = std::enable_if_t<has_operator_equal<value_type, U>::value>>
233     bool operator==(const U& rhs) const {
234       return operator const_reference() == rhs;
235     }
236
237     template <class U, class = std::enable_if_t<has_operator_equal<value_type, U>::value>>
238     bool operator!=(const U& rhs) const {
239       return !operator==(rhs);
240     }
241
242     template <typename CharT, typename Traits>
243     friend std::basic_ostream<CharT, Traits>& operator<<(
244         std::basic_ostream<CharT, Traits>& os, reference x) {
245       os << static_cast<const_reference>(x);
246       return os;
247     }
248
249     template <class... Ts>
250     decltype(auto) operator()(Ts&&... args) {
251       return map->operator[](idx)(std::forward<Ts>(args)...);
252     }
253
254     map_impl* map;
255     std::size_t idx;
256   };
257
258   template <class Value, class Reference, class MapPtr>
259   struct iterator_t
260       : iterator_adaptor<iterator_t<Value, Reference, MapPtr>, std::size_t, Reference> {
261     iterator_t() = default;
262     template <class V, class R, class M, class = requires_convertible<M, MapPtr>>
263     iterator_t(const iterator_t<V, R, M>& it) noexcept : iterator_t(it.map_, it.base()) {}
264     iterator_t(MapPtr m, std::size_t i) noexcept
265         : iterator_t::iterator_adaptor_(i), map_(m) {}
266     template <class V, class R, class M>
267     bool equal(const iterator_t<V, R, M>& rhs) const noexcept {
268       return map_ == rhs.map_ && iterator_t::base() == rhs.base();
269     }
270     Reference operator*() const { return (*map_)[iterator_t::base()]; }
271     MapPtr map_ = nullptr;
272   };
273
274   using iterator = iterator_t<value_type, reference, map_impl*>;
275   using const_iterator = iterator_t<const value_type, const_reference, const map_impl*>;
276
277   using allocator_type = typename T::allocator_type;
278
279   map_impl(const allocator_type& a = {}) : T(a) {}
280
281   map_impl(const map_impl&) = default;
282   map_impl& operator=(const map_impl&) = default;
283   map_impl(map_impl&&) = default;
284   map_impl& operator=(map_impl&&) = default;
285
286   map_impl(const T& t) : T(t), size_(t.size()) {}
287   map_impl(T&& t) : T(std::move(t)), size_(t.size()) {}
288
289   template <class U, class = requires_iterable<U>>
290   explicit map_impl(const U& u, const allocator_type& a = {}) : T(a), size_(u.size()) {
291     using std::begin;
292     using std::end;
293     std::copy(begin(u), end(u), this->begin());
294   }
295
296   template <class U, class = requires_iterable<U>>
297   map_impl& operator=(const U& u) {
298     if (u.size() < size_)
299       reset(u.size());
300     else
301       size_ = u.size();
302     using std::begin;
303     using std::end;
304     std::copy(begin(u), end(u), this->begin());
305     return *this;
306   }
307
308   void reset(std::size_t n) {
309     T::clear();
310     size_ = n;
311   }
312
313   reference operator[](std::size_t i) noexcept { return {this, i}; }
314   const_reference operator[](std::size_t i) const noexcept {
315     auto it = T::find(i);
316     static const value_type null = value_type{};
317     if (it == T::end()) return null;
318     return it->second;
319   }
320
321   iterator begin() noexcept { return {this, 0}; }
322   iterator end() noexcept { return {this, size_}; }
323
324   const_iterator begin() const noexcept { return {this, 0}; }
325   const_iterator end() const noexcept { return {this, size_}; }
326
327   std::size_t size() const noexcept { return size_; }
328
329   template <class Archive>
330   void serialize(Archive& ar, unsigned /* version */) {
331     ar& make_nvp("size", size_);
332     ar& make_nvp("map", static_cast<T&>(*this));
333   }
334
335   std::size_t size_ = 0;
336 };
337
338 template <class T>
339 struct ERROR_type_passed_to_storage_adaptor_not_recognized;
340
341 // clang-format off
342 template <class T>
343 using storage_adaptor_impl =
344   mp11::mp_cond<
345     is_vector_like<T>, vector_impl<T>,
346     is_array_like<T>, array_impl<T>,
347     is_map_like<T>, map_impl<T>,
348     std::true_type, ERROR_type_passed_to_storage_adaptor_not_recognized<T>
349   >;
350 // clang-format on
351 } // namespace detail
352
353 /// Turns any vector-like, array-like, and map-like container into a storage type.
354 template <class T>
355 class storage_adaptor : public detail::storage_adaptor_impl<T> {
356   using impl_type = detail::storage_adaptor_impl<T>;
357
358 public:
359   // standard copy, move, assign
360   storage_adaptor(storage_adaptor&&) = default;
361   storage_adaptor(const storage_adaptor&) = default;
362   storage_adaptor& operator=(storage_adaptor&&) = default;
363   storage_adaptor& operator=(const storage_adaptor&) = default;
364
365   // forwarding constructor
366   template <class... Ts>
367   storage_adaptor(Ts&&... ts) : impl_type(std::forward<Ts>(ts)...) {}
368
369   // forwarding assign
370   template <class U>
371   storage_adaptor& operator=(U&& u) {
372     impl_type::operator=(std::forward<U>(u));
373     return *this;
374   }
375
376   template <class U, class = detail::requires_iterable<U>>
377   bool operator==(const U& u) const {
378     using std::begin;
379     using std::end;
380     return std::equal(this->begin(), this->end(), begin(u), end(u), detail::safe_equal{});
381   }
382
383   template <class Archive>
384   void serialize(Archive& ar, unsigned /* version */) {
385     ar& make_nvp("impl", static_cast<impl_type&>(*this));
386   }
387
388 private:
389   friend struct unsafe_access;
390 };
391
392 } // namespace histogram
393 } // namespace boost
394
395 #endif