guanaqo 1.0.0-alpha.27
Utilities for scientific software
Loading...
Searching...
No Matches
type-erasure.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file
4/// @ingroup type_erasure
5/// Flexible type erasure utilities.
6
8#include <guanaqo/export.h>
11
12#include <algorithm>
13#include <array>
14#include <cassert>
15#include <cstddef>
16#include <exception>
17#include <functional>
18#include <memory>
19#include <new>
20#include <stdexcept>
21#include <type_traits>
22#include <typeinfo>
23#include <utility>
24
25namespace guanaqo {
26
27/// @ingroup type_erasure
28class GUANAQO_EXPORT bad_type_erased_type : public std::logic_error {
29 public:
30 bad_type_erased_type(const std::type_info &actual_type,
31 const std::type_info &requested_type,
32 const std::string &message = "")
33 : std::logic_error{message}, actual_type{actual_type},
35
36 [[nodiscard]] const char *what() const noexcept override {
37 message = "";
38 if (const char *w = std::logic_error::what(); w && *w) {
39 message += w;
40 message += ": ";
41 }
42 message = "Type requested: " + demangled_typename(requested_type) +
43 ", type contained: " + demangled_typename(actual_type);
44 return message.c_str();
45 }
46
47 private:
48 const std::type_info &actual_type;
49 const std::type_info &requested_type;
50 mutable std::string message;
51};
52
53/// @ingroup type_erasure
54class GUANAQO_EXPORT bad_type_erased_constness : public std::logic_error {
55 public:
57 : std::logic_error{"Non-const method called on a TypeErased object "
58 "that references a const object"} {}
59};
60
61namespace detail {
62
63template <class>
64struct required_function; // undefined
65template <class R, class... Args>
66struct required_function<R(Args...)> {
67 using type = R (*)(void *self, Args...);
68};
69template <class R, class... Args>
70struct required_function<R(Args...) const> {
71 using type = R (*)(const void *self, Args...);
72};
73template <class, class VTable>
74struct optional_function; // undefined
75template <class R, class... Args, class VTable>
76struct optional_function<R(Args...), VTable> {
77 using type = R (*)(void *self, Args..., const VTable &);
78};
79template <class R, class... Args, class VTable>
80struct optional_function<R(Args...) const, VTable> {
81 using type = R (*)(const void *self, Args..., const VTable &);
82};
83
84} // namespace detail
85
86/// @addtogroup type_erasure
87/// @{
88
89/// A required function includes a void pointer to self, in addition to the
90/// arguments of @p F.
91template <class F>
93/// An optional function includes a void pointer to self, the arguments of
94/// @p F, and an additional reference to the VTable, so that it can be
95/// implemented in terms of other functions.
96template <class F, class VTable>
98
100 /// Copy-construct a new instance into storage.
101 required_function_t<void(void *storage) const> copy;
102 /// Move-construct a new instance into storage.
103 required_function_t<void(void *storage)> move;
104 /// Destruct the given instance.
106};
107
108template <class T>
110 .copy =
111 [](const void *self, void *storage) {
112 new (storage) T(*std::launder(reinterpret_cast<const T *>(self)));
113 },
114 .move =
115 [](void *self, void *storage) noexcept {
116 // TODO: require that move constructor is noexcept?
117 if constexpr (std::is_const_v<T>)
118 std::terminate();
119 else
120 new (storage)
121 T(std::move(*std::launder(reinterpret_cast<T *>(self))));
122 },
123 .destroy =
124 [](void *self) {
125 if constexpr (std::is_const_v<T>)
126 std::terminate();
127 else
128 std::destroy_at(std::launder(reinterpret_cast<T *>(self)));
129 },
130};
131
132/// Struct that stores pointers to functions to copy, move or destroy a
133/// polymorphic object.
134/// Inherit from this struct to add useful functions.
136 /// The original type of the stored object.
137 const std::type_info *type = &typeid(void);
138 /// Function pointers for copying, moving and destroying the stored object.
140
141 BasicVTable() = default;
142
143 template <class T>
144 BasicVTable(std::in_place_t, T &) noexcept {
145 type = &typeid(T);
147 }
148
149 void copy(const void *self, void *storage) const {
150 copy_move_destroy_functions->copy(self, storage);
151 }
152 void move(void *self, void *storage) const {
153 copy_move_destroy_functions->move(self, storage);
154 }
155 void destroy(void *self) const {
156 copy_move_destroy_functions->destroy(self);
157 }
158};
159
160/// @}
161
162namespace detail {
163template <class Class, class... ExtraArgs>
164struct Launderer {
165 private:
166 template <auto M, class V, class C, class R, class... Args>
167 [[gnu::always_inline]] static constexpr auto
168 do_invoke(V *self, Args... args, ExtraArgs...) -> R {
169 return std::invoke(M, *std::launder(reinterpret_cast<C *>(self)),
170 std::forward<Args>(args)...);
171 }
172 template <auto M, class T, class R, class... Args>
173 requires std::is_base_of_v<T, Class>
174 [[gnu::always_inline]] static constexpr auto invoker_ovl(R (T::*)(Args...)
175 const) {
176 return do_invoke<M, const void, const Class, R, Args...>;
177 }
178 template <auto M, class T, class R, class... Args>
179 requires std::is_base_of_v<T, Class>
180 [[gnu::always_inline]] static constexpr auto
181 invoker_ovl(R (T::*)(Args...)) {
182 if constexpr (std::is_const_v<Class>)
183 return +[](void *, Args..., ExtraArgs...) {
185 };
186 else
187 return do_invoke<M, void, Class, R, Args...>;
188 }
189
190 public:
191 /// Returns a function that accepts a void pointer, casts it to the class
192 /// type of the member function @p Method, launders it, and then invokes
193 /// @p Method with it, passing on the arguments to @p Method. The function
194 /// can also accept additional arguments at the end, of type @p ExtraArgs.
195 template <auto Method>
196 [[gnu::always_inline]] static constexpr auto invoker() {
197 return invoker_ovl<Method>(Method);
198 }
199};
200} // namespace detail
201
202/// @addtogroup type_erasure
203/// @{
204
205/// @copydoc detail::Launderer::invoker
206template <class Class, auto Method, class... ExtraArgs>
207[[gnu::always_inline]] constexpr auto type_erased_wrapped() {
208 return detail::Launderer<Class, ExtraArgs...>::template invoker<Method>();
209}
210
211template <class VTable, class Allocator>
212constexpr size_t default_te_buffer_size() {
213 struct S {
214 [[no_unique_address]] Allocator allocator;
215 void *self = nullptr;
216 VTable vtable;
217 };
218 const size_t max_size = 128;
219 return max_size - std::min(max_size, sizeof(S));
220}
221
222template <class... Types>
223constexpr size_t required_te_buffer_size_for() {
224 constexpr size_t sizes[] = {sizeof(Types)...}; // NOLINT(*-avoid-c-arrays)
225 return *std::max_element(std::begin(sizes), std::end(sizes));
226}
227
228/// Class for polymorphism through type erasure. Saves the entire vtable, and
229/// uses small buffer optimization.
230///
231/// @todo Decouple allocation/small buffer optimization.
232template <class VTable = BasicVTable,
233 class Allocator = std::allocator<std::byte>,
234 size_t SmallBufferSize = default_te_buffer_size<VTable, Allocator>()>
236 public:
237 static constexpr size_t small_buffer_size = SmallBufferSize;
238 using allocator_type = Allocator;
239
240 private:
241 using allocator_traits = std::allocator_traits<allocator_type>;
242 using buffer_type = std::array<std::byte, small_buffer_size>;
243 [[no_unique_address]] alignas(std::max_align_t) buffer_type small_buffer;
244 [[no_unique_address]] allocator_type allocator;
245
246 private:
247 /// True if @p T is not a child class of @ref TypeErased.
248 template <class T>
249 static constexpr auto no_child_of_ours =
250 !std::is_base_of_v<TypeErased, std::remove_cvref_t<T>>;
251
252 protected:
253 static constexpr size_t invalid_size =
254 static_cast<size_t>(0xDEAD'BEEF'DEAD'BEEF);
255 static constexpr size_t mut_ref_size =
256 static_cast<size_t>(0xFFFF'FFFF'FFFF'FFFF);
257 static constexpr size_t const_ref_size =
258 static_cast<size_t>(0xFFFF'FFFF'FFFF'FFFE);
259 [[nodiscard]] static bool size_indicates_ownership(size_t size) {
260 return size != const_ref_size && size != mut_ref_size;
261 }
262 [[nodiscard]] static bool size_indicates_const(size_t size) {
263 return size == const_ref_size;
264 }
265
266 /// Pointer to the stored object.
267 void *self = nullptr;
268 /// Size required to store the object.
270 VTable vtable;
271
272 public:
273 /// Default constructor.
274 TypeErased() noexcept(noexcept(allocator_type()) &&
275 noexcept(VTable())) = default;
276 /// Default constructor (allocator aware).
277 template <class Alloc>
278 TypeErased(std::allocator_arg_t, const Alloc &alloc) : allocator{alloc} {}
279
280 /// Copy constructor.
281 TypeErased(const TypeErased &other)
282 : allocator{allocator_traits::select_on_container_copy_construction(
283 other.allocator)},
284 vtable{other.vtable} {
286 }
287 /// Copy constructor (allocator aware).
288 TypeErased(const TypeErased &other, const allocator_type &alloc)
289 : allocator{alloc}, vtable{other.vtable} {
291 }
292 /// Copy constructor (allocator aware).
293 TypeErased(std::allocator_arg_t, const allocator_type &alloc,
294 const TypeErased &other)
295 : TypeErased{other, alloc} {}
296
297 /// Copy assignment.
299 // Check for self-assignment
300 if (&other == this)
301 return *this;
302 // Delete our own storage before assigning a new value
303 cleanup();
304 vtable = other.vtable;
306 return *this;
307 }
308
309 /// Move constructor.
310 TypeErased(TypeErased &&other) noexcept
311 : allocator{std::move(other.allocator)},
312 vtable{std::move(other.vtable)} {
313 size = other.size;
314 // If not owned, or if dynamically allocated, simply steal storage,
315 // simply move the pointer.
316 // TODO: is it safe to assume that we can simply move the pointer
317 // without performing a move if we moved the allocator? What if the
318 // allocator has a small buffer?
319 if (!other.owns_referenced_object() || size > small_buffer_size) {
320 // We stole the allocator, so we can steal the storage as well
321 self = std::exchange(other.self, nullptr);
322 }
323 // Otherwise, use the small buffer and do an explicit move
324 else if (other.self) {
325 self = small_buffer.data();
326 vtable.move(other.self, self); // assumed not to throw
327 vtable.destroy(other.self); // nothing to deallocate
328 other.self = nullptr;
329 }
330 other.size = invalid_size;
331 }
332 /// Move constructor (allocator aware).
333 TypeErased(TypeErased &&other, const allocator_type &alloc) noexcept
334 : allocator{alloc}, vtable{std::move(other.vtable)} {
335 // Only continue if other actually contains a value
336 if (other.self == nullptr)
337 return;
338 size = other.size;
339 // If not owned, simply move the pointer
340 if (!other.owns_referenced_object()) {
341 self = std::exchange(other.self, nullptr);
342 }
343 // If dynamically allocated, simply steal other's storage
344 else if (size > small_buffer_size) {
345 // Can we steal the storage because of equal allocators?
346 if (allocator == other.allocator) {
347 self = std::exchange(other.self, nullptr);
348 }
349 // If the allocators are not the same, we cannot steal the
350 // storage, so do an explicit move
351 else {
352 self = allocator.allocate(size);
353 vtable.move(other.self, self);
354 // Cannot call other.cleanup() here because we stole the vtable
355 vtable.destroy(other.self);
356 other.deallocate();
357 }
358 }
359 // Otherwise, use the small buffer and do an explicit move
360 else if (other.self) {
361 self = small_buffer.data();
362 vtable.move(other.self, self);
363 // Cannot call other.cleanup() here because we stole the vtable
364 vtable.destroy(other.self); // nothing to deallocate
365 other.self = nullptr;
366 }
367 other.size = invalid_size;
368 }
369 /// Move constructor (allocator aware).
370 TypeErased(std::allocator_arg_t, const allocator_type &alloc,
371 TypeErased &&other) noexcept
372 : TypeErased{std::move(other), alloc} {}
373
374 /// Move assignment.
375 TypeErased &operator=(TypeErased &&other) noexcept {
376 // Check for self-assignment
377 if (&other == this)
378 return *this;
379 // Delete our own storage before assigning a new value
380 cleanup();
381 // Check if we are allowed to steal the allocator
382 static constexpr bool prop_alloc =
383 allocator_traits::propagate_on_container_move_assignment::value;
384 if constexpr (prop_alloc)
385 allocator = std::move(other.allocator);
386 // Only assign if other contains a value
387 if (other.self == nullptr)
388 return *this;
389
390 size = other.size;
391 vtable = std::move(other.vtable);
392 // If not owned, simply move the pointer
393 if (!other.owns_referenced_object()) {
394 self = std::exchange(other.self, nullptr);
395 }
396 // If dynamically allocated, simply steal other's storage
397 else if (size > small_buffer_size) {
398 // Can we steal the storage because of equal allocators?
399 // TODO: is it safe to assume that we can simply move the pointer
400 // without performing a move if we moved the allocator? What if the
401 // allocator has a small buffer?
402 if (prop_alloc || allocator == other.allocator) {
403 self = std::exchange(other.self, nullptr);
404 }
405 // If the allocators are not the same, we cannot steal the
406 // storage, so do an explicit move
407 else {
408 self = allocator.allocate(size);
409 vtable.move(other.self, self); // assumed not to throw
410 vtable.destroy(other.self);
411 // Careful, we might have moved other.allocator!
412 auto &deallocator = prop_alloc ? allocator : other.allocator;
413 using pointer_t = typename allocator_traits::pointer;
414 auto &&other_pointer = static_cast<pointer_t>(other.self);
415 deallocator.deallocate(other_pointer, size);
416 other.self = nullptr;
417 }
418 }
419 // Otherwise, use the small buffer and do an explicit move
420 else if (other.self) {
421 self = small_buffer.data();
422 vtable.move(other.self, self);
423 vtable.destroy(other.self); // nothing to deallocate
424 other.self = nullptr;
425 }
426 other.size = invalid_size;
427 return *this;
428 }
429
430 /// Destructor.
432
433 /// Main constructor that type-erases the given argument.
434 template <class T, class Alloc>
435 requires no_child_of_ours<T>
436 explicit TypeErased(std::allocator_arg_t, const Alloc &alloc, T &&d)
437 : allocator{alloc} {
438 construct_inplace<std::remove_cvref_t<T>>(std::forward<T>(d));
439 }
440 /// Main constructor that type-erases the object constructed from the given
441 /// argument.
442 template <class T, class Alloc, class... Args>
443 explicit TypeErased(std::allocator_arg_t, const Alloc &alloc,
444 std::in_place_type_t<T>, Args &&...args)
445 : allocator{alloc} {
446 construct_inplace<std::remove_cvref_t<T>>(std::forward<Args>(args)...);
447 }
448 /// @copydoc TypeErased(std::allocator_arg_t, const Alloc &, T &&)
449 /// Requirement prevents this constructor from taking precedence over the
450 /// copy and move constructors.
451 template <class T>
452 requires no_child_of_ours<T>
453 explicit TypeErased(T &&d) {
454 construct_inplace<std::remove_cvref_t<T>>(std::forward<T>(d));
455 }
456 /// Main constructor that type-erases the object constructed from the given
457 /// argument.
458 template <class T, class... Args>
459 explicit TypeErased(std::in_place_type_t<T>, Args &&...args) {
460 construct_inplace<std::remove_cvref_t<T>>(std::forward<Args>(args)...);
461 }
462
463 /// Construct a type-erased wrapper of type Ret for an object of type T,
464 /// initialized in-place with the given arguments.
465 template <class Ret, class T, class Alloc, class... Args>
466 requires std::is_base_of_v<TypeErased, Ret>
467 static Ret make(std::allocator_arg_t tag, const Alloc &alloc,
468 Args &&...args) {
469 Ret r{tag, alloc};
470 r.template construct_inplace<T>(std::forward<Args>(args)...);
471 return r;
472 }
473 /// Construct a type-erased wrapper of type Ret for an object of type T,
474 /// initialized in-place with the given arguments.
475 template <class Ret, class T, class... Args>
476 requires no_leading_allocator<Args...>
477 static Ret make(Args &&...args) {
478 return make<Ret, T>(std::allocator_arg, allocator_type{},
479 std::forward<Args>(args)...);
480 }
481
482 /// Check if this wrapper wraps an object. False for default-constructed
483 /// objects.
484 explicit operator bool() const noexcept { return self != nullptr; }
485
486 /// Check if this wrapper owns the storage of the wrapped object, or
487 /// whether it simply stores a reference to an object that was allocated
488 /// elsewhere.
489 [[nodiscard]] bool owns_referenced_object() const noexcept {
491 }
492
493 /// Check if the wrapped object is const.
494 [[nodiscard]] bool referenced_object_is_const() const noexcept {
496 }
497
498 /// Get a copy of the allocator.
499 allocator_type get_allocator() const noexcept { return allocator; }
500
501 /// Query the contained type.
502 [[nodiscard]] const std::type_info &type() const noexcept {
503 return *vtable.type;
504 }
505
506 /// Convert the type-erased object to the given type.
507 /// @throws guanaqo::bad_type_erased_type
508 /// If T does not match the stored type.
509 template <class T>
510 requires(!std::is_const_v<T>)
511 [[nodiscard]] T &as() & {
512 if (typeid(T) != type())
513 throw bad_type_erased_type(type(), typeid(T));
516 return *reinterpret_cast<T *>(self);
517 }
518 /// @copydoc as()
519 template <class T>
520 requires(std::is_const_v<T>)
521 [[nodiscard]] T &as() const & {
522 if (typeid(T) != type())
523 throw bad_type_erased_type(type(), typeid(T));
524 return *reinterpret_cast<T *>(self);
525 }
526 /// @copydoc as()
527 template <class T>
528 [[nodiscard]] T &&as() && {
529 if (typeid(T) != type())
530 throw bad_type_erased_type(type(), typeid(T));
531 if (!std::is_const_v<T> && referenced_object_is_const())
533 return std::move(*reinterpret_cast<T *>(self));
534 }
535
536 /// Get a type-erased pointer to the wrapped object.
537 /// @throws guanaqo::bad_type_erased_constness
538 /// If the wrapped object is const.
539 /// @see @ref get_const_pointer()
540 [[nodiscard]] void *get_pointer() const {
543 return self;
544 }
545 /// Get a type-erased pointer to the wrapped object.
546 [[nodiscard]] const void *get_const_pointer() const { return self; }
547
548 /// @see @ref derived_from_TypeErased
549 template <std::derived_from<TypeErased> Child>
550 friend void derived_from_TypeErased_helper(const Child &) noexcept {
551 static constexpr bool False = sizeof(Child) != sizeof(Child);
552 static_assert(False, "not allowed in an evaluated context");
553 }
554
555 private:
556 /// Deallocates the storage when destroyed.
557 struct Deallocator {
560 Deallocator(const Deallocator &) = delete;
563 : instance{std::exchange(o.instance, nullptr)} {}
564 Deallocator &operator=(Deallocator &&) noexcept = delete;
565 void release() noexcept { instance = nullptr; }
566 ~Deallocator() { instance ? instance->deallocate() : void(); }
567 };
568
569 /// Ensure that storage is available, either by using the small buffer if
570 /// it is large enough, or by calling the allocator.
571 /// Returns a RAII wrapper that deallocates the storage unless released.
573 assert(!self);
574 assert(size != invalid_size);
575 assert(size > 0);
578 : allocator.allocate(size);
579 this->size = size;
580 return {this};
581 }
582
583 /// Deallocate the memory without invoking the destructor.
584 void deallocate() {
585 assert(size != invalid_size);
586 assert(size > 0);
588 using pointer_t = typename allocator_traits::pointer;
590 allocator.deallocate(reinterpret_cast<pointer_t>(self), size);
591 self = nullptr;
592 }
593
594 /// Destroy the type-erased object (if not empty), and deallocate the memory
595 /// if necessary.
596 void cleanup() {
597 if (!owns_referenced_object()) {
598 self = nullptr;
599 } else if (self) {
600 vtable.destroy(self);
601 deallocate();
602 }
603 }
604
605 template <bool CopyAllocator>
606 void do_copy_assign(const TypeErased &other) {
607 constexpr bool prop_alloc =
608 allocator_traits::propagate_on_container_copy_assignment::value;
609 if constexpr (CopyAllocator && prop_alloc)
610 allocator = other.allocator;
611 if (!other)
612 return;
613 if (!other.owns_referenced_object()) {
614 // Non-owning: simply copy the pointer.
615 size = other.size;
616 self = other.self;
617 } else {
618 auto storage_guard = allocate(other.size);
619 // If copy constructor throws, storage should be deallocated and
620 // self set to null, otherwise the TypeErased destructor will
621 // attempt to call the contained object's destructor, which is
622 // undefined behavior if construction failed.
623 vtable.copy(other.self, self);
624 storage_guard.release();
625 }
626 }
627
628 protected:
629 /// Ensure storage and construct the type-erased object of type T in-place.
630 template <class T, class... Args>
631 void construct_inplace(Args &&...args) {
632 static_assert(std::is_same_v<T, std::remove_cvref_t<T>>);
633 if constexpr (std::is_pointer_v<T>) {
634 T ptr{args...};
635 using Tnp = std::remove_pointer_t<T>;
636 size = std::is_const_v<Tnp> ? const_ref_size : mut_ref_size;
637 vtable = VTable{std::in_place, *ptr};
638 self = const_cast<std::remove_const_t<Tnp> *>(ptr);
639 } else {
640 // Allocate memory
641 auto storage_guard = allocate(sizeof(T));
642 // Construct the stored object
643 using destroyer = std::unique_ptr<T, noop_delete<T>>;
644#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000
645 // TODO: remove when we drop libc++ 15 support
646 destroyer obj_guard{new (self) T{std::forward<Args>(args)...}};
647#else
648 destroyer obj_guard{std::uninitialized_construct_using_allocator(
649 reinterpret_cast<T *>(self), allocator,
650 std::forward<Args>(args)...)};
651#endif
652 vtable = VTable{std::in_place, static_cast<T &>(*obj_guard.get())};
653 obj_guard.release();
654 storage_guard.release();
655 }
656 }
657
658 /// Call the vtable function @p f with the given arguments @p args,
659 /// implicitly passing the @ref self pointer and @ref vtable reference if
660 /// necessary.
661 template <class Ret, class... FArgs, class... Args>
662 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(const void *, FArgs...),
663 Args &&...args) const {
664 assert(f);
665 assert(self);
666 using LastArg = last_type_t<FArgs...>;
667 if constexpr (std::is_same_v<LastArg, const VTable &>)
668 return f(self, std::forward<Args>(args)..., vtable);
669 else
670 return f(self, std::forward<Args>(args)...);
671 }
672 /// @copydoc call
673 template <class Ret, class... FArgs, class... Args>
674 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(void *, FArgs...),
675 Args &&...args) {
676 assert(f);
677 assert(self);
680 using LastArg = last_type_t<FArgs...>;
681 if constexpr (std::is_same_v<LastArg, const VTable &>)
682 return f(self, std::forward<Args>(args)..., vtable);
683 else
684 return f(self, std::forward<Args>(args)...);
685 }
686 /// @copydoc call
687 template <class Ret>
688 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(const void *)) const {
689 assert(f);
690 assert(self);
691 return f(self);
692 }
693 /// @copydoc call
694 template <class Ret>
695 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(void *)) {
696 assert(f);
697 assert(self);
700 return f(self);
701 }
702 /// @copydoc call
703 template <class Ret>
704 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(const void *,
705 const VTable &)) const {
706 assert(f);
707 assert(self);
708 return f(self, vtable);
709 }
710 /// @copydoc call
711 template <class Ret>
712 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(void *,
713 const VTable &)) {
714 assert(f);
715 assert(self);
718 return f(self, vtable);
719 }
720};
721
722template <class Child>
724 requires(Child c) { derived_from_TypeErased_helper(c); };
725
726/// @}
727
728} // namespace guanaqo
static constexpr size_t mut_ref_size
static Ret make(Args &&...args)
Construct a type-erased wrapper of type Ret for an object of type T, initialized in-place with the gi...
static constexpr size_t small_buffer_size
std::array< std::byte, small_buffer_size > buffer_type
T & as() const &
Convert the type-erased object to the given type.
Deallocator allocate(size_t size)
Ensure that storage is available, either by using the small buffer if it is large enough,...
TypeErased & operator=(const TypeErased &other)
Copy assignment.
std::allocator_traits< allocator_type > allocator_traits
TypeErased() noexcept(noexcept(allocator_type()) &&noexcept(VTable()))=default
Default constructor.
TypeErased(const TypeErased &other)
Copy constructor.
decltype(auto) call(Ret(*f)(void *, const VTable &))
Call the vtable function f with the given arguments args, implicitly passing the self pointer and vta...
const void * get_const_pointer() const
Get a type-erased pointer to the wrapped object.
void construct_inplace(Args &&...args)
Ensure storage and construct the type-erased object of type T in-place.
allocator_type allocator
TypeErased(TypeErased &&other, const allocator_type &alloc) noexcept
Move constructor (allocator aware).
friend void derived_from_TypeErased_helper(const Child &) noexcept
static Ret make(std::allocator_arg_t tag, const Alloc &alloc, Args &&...args)
Construct a type-erased wrapper of type Ret for an object of type T, initialized in-place with the gi...
void deallocate()
Deallocate the memory without invoking the destructor.
decltype(auto) call(Ret(*f)(const void *, FArgs...), Args &&...args) const
Call the vtable function f with the given arguments args, implicitly passing the self pointer and vta...
~TypeErased()
Destructor.
decltype(auto) call(Ret(*f)(void *, FArgs...), Args &&...args)
Call the vtable function f with the given arguments args, implicitly passing the self pointer and vta...
static constexpr auto no_child_of_ours
True if T is not a child class of TypeErased.
T & as() &
Convert the type-erased object to the given type.
TypeErased(T &&d)
Main constructor that type-erases the given argument.
static bool size_indicates_const(size_t size)
TypeErased(std::allocator_arg_t, const Alloc &alloc, std::in_place_type_t< T >, Args &&...args)
Main constructor that type-erases the object constructed from the given argument.
T && as() &&
Convert the type-erased object to the given type.
decltype(auto) call(Ret(*f)(const void *, const VTable &)) const
Call the vtable function f with the given arguments args, implicitly passing the self pointer and vta...
TypeErased(TypeErased &&other) noexcept
Move constructor.
bool referenced_object_is_const() const noexcept
Check if the wrapped object is const.
void do_copy_assign(const TypeErased &other)
decltype(auto) call(Ret(*f)(void *))
Call the vtable function f with the given arguments args, implicitly passing the self pointer and vta...
TypeErased(std::allocator_arg_t, const allocator_type &alloc, const TypeErased &other)
Copy constructor (allocator aware).
void * self
Pointer to the stored object.
TypeErased(std::allocator_arg_t, const Alloc &alloc, T &&d)
Main constructor that type-erases the given argument.
TypeErased(std::in_place_type_t< T >, Args &&...args)
Main constructor that type-erases the object constructed from the given argument.
void * get_pointer() const
Get a type-erased pointer to the wrapped object.
allocator_type get_allocator() const noexcept
Get a copy of the allocator.
static bool size_indicates_ownership(size_t size)
static constexpr size_t const_ref_size
size_t size
Size required to store the object.
bool owns_referenced_object() const noexcept
Check if this wrapper owns the storage of the wrapped object, or whether it simply stores a reference...
static constexpr size_t invalid_size
void cleanup()
Destroy the type-erased object (if not empty), and deallocate the memory if necessary.
TypeErased(const TypeErased &other, const allocator_type &alloc)
Copy constructor (allocator aware).
TypeErased(std::allocator_arg_t, const allocator_type &alloc, TypeErased &&other) noexcept
Move constructor (allocator aware).
decltype(auto) call(Ret(*f)(const void *)) const
Call the vtable function f with the given arguments args, implicitly passing the self pointer and vta...
const std::type_info & type() const noexcept
Query the contained type.
TypeErased & operator=(TypeErased &&other) noexcept
Move assignment.
const std::type_info & actual_type
bad_type_erased_type(const std::type_info &actual_type, const std::type_info &requested_type, const std::string &message="")
const std::type_info & requested_type
const char * what() const noexcept override
Pretty-print type names.
std::string demangled_typename(const std::type_info &t)
Get the pretty name of the given type as a string.
typename last_type< Pack... >::type last_type_t
required_function_t< void(void *storage)> move
Move-construct a new instance into storage.
required_function_t< void(void *storage) const > copy
Copy-construct a new instance into storage.
required_function_t< void()> destroy
Destruct the given instance.
typename detail::optional_function< F, VTable >::type optional_function_t
An optional function includes a void pointer to self, the arguments of F, and an additional reference...
constexpr size_t default_te_buffer_size()
typename detail::required_function< F >::type required_function_t
A required function includes a void pointer to self, in addition to the arguments of F.
constexpr size_t required_te_buffer_size_for()
constexpr CopyMoveDestroyVTable copy_move_destroy_vtable
constexpr auto type_erased_wrapped()
Returns a function that accepts a void pointer, casts it to the class type of the member function Met...
Deleter that skips deallocation.
const CopyMoveDestroyVTable * copy_move_destroy_functions
Function pointers for copying, moving and destroying the stored object.
void destroy(void *self) const
const std::type_info * type
The original type of the stored object.
void copy(const void *self, void *storage) const
BasicVTable(std::in_place_t, T &) noexcept
void move(void *self, void *storage) const
Deallocates the storage when destroyed.
Deallocator(const Deallocator &)=delete
Deallocator(Deallocator &&o) noexcept
Deallocator & operator=(Deallocator &&) noexcept=delete
Deallocator(TypeErased *instance) noexcept
Deallocator & operator=(const Deallocator &)=delete
static constexpr auto invoker()
Returns a function that accepts a void pointer, casts it to the class type of the member function Met...
static constexpr auto invoker_ovl(R(T::*)(Args...))
static constexpr auto do_invoke(V *self, Args... args, ExtraArgs...) -> R
static constexpr auto invoker_ovl(R(T::*)(Args...) const)
R(*)(void *self, Args..., const VTable &) type
R(*)(const void *self, Args..., const VTable &) type
Lightweight type-trait helpers.