File indexing completed on 2025-01-18 09:11:08
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include <any>
0012 #include <array>
0013 #include <cassert>
0014 #include <cstddef>
0015 #include <utility>
0016
0017
0018
0019
0020
0021 #if defined(_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)
0022 #include <iostream>
0023 #include <mutex>
0024 #include <set>
0025 #include <typeindex>
0026 #include <typeinfo>
0027 #endif
0028
0029 #if defined(_ACTS_ANY_ENABLE_VERBOSE) || defined(_ACTS_ANY_ENABLE_DEBUG)
0030 #include <iomanip>
0031 #include <iostream>
0032 #endif
0033
0034 #if defined(_ACTS_ANY_ENABLE_DEBUG)
0035 #define _ACTS_ANY_DEBUG(x) std::cout << x << std::endl;
0036 #else
0037 #define _ACTS_ANY_DEBUG(x)
0038 #endif
0039
0040 #if defined(_ACTS_ANY_ENABLE_VERBOSE)
0041 #define _ACTS_ANY_VERBOSE(x) std::cout << x << std::endl;
0042 #define _ACTS_ANY_VERBOSE_BUFFER(s, b) \
0043 do { \
0044 std::cout << "" << s << ": 0x"; \
0045 for (char c : b) { \
0046 std::cout << std::hex << static_cast<int>(c); \
0047 } \
0048 std::cout << std::endl; \
0049 } while (0)
0050 #else
0051 #define _ACTS_ANY_VERBOSE(x)
0052 #define _ACTS_ANY_VERBOSE_BUFFER(s, b)
0053 #endif
0054
0055 namespace Acts {
0056
0057 #if defined(_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)
0058 static std::mutex _s_any_mutex;
0059 static std::set<std::pair<std::type_index, void*>> _s_any_allocations;
0060
0061 #define _ACTS_ANY_TRACK_ALLOCATION(T, heap) \
0062 do { \
0063 std::lock_guard guard{_s_any_mutex}; \
0064 _s_any_allocations.emplace(std::type_index(typeid(T)), heap); \
0065 _ACTS_ANY_DEBUG("Allocate type: " << typeid(T).name() << " at " << heap) \
0066 } while (0)
0067
0068 #define _ACTS_ANY_TRACK_DEALLOCATION(T, heap) \
0069 do { \
0070 std::lock_guard guard{_s_any_mutex}; \
0071 auto it = \
0072 _s_any_allocations.find(std::pair{std::type_index(typeid(T)), heap}); \
0073 if (it == _s_any_allocations.end()) { \
0074 throw std::runtime_error{ \
0075 "Trying to deallocate heap address that we didn't allocate"}; \
0076 } \
0077 _s_any_allocations.erase(it); \
0078 } while (0)
0079
0080 struct _AnyAllocationReporter {
0081 static void checkAllocations() {
0082 std::lock_guard guard{_s_any_mutex};
0083
0084 if (!_s_any_allocations.empty()) {
0085 std::cout << "Not all allocations have been released" << std::endl;
0086 for (const auto& [idx, addr] : _s_any_allocations) {
0087 std::cout << "- " << idx.name() << ": " << addr << std::endl;
0088 }
0089 throw std::runtime_error{"AnyCheckAllocations failed"};
0090 }
0091 }
0092
0093 ~_AnyAllocationReporter() noexcept { checkAllocations(); }
0094 };
0095 static _AnyAllocationReporter s_reporter;
0096 #else
0097 #define _ACTS_ANY_TRACK_ALLOCATION(T, heap) \
0098 do { \
0099 } while (0)
0100 #define _ACTS_ANY_TRACK_DEALLOCATION(T, heap) \
0101 do { \
0102 } while (0)
0103 #endif
0104
0105 class AnyBaseAll {};
0106
0107
0108 template <std::size_t SIZE>
0109 class AnyBase : public AnyBaseAll {
0110 static_assert(sizeof(void*) <= SIZE, "Size is too small for a pointer");
0111
0112 public:
0113 template <typename T, typename... Args>
0114 explicit AnyBase(std::in_place_type_t<T> , Args&&... args) {
0115 using U = std::decay_t<T>;
0116 static_assert(
0117 std::is_move_assignable_v<U> && std::is_move_constructible_v<U>,
0118 "Type needs to be move assignable and move constructible");
0119 static_assert(
0120 std::is_copy_assignable_v<U> && std::is_copy_constructible_v<U>,
0121 "Type needs to be copy assignable and copy constructible");
0122
0123 m_handler = makeHandler<U>();
0124 if constexpr (!heapAllocated<U>()) {
0125
0126 new (m_data.data()) U(std::forward<Args>(args)...);
0127 _ACTS_ANY_VERBOSE("Construct local (this="
0128 << this
0129 << ") at: " << static_cast<void*>(m_data.data()));
0130 } else {
0131
0132 U* heap = new U(std::forward<Args>(args)...);
0133 _ACTS_ANY_DEBUG("Allocate type: " << typeid(U).name() << " at " << heap);
0134 _ACTS_ANY_TRACK_ALLOCATION(T, heap);
0135 setDataPtr(heap);
0136 }
0137 }
0138
0139 #if defined(_ACTS_ANY_ENABLE_VERBOSE)
0140 AnyBase() { _ACTS_ANY_VERBOSE("Default construct this=" << this); };
0141 #else
0142 AnyBase() = default;
0143 #endif
0144
0145 template <typename T>
0146 explicit AnyBase(T&& value)
0147 requires(!std::same_as<std::decay_t<T>, AnyBase<SIZE>>)
0148 : AnyBase{std::in_place_type<T>, std::forward<T>(value)} {}
0149
0150 template <typename T>
0151 T& as() {
0152 static_assert(std::is_same_v<T, std::decay_t<T>>,
0153 "Please pass the raw type, no const or ref");
0154 if (makeHandler<T>() != m_handler) {
0155 throw std::bad_any_cast{};
0156 }
0157
0158 _ACTS_ANY_VERBOSE("Get as "
0159 << (m_handler->heapAllocated ? "heap" : "local"));
0160
0161 return *reinterpret_cast<T*>(dataPtr());
0162 }
0163
0164 template <typename T>
0165 const T& as() const {
0166 static_assert(std::is_same_v<T, std::decay_t<T>>,
0167 "Please pass the raw type, no const or ref");
0168 if (makeHandler<T>() != m_handler) {
0169 throw std::bad_any_cast{};
0170 }
0171
0172 _ACTS_ANY_VERBOSE("Get as " << (m_handler->heap ? "heap" : "local"));
0173
0174 return *reinterpret_cast<const T*>(dataPtr());
0175 }
0176
0177 ~AnyBase() { destroy(); }
0178
0179 AnyBase(const AnyBase& other) {
0180 if (m_handler == nullptr && other.m_handler == nullptr) {
0181
0182 return;
0183 }
0184
0185 _ACTS_ANY_VERBOSE("Copy construct (this="
0186 << this << ") at: " << static_cast<void*>(m_data.data()));
0187
0188 m_handler = other.m_handler;
0189 copyConstruct(other);
0190 }
0191
0192 AnyBase& operator=(const AnyBase& other) {
0193 _ACTS_ANY_VERBOSE("Copy assign (this="
0194 << this << ") at: " << static_cast<void*>(m_data.data()));
0195
0196 if (m_handler == nullptr && other.m_handler == nullptr) {
0197
0198 return *this;
0199 }
0200
0201 if (m_handler == other.m_handler) {
0202
0203 copy(std::move(other));
0204 } else {
0205 if (m_handler != nullptr) {
0206
0207 destroy();
0208 }
0209 assert(m_handler == nullptr);
0210 m_handler = other.m_handler;
0211 copyConstruct(std::move(other));
0212 }
0213 return *this;
0214 }
0215
0216 AnyBase(AnyBase&& other) noexcept {
0217 _ACTS_ANY_VERBOSE("Move construct (this="
0218 << this << ") at: " << static_cast<void*>(m_data.data()));
0219 if (m_handler == nullptr && other.m_handler == nullptr) {
0220
0221 return;
0222 }
0223
0224 m_handler = other.m_handler;
0225 moveConstruct(std::move(other));
0226 }
0227
0228 AnyBase& operator=(AnyBase&& other) {
0229 _ACTS_ANY_VERBOSE("Move assign (this="
0230 << this << ") at: " << static_cast<void*>(m_data.data()));
0231 if (m_handler == nullptr && other.m_handler == nullptr) {
0232
0233 return *this;
0234 }
0235
0236 if (m_handler == other.m_handler) {
0237
0238 move(std::move(other));
0239 } else {
0240 if (m_handler != nullptr) {
0241
0242 destroy();
0243 }
0244 assert(m_handler == nullptr);
0245 m_handler = other.m_handler;
0246 moveConstruct(std::move(other));
0247 }
0248
0249 return *this;
0250 }
0251
0252 operator bool() const { return m_handler != nullptr; }
0253
0254 private:
0255 void* dataPtr() {
0256 if (m_handler->heapAllocated) {
0257 return *reinterpret_cast<void**>(m_data.data());
0258 } else {
0259 return reinterpret_cast<void*>(m_data.data());
0260 }
0261 }
0262
0263 void setDataPtr(void* ptr) { *reinterpret_cast<void**>(m_data.data()) = ptr; }
0264
0265 const void* dataPtr() const {
0266 if (m_handler->heapAllocated) {
0267 return *reinterpret_cast<void* const*>(m_data.data());
0268 } else {
0269 return reinterpret_cast<const void*>(m_data.data());
0270 }
0271 }
0272
0273 struct Handler {
0274 void (*destroy)(void* ptr) = nullptr;
0275 void (*moveConstruct)(void* from, void* to) = nullptr;
0276 void (*move)(void* from, void* to) = nullptr;
0277 void* (*copyConstruct)(const void* from, void* to) = nullptr;
0278 void (*copy)(const void* from, void* to) = nullptr;
0279 bool heapAllocated{false};
0280 };
0281
0282 template <typename T>
0283 static const Handler* makeHandler() {
0284 static_assert(!std::is_same_v<T, AnyBase<SIZE>>, "Cannot wrap any in any");
0285 static const Handler static_handler = []() {
0286 Handler h;
0287 h.heapAllocated = heapAllocated<T>();
0288 if constexpr (!std::is_trivially_destructible_v<T> ||
0289 heapAllocated<T>()) {
0290 h.destroy = &destroyImpl<T>;
0291 }
0292 if constexpr (!std::is_trivially_move_constructible_v<T> ||
0293 heapAllocated<T>()) {
0294 h.moveConstruct = &moveConstructImpl<T>;
0295 }
0296 if constexpr (!std::is_trivially_move_assignable_v<T> ||
0297 heapAllocated<T>()) {
0298 h.move = &moveImpl<T>;
0299 }
0300 if constexpr (!std::is_trivially_copy_constructible_v<T> ||
0301 heapAllocated<T>()) {
0302 h.copyConstruct = ©ConstructImpl<T>;
0303 }
0304 if constexpr (!std::is_trivially_copy_assignable_v<T> ||
0305 heapAllocated<T>()) {
0306 h.copy = ©Impl<T>;
0307 }
0308
0309 _ACTS_ANY_DEBUG("Type: " << typeid(T).name());
0310 _ACTS_ANY_DEBUG(" -> destroy: " << h.destroy);
0311 _ACTS_ANY_DEBUG(" -> moveConstruct: " << h.moveConstruct);
0312 _ACTS_ANY_DEBUG(" -> move: " << h.move);
0313 _ACTS_ANY_DEBUG(" -> copyConstruct: " << h.copyConstruct);
0314 _ACTS_ANY_DEBUG(" -> copy: " << h.copy);
0315 _ACTS_ANY_DEBUG(
0316 " -> heapAllocated: " << (h.heapAllocated ? "yes" : "no"));
0317
0318 return h;
0319 }();
0320 return &static_handler;
0321 }
0322
0323 template <typename T>
0324 static constexpr bool heapAllocated() {
0325 return sizeof(T) > SIZE;
0326 }
0327
0328 void destroy() {
0329 _ACTS_ANY_VERBOSE("Destructor this=" << this << " handler: " << m_handler);
0330 if (m_handler != nullptr && m_handler->destroy != nullptr) {
0331 _ACTS_ANY_VERBOSE("Non-trivial destruction");
0332 m_handler->destroy(dataPtr());
0333 }
0334 m_handler = nullptr;
0335 }
0336
0337 void moveConstruct(AnyBase&& fromAny) {
0338 if (m_handler == nullptr) {
0339 return;
0340 }
0341
0342 void* to = dataPtr();
0343 void* from = fromAny.dataPtr();
0344 if (m_handler->heapAllocated) {
0345
0346 setDataPtr(fromAny.dataPtr());
0347
0348 fromAny.m_handler = nullptr;
0349 return;
0350 }
0351
0352 if (m_handler->moveConstruct == nullptr) {
0353 _ACTS_ANY_VERBOSE("Trivially move construct");
0354
0355 m_data = std::move(fromAny.m_data);
0356 } else {
0357 m_handler->moveConstruct(from, to);
0358 }
0359 }
0360
0361 void move(AnyBase&& fromAny) {
0362 if (m_handler == nullptr) {
0363 return;
0364 }
0365
0366 void* to = dataPtr();
0367 void* from = fromAny.dataPtr();
0368 if (m_handler->heapAllocated) {
0369
0370
0371 m_handler->destroy(dataPtr());
0372 setDataPtr(fromAny.dataPtr());
0373
0374 fromAny.m_handler = nullptr;
0375 return;
0376 }
0377
0378 if (m_handler->move == nullptr) {
0379 _ACTS_ANY_VERBOSE("Trivially move");
0380
0381 m_data = std::move(fromAny.m_data);
0382 } else {
0383 m_handler->move(from, to);
0384 }
0385 }
0386
0387 void copyConstruct(const AnyBase& fromAny) {
0388 if (m_handler == nullptr) {
0389 return;
0390 }
0391
0392 void* to = dataPtr();
0393 const void* from = fromAny.dataPtr();
0394
0395 if (m_handler->copyConstruct == nullptr) {
0396 _ACTS_ANY_VERBOSE("Trivially copy construct");
0397
0398 m_data = fromAny.m_data;
0399 } else {
0400 void* copyAt = m_handler->copyConstruct(from, to);
0401 if (to == nullptr) {
0402 assert(copyAt != nullptr);
0403
0404 setDataPtr(copyAt);
0405 }
0406 }
0407 }
0408
0409 void copy(const AnyBase& fromAny) {
0410 if (m_handler == nullptr) {
0411 return;
0412 }
0413
0414 void* to = dataPtr();
0415 const void* from = fromAny.dataPtr();
0416
0417 if (m_handler->copy == nullptr) {
0418 _ACTS_ANY_VERBOSE("Trivially copy");
0419
0420 m_data = fromAny.m_data;
0421 } else {
0422 m_handler->copy(from, to);
0423 }
0424 }
0425
0426 template <typename T>
0427 static void destroyImpl(void* ptr) {
0428 assert(ptr != nullptr && "Address to destroy is nullptr");
0429 T* obj = static_cast<T*>(ptr);
0430 if constexpr (!heapAllocated<T>()) {
0431
0432 _ACTS_ANY_VERBOSE("Destroy local at: " << ptr);
0433 obj->~T();
0434 } else {
0435
0436 _ACTS_ANY_DEBUG("Delete type: " << typeid(T).name()
0437 << " heap at: " << obj);
0438 _ACTS_ANY_TRACK_DEALLOCATION(T, obj);
0439 delete obj;
0440 }
0441 }
0442
0443 template <typename T>
0444 static void moveConstructImpl(void* from, void* to) {
0445 _ACTS_ANY_VERBOSE("move const: " << from << " -> " << to);
0446 assert(from != nullptr && "Source is null");
0447 assert(to != nullptr && "Target is null");
0448 T* _from = static_cast<T*>(from);
0449 new (to) T(std::move(*_from));
0450 }
0451
0452 template <typename T>
0453 static void moveImpl(void* from, void* to) {
0454 _ACTS_ANY_VERBOSE("move: " << from << " -> " << to);
0455 assert(from != nullptr && "Source is null");
0456 assert(to != nullptr && "Target is null");
0457
0458 T* _from = static_cast<T*>(from);
0459 T* _to = static_cast<T*>(to);
0460
0461 (*_to) = std::move(*_from);
0462 }
0463
0464 template <typename T>
0465 static void* copyConstructImpl(const void* from, void* to) {
0466 _ACTS_ANY_VERBOSE("copy const: " << from << " -> " << to);
0467 assert(from != nullptr && "Source is null");
0468 const T* _from = static_cast<const T*>(from);
0469 if (to == nullptr) {
0470 assert(heapAllocated<T>() && "Received nullptr in local buffer case");
0471 to = new T(*_from);
0472 _ACTS_ANY_TRACK_ALLOCATION(T, to);
0473
0474 } else {
0475 assert(!heapAllocated<T>() && "Received non-nullptr in heap case");
0476 new (to) T(*_from);
0477 }
0478 return to;
0479 }
0480
0481 template <typename T>
0482 static void copyImpl(const void* from, void* to) {
0483 _ACTS_ANY_VERBOSE("copy: " << from << " -> " << to);
0484 assert(from != nullptr && "Source is null");
0485 assert(to != nullptr && "Target is null");
0486
0487 const T* _from = static_cast<const T*>(from);
0488 T* _to = static_cast<T*>(to);
0489
0490 (*_to) = *_from;
0491 }
0492
0493 static constexpr std::size_t kMaxAlignment =
0494 std::max(alignof(std::max_align_t),
0495 #if defined(__AVX512F__)
0496 std::size_t{64}
0497 #elif defined(__AVX__)
0498 std::size_t{32}
0499 #elif defined(__SSE__)
0500 std::size_t{16}
0501 #else
0502 std::size_t{0}
0503
0504
0505 #endif
0506 );
0507
0508 alignas(kMaxAlignment) std::array<std::byte, SIZE> m_data{};
0509 const Handler* m_handler{nullptr};
0510 };
0511
0512 using Any = AnyBase<sizeof(void*)>;
0513
0514 #undef _ACTS_ANY_VERBOSE
0515 #undef _ACTS_ANY_VERBOSE_BUFFER
0516 #undef _ACTS_ANY_ENABLE_VERBOSE
0517
0518 }