libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
unordered_tuple.hpp
Go to the documentation of this file.
1 #ifndef LIBJMMCG_CORE_UNORDERED_TUPLE_HPP
2 #define LIBJMMCG_CORE_UNORDERED_TUPLE_HPP
3 
4 /******************************************************************************
5 ** Copyright © 2017 by J.M.McGuiness, libjmmcg@hussar.me.uk
6 **
7 ** This library is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU Lesser General Public
9 ** License as published by the Free Software Foundation; either
10 ** version 2.1 of the License, or (at your option) any later version.
11 **
12 ** This library is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ** Lesser General Public License for more details.
16 **
17 ** You should have received a copy of the GNU Lesser General Public
18 ** License along with this library; if not, write to the Free Software
19 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21 
23 #include "max_min.hpp"
24 #include "non_allocatable.hpp"
25 
26 #include "frozen/unordered_map.h"
27 
28 #include <algorithm>
29 #include <cstdint>
30 
31 namespace jmmcg { namespace LIBJMMCG_VER_NAMESPACE {
32 
33 /// A unordered container for a disparate list of types for which the keys form a perfect hash, computable at compile-time.
34 /**
35  This class performs zero memory allocations. All of the data is maintained on the stack at the cost of some additional space. There is padding added between each instance of a MappedType to ensure that those instances avoid false sharing in any cache level.
36 */
37 template<
38  class KeyType, ///< The type of the keys used to identify the particular instance of a MappedType.
39  class MappedType, ///< A base-class common to all of the MappedTypes.
40  class Hasher, ///< The hash functor to be applied to the keys.
41  template<class> class ExtractHash, ///< A template-algorithm to extract the particular key to be hashed from the MappedType, that will be hashed using the Hasher. Must have a static, constexpr member called value that should contain the key extracted from the type.
42  class... MappedTypes ///< The disparate list of types to be contained, which will be used to generate a hash with ExtractHash and Hasher.
43 >
44 class unordered_tuple final : private non_newable {
45 public:
46  enum : std::size_t {
47  size=sizeof...(MappedTypes)
48  };
49  BOOST_MPL_ASSERT_RELATION(size, >, 0);
50 
51 private:
52  using mapped_buffer_element=std::uint8_t;
53  enum : std::size_t {
54  tmp_max_size=varadic_max(sizeof(MappedTypes)...),
55  mapped_elem_stride=((tmp_max_size+sizeof(std::uint64_t))/sizeof(std::uint64_t))*sizeof(std::uint64_t), ///< False sharing size.
56  };
57  /**
58  As we do not know the alignment of each of the objects to be constructed, be very conservative and 64-bit align all of them. This might waste memory, but that is life... At least they'll be better aligned to avoid false sharing.
59  */
60  BOOST_MPL_ASSERT_RELATION(mapped_elem_stride%sizeof(std::uint64_t), ==, 0);
61 
62 public:
63  enum : std::size_t {
64  max_size=mapped_elem_stride*size
65  };
66 
67 private:
68  /// Map the keys to an index into the mapped_cont_t, at compile-time.
69  using mapped_index_colln=frozen::unordered_map<
70  KeyType,
71  unsigned,
72  size,
73  Hasher
74  >;
75  template<class>
76  class idx_for_mapped_index_colln;
77  template<std::size_t... Indices>
78  class idx_for_mapped_index_colln<std::index_sequence<Indices...>> final {
79  public:
80  static constexpr typename mapped_index_colln::const_iterator
81  find(typename mapped_index_colln::key_type key) noexcept(true) {
82  typename mapped_index_colln::const_iterator const iter=mapped_indices.find(key);
83  assert(iter!=mapped_indices.end());
84  return iter;
85  }
86 
87  private:
88  /// The compile-time map of hashes to indices into the mapped_cont_t.
89  ALIGN_TO_L1_CACHE static inline constexpr const mapped_index_colln mapped_indices={
90  {ExtractHash<MappedTypes>::value, Indices*mapped_elem_stride}...
91  };
92  };
93  using mapped_index_colln_t=idx_for_mapped_index_colln<std::make_index_sequence<size>>;
94 
95  /// The type of the underlying buffer that will contain the instances of mapped_types.
96  using mapped_cont_t=std::array<mapped_buffer_element, mapped_elem_stride*size>;
97  enum : std::size_t {
98  objs_size=(0+...+sizeof(MappedTypes))
99  };
100  BOOST_MPL_ASSERT_RELATION(static_cast<std::size_t>(objs_size), <=, static_cast<std::size_t>(max_size));
101 
102  /**
103  Avoid false sharing...
104  */
105  ALIGN_TO_L1_CACHE mapped_cont_t mapped_cont{};
106 
107  /// Recursively construct, in turn & in-place, each instance of a mapped_type, immediately stopping if an exception is thrown.
108  template<class MappedType1, class... MappedTypes1>
109  struct ctors_t final {
110  template<class MappedCtorArg, class... MappedCtorArgs>
111  static void
112  result(typename mapped_cont_t::pointer ptr, MappedCtorArg &&arg, MappedCtorArgs &&...args) noexcept(
113  noexcept(MappedType1(std::forward<MappedCtorArg>(arg)))
114  ||
115  noexcept(ctors_t<MappedTypes1...>::result(ptr+mapped_elem_stride, std::forward<MappedCtorArgs>(args)...))
116  ) {
117  BOOST_MPL_ASSERT((std::is_base_of<mapped_type, MappedType1>));
118  new (ptr) MappedType1(std::forward<MappedCtorArg>(arg));
119  ctors_t<MappedTypes1...>::result(ptr+mapped_elem_stride, std::forward<MappedCtorArgs>(args)...);
120  }
121  };
122  template<class MappedType1>
123  struct ctors_t<MappedType1> final {
124  template<class MappedCtorArg>
125  static void result(typename mapped_cont_t::pointer ptr, MappedCtorArg &&arg) noexcept(noexcept(MappedType1(std::forward<MappedCtorArg>(arg)))) {
126  BOOST_MPL_ASSERT((std::is_base_of<mapped_type, MappedType1>));
127  new (ptr) MappedType1(std::forward<MappedCtorArg>(arg));
128  }
129  };
130  /// Recursively construct, in turn & in-place, each instance of a mapped_type, immediately stopping if an exception is thrown.
131  template<class MappedType1, class... MappedTypes1>
132  struct default_ctors_t final {
133  static void
134  result(typename mapped_cont_t::pointer ptr) noexcept(
135  noexcept(MappedType1())
136  || noexcept(default_ctors_t<MappedTypes1...>::result(ptr+mapped_elem_stride))
137  ) {
138  BOOST_MPL_ASSERT((std::is_base_of<mapped_type, MappedType1>));
139  new (ptr) MappedType1();
140  default_ctors_t<MappedTypes1...>::result(ptr+mapped_elem_stride);
141  }
142  };
143  template<class MappedType1>
144  struct default_ctors_t<MappedType1> final {
145  static void
146  result(typename mapped_cont_t::pointer ptr) noexcept(noexcept(MappedType1())) {
147  BOOST_MPL_ASSERT((std::is_base_of<mapped_type, MappedType1>));
148  new (ptr) MappedType1();
149  }
150  };
151 
152 public:
153  using key_type=typename mapped_index_colln::key_type;
154  using mapped_type=MappedType;
155 
156  // TODO consider noexcept(true) ctors, so no need for try-catch so constexpr ctors possible...
157  unordered_tuple() noexcept(noexcept(default_ctors_t<MappedTypes...>::result(mapped_cont.data())));
158  /**
159  If the construction of any instance of a mapped_type fails, then any already constructed instance of a mapped_type will be correctly deleted, and that failing exception re-thrown.
160  Each of the MappedTypes is either default constructed or constructed with exactly, successively, one of the {arg, args...} parameters passed to this ctor. Thus there must be the same number of MappedTypes as the number of ctor arguments passed in. i.e. sizeof...(MappedTypes)==sizeof...(args)+1.
161  */
162  template<
163  class MappedCtorArg,
164  class... MappedCtorArgs,
166  >
167  unordered_tuple(MappedCtorArg &&arg, MappedCtorArgs &&... args) noexcept(noexcept(ctors_t<MappedTypes...>::result(mapped_cont.data(), std::forward<MappedCtorArg>(arg), std::forward<MappedCtorArgs>(args)...)));
168  ~unordered_tuple() noexcept(true);
169 
170  /// Find the instance of mapped_type associated with the instance of key_type.
171  /**
172  Algorithmic complexity: O(1)
173  Note that great effort has been made to ensure that this is as fast as possible.
174 
175  \param key The instance of key_type associated with a mapped_type.
176  \return A reference to the associated instance of mapped_type.
177  */
178  constexpr mapped_type const &operator[](const key_type key) const noexcept(true);
179  /// Find the instance of mapped_type associated with the instance of key_type.
180  /**
181  Algorithmic complexity: O(1)
182  Note that great effort has been made to ensure that this is as fast as possible.
183 
184  \param key The instance of key_type associated with a mapped_type.
185  \return A reference to the associated instance of mapped_type.
186  */
187  constexpr mapped_type &operator[](const key_type key) noexcept(true);
188 
189 private:
190  /// Recursively delete, in turn, all instanced of mapped_types that have been successfully constructed.
191  template<class MappedType1, class... MappedTypes1>
192  struct dtors final {
193  static void result(typename mapped_cont_t::pointer ptr) noexcept(true) {
194  MappedType1 * obj_ctord=reinterpret_cast<MappedType1 *>(ptr);
195  if (obj_ctord) {
196  obj_ctord->~MappedType1();
197  obj_ctord=nullptr;
198  }
199  dtors<MappedTypes1...>::result(ptr+mapped_elem_stride);
200  }
201  };
202  template<class MappedType1>
203  struct dtors<MappedType1> final {
204  static void result(typename mapped_cont_t::pointer ptr) noexcept(true) {
205  MappedType1 * obj_ctord=reinterpret_cast<MappedType1 *>(ptr);
206  if (obj_ctord) {
207  obj_ctord->~MappedType1();
208  obj_ctord=nullptr;
209  }
210  }
211  };
212 };
213 
214 } }
215 
217 
218 #endif