Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Any.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file Any.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2021 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
9 #pragma once
10 
11 #include <any>
12 #include <array>
13 #include <cassert>
14 #include <cstddef>
15 #include <memory>
16 #include <utility>
17 
18 // #define _ACTS_ANY_ENABLE_VERBOSE
19 // #define _ACTS_ANY_ENABLE_DEBUG
20 // #define _ACTS_ANY_ENABLE_TRACK_ALLOCATIONS
21 
22 #if defined(_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)
23 #include <iostream>
24 #include <mutex>
25 #include <set>
26 #include <typeindex>
27 #include <typeinfo>
28 #endif
29 
30 #if defined(_ACTS_ANY_ENABLE_VERBOSE) || defined(_ACTS_ANY_ENABLE_DEBUG)
31 #include <iomanip>
32 #include <iostream>
33 #endif
34 
35 #if defined(_ACTS_ANY_ENABLE_DEBUG)
36 #define _ACTS_ANY_DEBUG(x) std::cout << x << std::endl;
37 #else
38 #define _ACTS_ANY_DEBUG(x)
39 #endif
40 
41 #if defined(_ACTS_ANY_ENABLE_VERBOSE)
42 #define _ACTS_ANY_VERBOSE(x) std::cout << x << std::endl;
43 #define _ACTS_ANY_VERBOSE_BUFFER(s, b) \
44  do { \
45  std::cout << "" << s << ": 0x"; \
46  for (char c : b) { \
47  std::cout << std::hex << (int)c; \
48  } \
49  std::cout << std::endl; \
50  } while (0)
51 #else
52 #define _ACTS_ANY_VERBOSE(x)
53 #define _ACTS_ANY_VERBOSE_BUFFER(s, b)
54 #endif
55 
56 namespace Acts {
57 
58 #if defined(_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)
59 static std::mutex _s_any_mutex;
60 static std::set<std::pair<std::type_index, void*>> _s_any_allocations;
61 
62 #define _ACTS_ANY_TRACK_ALLOCATION(T, heap) \
63  do { \
64  std::lock_guard guard{_s_any_mutex}; \
65  _s_any_allocations.emplace(std::type_index(typeid(T)), heap); \
66  _ACTS_ANY_DEBUG("Allocate type: " << typeid(T).name() << " at " << heap) \
67  } while (0)
68 
69 #define _ACTS_ANY_TRACK_DEALLOCATION(T, heap) \
70  do { \
71  std::lock_guard guard{_s_any_mutex}; \
72  auto it = \
73  _s_any_allocations.find(std::pair{std::type_index(typeid(T)), heap}); \
74  if (it == _s_any_allocations.end()) { \
75  throw std::runtime_error{ \
76  "Trying to deallocate heap address that we didn't allocate"}; \
77  } \
78  _s_any_allocations.erase(it); \
79  } while (0)
80 
81 struct _AnyAllocationReporter {
82  static void checkAllocations() {
83  std::lock_guard guard{_s_any_mutex};
84 
85  if (!_s_any_allocations.empty()) {
86  std::cout << "Not all allocations have been released" << std::endl;
87  for (const auto& [idx, addr] : _s_any_allocations) {
88  std::cout << "- " << idx.name() << ": " << addr << std::endl;
89  }
90  throw std::runtime_error{"AnyCheckAllocations failed"};
91  }
92  }
93 
94  ~_AnyAllocationReporter() noexcept { checkAllocations(); }
95 };
96 static _AnyAllocationReporter s_reporter;
97 #else
98 #define _ACTS_ANY_TRACK_ALLOCATION(T, heap) \
99  do { \
100  } while (0)
101 #define _ACTS_ANY_TRACK_DEALLOCATION(T, heap) \
102  do { \
103  } while (0)
104 #endif
105 
106 class AnyBaseAll {};
107 
109 template <size_t SIZE>
110 class AnyBase : public AnyBaseAll {
111  static_assert(sizeof(void*) <= SIZE, "Size is too small for a pointer");
112 
113  public:
114  template <typename T, typename... Args>
115  explicit AnyBase(std::in_place_type_t<T> /*unused*/, Args&&... args) {
116  using U = std::decay_t<T>;
117  static_assert(
118  std::is_move_assignable_v<U> && std::is_move_constructible_v<U>,
119  "Type needs to be move assignable and move constructible");
120  static_assert(
121  std::is_copy_assignable_v<U> && std::is_copy_constructible_v<U>,
122  "Type needs to be copy assignable and copy constructible");
123 
124  m_handler = makeHandler<U>();
125  if constexpr (not heapAllocated<U>()) {
126  // construct into local buffer
127  /*U* ptr =*/new (m_data.data()) U(std::forward<Args>(args)...);
129  "Construct local (this=" << this << ") at: " << (void*)m_data.data());
130  } else {
131  // too large, heap allocate
132  U* heap = new U(std::forward<Args>(args)...);
134  setDataPtr(heap);
135  }
136  }
137 
138 #if defined(_ACTS_ANY_ENABLE_VERBOSE)
139  AnyBase() {
140  _ACTS_ANY_VERBOSE("Default construct this=" << this);
141  };
142 #else
143  AnyBase() = default;
144 #endif
145 
146  template <typename T, typename = std::enable_if_t<
147  !std::is_same_v<std::decay_t<T>, AnyBase<SIZE>>>>
148  explicit AnyBase(T&& value)
149  : AnyBase{std::in_place_type<T>, std::forward<T>(value)} {}
150 
151  template <typename T>
152  T& as() {
153  static_assert(std::is_same_v<T, std::decay_t<T>>,
154  "Please pass the raw type, no const or ref");
155  if (makeHandler<T>() != m_handler) {
156  throw std::bad_any_cast{};
157  }
158 
159  _ACTS_ANY_VERBOSE("Get as "
160  << (m_handler->heapAllocated ? "heap" : "local"));
161 
162  return *reinterpret_cast<T*>(dataPtr());
163  }
164 
165  template <typename T>
166  const T& as() const {
167  static_assert(std::is_same_v<T, std::decay_t<T>>,
168  "Please pass the raw type, no const or ref");
169  if (makeHandler<T>() != m_handler) {
170  throw std::bad_any_cast{};
171  }
172 
173  _ACTS_ANY_VERBOSE("Get as " << (m_handler->heap ? "heap" : "local"));
174 
175  return *reinterpret_cast<const T*>(dataPtr());
176  }
177 
178  ~AnyBase() {
179  destroy();
180  }
181 
182  AnyBase(const AnyBase& other) {
183  if (m_handler == nullptr && other.m_handler == nullptr) {
184  // both are empty, noop
185  return;
186  }
187 
189  "Copy construct (this=" << this << ") at: " << (void*)m_data.data());
190 
191  m_handler = other.m_handler;
192  copyConstruct(other);
193  }
194 
195  AnyBase& operator=(const AnyBase& other) {
196  _ACTS_ANY_VERBOSE("Copy assign (this=" << this
197  << ") at: " << (void*)m_data.data());
198 
199  if (m_handler == nullptr && other.m_handler == nullptr) {
200  // both are empty, noop
201  return *this;
202  }
203 
204  if (m_handler == nullptr) { // this object is empty
205  m_handler = other.m_handler;
206  copyConstruct(other);
207  } else {
208  // @TODO: Support assigning between different types
209  if (m_handler != other.m_handler) {
210  throw std::bad_any_cast{};
211  }
212  copy(other);
213  }
214  return *this;
215  }
216 
217  AnyBase(AnyBase&& other) {
219  "Move construct (this=" << this << ") at: " << (void*)m_data.data());
220  if (m_handler == nullptr && other.m_handler == nullptr) {
221  // both are empty, noop
222  return;
223  }
224 
225  m_handler = other.m_handler;
226  moveConstruct(std::move(other));
227  }
228 
229  AnyBase& operator=(AnyBase&& other) {
230  _ACTS_ANY_VERBOSE("Move assign (this=" << this
231  << ") at: " << (void*)m_data.data());
232  if (m_handler == nullptr && other.m_handler == nullptr) {
233  // both are empty, noop
234  return *this;
235  }
236 
237  if (m_handler == nullptr) { // this object is empty
238  m_handler = other.m_handler;
239  moveConstruct(std::move(other));
240  } else {
241  // @TODO: Support assigning between different types
242  if (m_handler != other.m_handler) {
243  throw std::bad_any_cast{};
244  }
245  move(std::move(other));
246  }
247  return *this;
248  }
249 
250  operator bool() const {
251  return m_handler != nullptr;
252  }
253 
254  private:
255  void* dataPtr() {
256  if (m_handler->heapAllocated) {
257  return *reinterpret_cast<void**>(m_data.data());
258  } else {
259  return reinterpret_cast<void*>(m_data.data());
260  }
261  }
262 
263  void setDataPtr(void* ptr) {
264  *reinterpret_cast<void**>(m_data.data()) = ptr;
265  }
266 
267  const void* dataPtr() const {
268  if (m_handler->heapAllocated) {
269  return *reinterpret_cast<void* const*>(m_data.data());
270  } else {
271  return reinterpret_cast<const void*>(m_data.data());
272  }
273  }
274 
275  struct Handler {
276  void (*destroy)(void* ptr) = nullptr;
277  void (*moveConstruct)(void* from, void* to) = nullptr;
278  void (*move)(void* from, void* to) = nullptr;
279  void* (*copyConstruct)(const void* from, void* to) = nullptr;
280  void (*copy)(const void* from, void* to) = nullptr;
281  bool heapAllocated{false};
282  };
283 
284  template <typename T>
285  static const Handler* makeHandler() {
286  static_assert(!std::is_same_v<T, AnyBase<SIZE>>, "Cannot wrap any in any");
287  static const Handler static_handler = []() {
288  Handler h;
289  h.heapAllocated = heapAllocated<T>();
290  if constexpr (!std::is_trivially_destructible_v<T> ||
291  heapAllocated<T>()) {
292  h.destroy = &destroyImpl<T>;
293  }
294  if constexpr (!std::is_trivially_move_constructible_v<T> ||
295  heapAllocated<T>()) {
296  h.moveConstruct = &moveConstructImpl<T>;
297  }
298  if constexpr (!std::is_trivially_move_assignable_v<T> ||
299  heapAllocated<T>()) {
300  h.move = &moveImpl<T>;
301  }
302  if constexpr (!std::is_trivially_copy_constructible_v<T> ||
303  heapAllocated<T>()) {
304  h.copyConstruct = &copyConstructImpl<T>;
305  }
306  if constexpr (!std::is_trivially_copy_assignable_v<T> ||
307  heapAllocated<T>()) {
308  h.copy = &copyImpl<T>;
309  }
310 
311  _ACTS_ANY_DEBUG("Type: " << typeid(T).name());
312  _ACTS_ANY_DEBUG(" -> destroy: " << h.destroy);
313  _ACTS_ANY_DEBUG(" -> moveConstruct: " << h.moveConstruct);
314  _ACTS_ANY_DEBUG(" -> move: " << h.move);
315  _ACTS_ANY_DEBUG(" -> copyConstruct: " << h.copyConstruct);
316  _ACTS_ANY_DEBUG(" -> copy: " << h.copy);
318  " -> heapAllocated: " << (h.heapAllocated ? "yes" : "no"));
319 
320  return h;
321  }();
322  return &static_handler;
323  }
324 
325  template <typename T>
326  static constexpr bool heapAllocated() {
327  return sizeof(T) > SIZE;
328  }
329 
330  void destroy() {
331  _ACTS_ANY_VERBOSE("Destructor this=" << this << " handler: " << m_handler);
332  if (m_handler != nullptr && m_handler->destroy != nullptr) {
333  m_handler->destroy(dataPtr());
334  m_handler = nullptr;
335  }
336  }
337 
338  void moveConstruct(AnyBase&& fromAny) {
339  if (m_handler == nullptr) {
340  return;
341  }
342 
343  void* to = dataPtr();
344  void* from = fromAny.dataPtr();
345  if (m_handler->heapAllocated) {
346  // stored on heap: just copy the pointer
347  setDataPtr(fromAny.dataPtr());
348  // do not delete in moved-from any
349  fromAny.m_handler = nullptr;
350  return;
351  }
352 
353  if (m_handler->moveConstruct == nullptr) {
354  // trivially move constructible
355  m_data = std::move(fromAny.m_data);
356  } else {
357  m_handler->moveConstruct(from, to);
358  }
359  }
360 
361  void move(AnyBase&& fromAny) {
362  if (m_handler == nullptr) {
363  return;
364  }
365 
366  void* to = dataPtr();
367  void* from = fromAny.dataPtr();
368  if (m_handler->heapAllocated) {
369  // stored on heap: just copy the pointer
370  // need to delete existing pointer
371  m_handler->destroy(dataPtr());
372  setDataPtr(fromAny.dataPtr());
373  // do not delete in moved-from any
374  fromAny.m_handler = nullptr;
375  return;
376  }
377 
378  if (m_handler->move == nullptr) {
379  // trivially movable
380  m_data = std::move(fromAny.m_data);
381  } else {
382  m_handler->move(from, to);
383  }
384  }
385 
386  void copyConstruct(const AnyBase& fromAny) {
387  if (m_handler == nullptr) {
388  return;
389  }
390 
391  void* to = dataPtr();
392  const void* from = fromAny.dataPtr();
393 
394  if (m_handler->copyConstruct == nullptr) {
395  // trivially copy constructible
396  m_data = fromAny.m_data;
397  } else {
398  void* copyAt = m_handler->copyConstruct(from, to);
399  if (to == nullptr) {
400  assert(copyAt != nullptr);
401  // copy allocated, store pointer
402  setDataPtr(copyAt);
403  }
404  }
405  }
406 
407  void copy(const AnyBase& fromAny) {
408  if (m_handler == nullptr) {
409  return;
410  }
411 
412  void* to = dataPtr();
413  const void* from = fromAny.dataPtr();
414 
415  if (m_handler->copy == nullptr) {
416  // trivially copyable
417  m_data = fromAny.m_data;
418  } else {
419  m_handler->copy(from, to);
420  }
421  }
422 
423  template <typename T>
424  static void destroyImpl(void* ptr) {
425  assert(ptr != nullptr && "Address to destroy is nullptr");
426  T* obj = static_cast<T*>(ptr);
427  if constexpr (!heapAllocated<T>()) {
428  // stored in place: just call the destructor
429  _ACTS_ANY_VERBOSE("Destroy local at: " << ptr);
430  obj->~T();
431  } else {
432  // stored on heap: delete
433  _ACTS_ANY_DEBUG("Delete type: " << typeid(T).name()
434  << " heap at: " << obj);
436  delete obj;
437  }
438  }
439 
440  template <typename T>
441  static void moveConstructImpl(void* from, void* to) {
442  _ACTS_ANY_VERBOSE("move const: " << from << " -> " << to);
443  assert(from != nullptr && "Source is null");
444  assert(to != nullptr && "Target is null");
445  T* _from = static_cast<T*>(from);
446  /*T* ptr =*/new (to) T(std::move(*_from));
447  }
448 
449  template <typename T>
450  static void moveImpl(void* from, void* to) {
451  _ACTS_ANY_VERBOSE("move: " << from << " -> " << to);
452  assert(from != nullptr && "Source is null");
453  assert(to != nullptr && "Target is null");
454 
455  T* _from = static_cast<T*>(from);
456  T* _to = static_cast<T*>(to);
457 
458  (*_to) = std::move(*_from);
459  }
460 
461  template <typename T>
462  static void* copyConstructImpl(const void* from, void* to) {
463  _ACTS_ANY_VERBOSE("copy const: " << from << " -> " << to);
464  assert(from != nullptr && "Source is null");
465  const T* _from = static_cast<const T*>(from);
466  if (to == nullptr) {
467  assert(heapAllocated<T>() && "Received nullptr in local buffer case");
468  to = new T(*_from);
470 
471  } else {
472  assert(!heapAllocated<T>() && "Received non-nullptr in heap case");
473  /*T* ptr =*/new (to) T(*_from);
474  }
475  return to;
476  }
477 
478  template <typename T>
479  static void copyImpl(const void* from, void* to) {
480  _ACTS_ANY_VERBOSE("copy: " << from << " -> " << to);
481  assert(from != nullptr && "Source is null");
482  assert(to != nullptr && "Target is null");
483 
484  const T* _from = static_cast<const T*>(from);
485  T* _to = static_cast<T*>(to);
486 
487  (*_to) = *_from;
488  }
489 
490  static constexpr size_t kMaxAlignment = std::max(alignof(std::max_align_t),
491 #if defined(__AVX512F__)
492  size_t(64)
493 #elif defined(__AVX__)
494  size_t(32)
495 #elif defined(__SSE__)
496  size_t(16)
497 #else
498  size_t(0) // Neutral element
499  // for maximum
500 #endif
501  );
502 
503  alignas(kMaxAlignment) std::array<std::byte, SIZE> m_data{};
504  const Handler* m_handler{nullptr};
505 };
506 
508 
509 #undef _ACTS_ANY_VERBOSE
510 #undef _ACTS_ANY_VERBOSE_BUFFER
511 #undef _ACTS_ANY_ENABLE_VERBOSE
512 
513 } // namespace Acts