Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
callable_buffer.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
4#include <helios/container/details/callable_buffer_common.hpp>
5
6#include <concepts>
7#include <cstddef>
8#include <cstring>
9#include <functional>
10#include <memory>
11#include <memory_resource>
12#include <new>
13#include <tuple>
14#include <type_traits>
15#include <utility>
16#include <vector>
17
19
20/**
21 * @brief Implementation class for a single-instance callable buffer with
22 * explicit allocator.
23 * @details Stores exactly one callable of any `CallableBufferStorable` type in
24 * a contiguous byte buffer, with embedded function pointers for type-erased
25 * invocation. Unlike `CallableBufferArray`, this container holds at most one
26 * callable at a time. Designed for command/handler patterns where you want
27 * value semantics with type-erased dispatch.
28 *
29 * Use the `CallableBuffer` alias for ergonomic usage.
30 *
31 * @tparam Allocator Allocator type for the internal byte buffer (default:
32 * `std::allocator<std::byte>`).
33 * @tparam Signatures Function signatures in the form void(Args...).
34 */
35template <typename Allocator, typename... Signatures>
36 requires((sizeof...(Signatures) > 0) &&
37 (details::VoidSignature<Signatures> && ...))
39public:
40 static constexpr size_t kNumOperations = sizeof...(Signatures);
41
42 using allocator_type = Allocator;
44 std::allocator_traits<allocator_type>::template rebind_alloc<std::byte>;
45 using size_type = size_t;
46
47private:
48 using DestroyFn = void (*)(void*);
49 using RelocateFn = void (*)(void* dest, void* src);
50
51 /// @brief Function pointer type for the first signature (used for size
52 /// calculations).
53 using FirstExecuteFn = details::TupleToFunctionPtrType<
54 details::NthSignatureArgsT<0, Signatures...>>;
55
56 static_assert(((sizeof(details::TupleToFunctionPtrType<
57 details::NthSignatureArgsT<0, Signatures...>>) ==
58 sizeof(details::TupleToFunctionPtrType<
59 details::SignatureArgsT<Signatures>>)) &&
60 ...),
61 "All function pointer types must have the same size");
62
63 using BufferType = std::vector<std::byte, byte_allocator_type>;
64
65 /// @brief Pack of signature types for use in method validation.
66 using SignaturePack = std::tuple<Signatures...>;
67
68public:
69 /// @brief Default constructor. Creates an empty buffer with no callable.
70 CallableBufferImpl() = default;
71
72 /**
73 * @brief Constructs with a custom allocator.
74 * @param alloc Allocator instance to use
75 */
76 explicit CallableBufferImpl(const allocator_type& alloc)
77 : buffer_(byte_allocator_type(alloc)) {}
78
79 /**
80 * @brief Constructs with a PMR memory resource.
81 * @details Enabled only when `allocator_type` is constructible from
82 * `std::pmr::memory_resource*`.
83 * @param resource Memory resource used to construct allocator
84 */
85 explicit CallableBufferImpl(std::pmr::memory_resource* resource)
86 requires std::constructible_from<allocator_type, std::pmr::memory_resource*>
88
89 CallableBufferImpl(std::nullptr_t) = delete;
90
93 ~CallableBufferImpl() noexcept { Clear(); }
94
97
98 /// @brief Destroys the stored callable (if any) and resets the buffer.
99 void Clear() noexcept;
100
101 /**
102 * @brief Stores a callable using its default `operator()` for invocation.
103 * @details For single-signature buffers, uses `operator()(Args...)`.
104 * For multi-signature buffers, uses
105 * `operator()(std::integral_constant<size_t, N>, Args...)`.
106 * Any previously stored callable is destroyed first.
107 * @tparam T Callable type
108 * @param callable Callable object to store
109 */
110 template <CallableBufferStorable T>
111 requires details::DefaultOpCallable<std::remove_cvref_t<T>, Signatures...>
112 void Set(T&& callable) {
113 SetImpl(std::forward<T>(callable));
114 }
115
116 /**
117 * @brief Stores a callable with custom member function pointers.
118 * @details Allows specifying custom function names for each operation.
119 * Number of Methods must match `kNumOperations`.
120 * Any previously stored callable is destroyed first.
121 * @tparam Methods Member function pointers for each operation
122 * @tparam T Callable type (deduced)
123 * @param callable Callable object to store
124 *
125 * @code
126 * // Single operation
127 * buffer.Set<&MyCmd::Run>(MyCmd{});
128 *
129 * // Multiple operations
130 * buffer.Set<&MyCmd::Execute, &MyCmd::Log>(MyCmd{});
131 * @endcode
132 */
133 template <auto... Methods, CallableBufferStorable T>
134 requires details::AllMethodsValid<T, SignaturePack, Methods...> &&
135 (sizeof...(Methods) == kNumOperations)
136 void Set(T&& callable) {
137 SetImplMethods<Methods...>(std::forward<T>(callable));
138 }
139
140 /**
141 * @brief Invokes operation `N` on the stored callable.
142 * @details Supports polymorphic argument conversion for flexible invocation.
143 * @warning Triggers assertion if the buffer is empty.
144 * @tparam N Operation index (0 to `kNumOperations-1`)
145 * @tparam UArgs Actual argument types (must be convertible to signature args)
146 * @param args Arguments for operation `N`
147 */
148 template <size_t N = 0, typename... UArgs>
149 requires details::ArgsConvertibleTo<
150 details::NthSignatureArgsT<N, Signatures...>, UArgs...> &&
151 (N < sizeof...(Signatures))
152 void Invoke(UArgs&&... args) noexcept;
153
154 /**
155 * @brief Swaps contents with another buffer.
156 * @param other Buffer to swap with
157 */
158 void Swap(CallableBufferImpl& other) noexcept(
159 std::is_nothrow_swappable_v<BufferType>) {
160 buffer_.swap(other.buffer_);
161 std::swap(has_value_, other.has_value_);
162 }
163
164 friend void swap(CallableBufferImpl& lhs, CallableBufferImpl& rhs) noexcept(
165 std::is_nothrow_swappable_v<BufferType>) {
166 lhs.Swap(rhs);
167 }
168
169 /**
170 * @brief Checks if a callable is currently stored.
171 * @return true if empty, false otherwise
172 */
173 [[nodiscard]] bool Empty() const noexcept { return !has_value_; }
174
175 /**
176 * @brief Gets the current capacity in bytes of the internal buffer.
177 * @return Capacity in bytes
178 */
179 [[nodiscard]] size_type CapacityBytes() const noexcept {
180 return buffer_.capacity();
181 }
182
183 /**
184 * @brief Gets the allocator used by the buffer.
185 * @return Allocator instance
186 */
187 [[nodiscard]] allocator_type GetAllocator() const noexcept {
188 return allocator_type(buffer_.get_allocator());
189 }
190
191private:
192 static constexpr size_type BaseHeaderSize() noexcept {
193 return (kNumOperations * sizeof(FirstExecuteFn)) + sizeof(DestroyFn) +
194 sizeof(RelocateFn) + sizeof(size_type);
195 }
196
197 static constexpr size_type AlignUp(size_type offset,
198 size_type alignment) noexcept {
199 return (offset + alignment - 1) & ~(alignment - 1);
200 }
201
202 template <typename T, size_t N, typename... Args>
203 static void ExecuteDefault(Args... args, void* data) noexcept;
204
205 template <typename T, auto Method, typename... Args>
206 static void ExecuteMethod(Args... args, void* data) noexcept;
207
208 template <typename T>
209 static void DestroyCallable(void* data) noexcept {
210 std::destroy_at(static_cast<T*>(data));
211 }
212
213 template <typename T>
214 static void RelocateCallable(void* dest, void* src) noexcept;
215
216 template <typename T>
217 void SetImpl(T&& callable);
218
219 template <auto... Methods, typename T>
220 void SetImplMethods(T&& callable);
221
222 template <typename T, size_t... Indices>
223 void StoreFunctionPointersDefault(std::index_sequence<Indices...>) noexcept;
224
225 template <typename T, auto... Methods, size_t... Indices>
226 void StoreFunctionPointersMethods(std::index_sequence<Indices...>) noexcept;
227
228 [[nodiscard]] void* GetDataPtr() const noexcept;
229
230 BufferType buffer_;
231 bool has_value_ = false;
232};
233
234template <typename Allocator, typename... Signatures>
235 requires((sizeof...(Signatures) > 0) &&
236 (details::VoidSignature<Signatures> && ...))
237inline CallableBufferImpl<Allocator, Signatures...>::CallableBufferImpl(
238 CallableBufferImpl&& other) noexcept
239 : buffer_(std::move(other.buffer_)), has_value_(other.has_value_) {
240 other.has_value_ = false;
241}
242
243template <typename Allocator, typename... Signatures>
244 requires((sizeof...(Signatures) > 0) &&
245 (details::VoidSignature<Signatures> && ...))
247 CallableBufferImpl&& other) noexcept -> CallableBufferImpl& {
248 if (this == &other) [[unlikely]] {
249 return *this;
250 }
251
252 Clear();
253 buffer_ = std::move(other.buffer_);
254 has_value_ = other.has_value_;
255 other.has_value_ = false;
256 return *this;
257}
258
259template <typename Allocator, typename... Signatures>
260 requires((sizeof...(Signatures) > 0) &&
261 (details::VoidSignature<Signatures> && ...))
262inline void CallableBufferImpl<Allocator, Signatures...>::Clear() noexcept {
263 if (!has_value_) {
264 return;
265 }
266
267 constexpr size_type fn_ptr_size = sizeof(FirstExecuteFn);
268 auto* header_base = buffer_.data();
269
270 auto destroy_fn = *std::launder(reinterpret_cast<DestroyFn*>(
271 header_base + (kNumOperations * fn_ptr_size)));
272
273 if (destroy_fn != nullptr) {
274 auto* data = GetDataPtr();
275 destroy_fn(data);
276 }
277
278 buffer_.clear();
279 has_value_ = false;
280}
281
282template <typename Allocator, typename... Signatures>
283 requires((sizeof...(Signatures) > 0) &&
284 (details::VoidSignature<Signatures> && ...))
285template <size_t N, typename... UArgs>
286 requires details::ArgsConvertibleTo<
287 details::NthSignatureArgsT<N, Signatures...>, UArgs...> &&
288 (N < sizeof...(Signatures))
289inline void CallableBufferImpl<Allocator, Signatures...>::Invoke(
290 UArgs&&... args) noexcept {
291 HELIOS_ASSERT(!Empty(), "Cannot invoke on an empty buffer!");
292
293 using ArgsTuple = details::NthSignatureArgsT<N, Signatures...>;
294 using ExecuteFn = details::TupleToFunctionPtrType<ArgsTuple>;
295
296 auto* header_base = buffer_.data();
297 auto exec_fn = *std::launder(
298 reinterpret_cast<ExecuteFn*>(header_base + (N * sizeof(FirstExecuteFn))));
299 auto* data = GetDataPtr();
300 exec_fn(std::forward<UArgs>(args)..., data);
301}
302
303template <typename Allocator, typename... Signatures>
304 requires((sizeof...(Signatures) > 0) &&
305 (details::VoidSignature<Signatures> && ...))
306template <typename T>
307inline void CallableBufferImpl<Allocator, Signatures...>::SetImpl(
308 T&& callable) {
309 using DecayedT = std::remove_cvref_t<T>;
310
311 constexpr size_type element_size = sizeof(DecayedT);
312 constexpr size_type element_align = alignof(DecayedT);
313 constexpr size_type base_header = BaseHeaderSize();
314 constexpr size_type fn_ptr_size = sizeof(FirstExecuteFn);
315
316 // Destroy any existing callable before overwriting
317 Clear();
318
319 const auto unaligned_data_offset = base_header;
320 const auto aligned_data_offset =
321 AlignUp(unaligned_data_offset, element_align);
322 const auto total_size = aligned_data_offset + element_size;
323
324 buffer_.resize(total_size);
325
326 auto* header_base = buffer_.data();
327
328 StoreFunctionPointersDefault<DecayedT>(
329 std::make_index_sequence<kNumOperations>{});
330
331 auto* destroy_fn_ptr = std::launder(reinterpret_cast<DestroyFn*>(
332 header_base + (kNumOperations * fn_ptr_size)));
333 if constexpr (std::is_trivially_destructible_v<DecayedT>) {
334 *destroy_fn_ptr = nullptr;
335 } else {
336 *destroy_fn_ptr = &DestroyCallable<DecayedT>;
337 }
338
339 auto* relocate_fn_ptr = std::launder(reinterpret_cast<RelocateFn*>(
340 header_base + (kNumOperations * fn_ptr_size) + sizeof(DestroyFn)));
341 if constexpr (std::is_trivially_copyable_v<DecayedT>) {
342 *relocate_fn_ptr = nullptr;
343 } else {
344 *relocate_fn_ptr = &RelocateCallable<DecayedT>;
345 }
346
347 // Store data offset
348 auto* data_offset_storage = std::launder(reinterpret_cast<size_type*>(
349 header_base + (kNumOperations * fn_ptr_size) + sizeof(DestroyFn) +
350 sizeof(RelocateFn)));
351 *data_offset_storage = aligned_data_offset;
352
353 auto* data = header_base + aligned_data_offset;
354 std::construct_at(std::launder(reinterpret_cast<DecayedT*>(data)),
355 std::forward<T>(callable));
356
357 has_value_ = true;
358}
359
360template <typename Allocator, typename... Signatures>
361 requires((sizeof...(Signatures) > 0) &&
362 (details::VoidSignature<Signatures> && ...))
363template <auto... Methods, typename T>
364inline void CallableBufferImpl<Allocator, Signatures...>::SetImplMethods(
365 T&& callable) {
366 using DecayedT = std::remove_cvref_t<T>;
367
368 constexpr size_type element_size = sizeof(DecayedT);
369 constexpr size_type element_align = alignof(DecayedT);
370 constexpr size_type base_header = BaseHeaderSize();
371 constexpr size_type fn_ptr_size = sizeof(FirstExecuteFn);
372
373 Clear();
374
375 const auto unaligned_data_offset = base_header;
376 const auto aligned_data_offset =
377 AlignUp(unaligned_data_offset, element_align);
378 const auto total_size = aligned_data_offset + element_size;
379
380 buffer_.resize(total_size);
381
382 auto* header_base = buffer_.data();
383
384 StoreFunctionPointersMethods<DecayedT, Methods...>(
385 std::make_index_sequence<kNumOperations>{});
386
387 auto* destroy_fn_ptr = std::launder(reinterpret_cast<DestroyFn*>(
388 header_base + (kNumOperations * fn_ptr_size)));
389 if constexpr (std::is_trivially_destructible_v<DecayedT>) {
390 *destroy_fn_ptr = nullptr;
391 } else {
392 *destroy_fn_ptr = &DestroyCallable<DecayedT>;
393 }
394
395 auto* relocate_fn_ptr = std::launder(reinterpret_cast<RelocateFn*>(
396 header_base + (kNumOperations * fn_ptr_size) + sizeof(DestroyFn)));
397 if constexpr (std::is_trivially_copyable_v<DecayedT>) {
398 *relocate_fn_ptr = nullptr;
399 } else {
400 *relocate_fn_ptr = &RelocateCallable<DecayedT>;
401 }
402
403 auto* data_offset_storage = std::launder(reinterpret_cast<size_type*>(
404 header_base + (kNumOperations * fn_ptr_size) + sizeof(DestroyFn) +
405 sizeof(RelocateFn)));
406 *data_offset_storage = aligned_data_offset;
407
408 auto* data = header_base + aligned_data_offset;
409 std::construct_at(std::launder(reinterpret_cast<DecayedT*>(data)),
410 std::forward<T>(callable));
411
412 has_value_ = true;
413}
414
415template <typename Allocator, typename... Signatures>
416 requires((sizeof...(Signatures) > 0) &&
417 (details::VoidSignature<Signatures> && ...))
418template <typename T, size_t... Indices>
419inline void
420CallableBufferImpl<Allocator, Signatures...>::StoreFunctionPointersDefault(
421 std::index_sequence<Indices...>) noexcept {
422 auto* header_base = buffer_.data();
423
424 (
425 [header_base]<size_t Index>() {
426 using ArgsTuple = details::NthSignatureArgsT<Index, Signatures...>;
427 using ExecuteFn = details::TupleToFunctionPtrType<ArgsTuple>;
428
429 [header_base]<typename... Args>(std::tuple<Args...>*) {
430 auto* fn_ptr = std::launder(reinterpret_cast<ExecuteFn*>(
431 header_base + (Index * sizeof(FirstExecuteFn))));
432 *fn_ptr = &ExecuteDefault<T, Index, Args...>;
433 }(static_cast<ArgsTuple*>(nullptr));
434 }.template operator()<Indices>(),
435 ...);
436}
437
438template <typename Allocator, typename... Signatures>
439 requires((sizeof...(Signatures) > 0) &&
440 (details::VoidSignature<Signatures> && ...))
441template <typename T, auto... Methods, size_t... Indices>
442inline void
443CallableBufferImpl<Allocator, Signatures...>::StoreFunctionPointersMethods(
444 std::index_sequence<Indices...>) noexcept {
445 auto* header_base = buffer_.data();
446
447 (
448 [header_base]<size_t Index>() {
449 using ArgsTuple = details::NthSignatureArgsT<Index, Signatures...>;
450 using ExecuteFn = details::TupleToFunctionPtrType<ArgsTuple>;
451
452 [header_base]<typename... Args>(std::tuple<Args...>*) {
453 auto* fn_ptr = std::launder(reinterpret_cast<ExecuteFn*>(
454 header_base + (Index * sizeof(FirstExecuteFn))));
455 *fn_ptr = &ExecuteMethod<T, details::kNthMethod<Index, Methods...>,
456 Args...>;
457 }(static_cast<ArgsTuple*>(nullptr));
458 }.template operator()<Indices>(),
459 ...);
460}
461
462template <typename Allocator, typename... Signatures>
463 requires((sizeof...(Signatures) > 0) &&
464 (details::VoidSignature<Signatures> && ...))
465template <typename T, size_t N, typename... Args>
466inline void CallableBufferImpl<Allocator, Signatures...>::ExecuteDefault(
467 Args... args, void* data) noexcept {
468 T* callable = static_cast<T*>(data);
469 if constexpr (kNumOperations == 1) {
470 std::invoke(*callable, args...);
471 } else {
472 std::invoke(*callable, std::integral_constant<size_t, N>{}, args...);
473 }
474}
475
476template <typename Allocator, typename... Signatures>
477 requires((sizeof...(Signatures) > 0) &&
478 (details::VoidSignature<Signatures> && ...))
479template <typename T, auto Method, typename... Args>
480inline void CallableBufferImpl<Allocator, Signatures...>::ExecuteMethod(
481 Args... args, void* data) noexcept {
482 T* callable = static_cast<T*>(data);
483 if constexpr (std::invocable<decltype(Method), T&, Args...>) {
484 std::invoke(Method, callable, args...);
485 } else {
486 std::invoke(Method, args...);
487 }
488}
489
490template <typename Allocator, typename... Signatures>
491 requires((sizeof...(Signatures) > 0) &&
492 (details::VoidSignature<Signatures> && ...))
493template <typename T>
494inline void CallableBufferImpl<Allocator, Signatures...>::RelocateCallable(
495 void* dest, void* src) noexcept {
496 T* typed_src = static_cast<T*>(src);
497 std::construct_at(static_cast<T*>(dest), std::move(*typed_src));
498 std::destroy_at(typed_src);
499}
500
501template <typename Allocator, typename... Signatures>
502 requires((sizeof...(Signatures) > 0) &&
503 (details::VoidSignature<Signatures> && ...))
504inline void* CallableBufferImpl<Allocator, Signatures...>::GetDataPtr()
505 const noexcept {
506 constexpr size_type fn_ptr_size = sizeof(FirstExecuteFn);
507 auto* header_base = buffer_.data();
508 const auto* data_offset_ptr = std::launder(reinterpret_cast<const size_type*>(
509 header_base + (kNumOperations * fn_ptr_size) + sizeof(DestroyFn) +
510 sizeof(RelocateFn)));
511 auto data_offset = *data_offset_ptr;
512 return const_cast<std::byte*>(buffer_.data() + data_offset);
513}
514
515namespace details {
516
517/// @brief Deduces the `CallableBufferImpl` type from signature arguments.
518template <typename... Args>
520
521template <VoidSignature FirstSig, typename... RestSigs>
522 requires(VoidSignature<RestSigs> && ...)
523struct CallableBufferDeducer<FirstSig, RestSigs...> {
524 using type =
525 CallableBufferImpl<std::allocator<std::byte>, FirstSig, RestSigs...>;
526};
527
528template <typename Alloc, VoidSignature FirstSig, typename... RestSigs>
529 requires InstantiatedAllocator<Alloc> && (VoidSignature<RestSigs> && ...)
530struct CallableBufferDeducer<Alloc, FirstSig, RestSigs...> {
531 using type = CallableBufferImpl<Alloc, FirstSig, RestSigs...>;
532};
533
534} // namespace details
535
536/**
537 * @brief Single-instance callable buffer with type-erased invocation.
538 * @details Stores exactly one callable of any type in a contiguous byte buffer
539 * with embedded function pointers for type-safe dispatch. Optimized for
540 * use-cases where a single command or handler needs to be stored and invoked
541 * without virtual dispatch or heap allocation per instance.
542 *
543 * The allocator parameter is optional. If the first template argument is a
544 * function signature (`void(Args...)`), the default allocator is used.
545 * Otherwise, the first argument is treated as an allocator.
546 *
547 * @tparam Args Either signatures only, or allocator followed by signatures
548 *
549 * @code
550 * // Single operation
551 * CallableBuffer<void(World&)> cmd;
552 * cmd.Set(SpawnEntityCmd{entity});
553 * cmd.Invoke(world);
554 *
555 * // Multiple operations
556 * CallableBuffer<void(World&), void(Logger&)> multi_cmd;
557 * multi_cmd.Set<&Cmd::Execute, &Cmd::Log>(Cmd{data});
558 * multi_cmd.Invoke<0>(world);
559 * multi_cmd.Invoke<1>(logger);
560 * @endcode
561 */
562template <typename... Args>
563using CallableBuffer = typename details::CallableBufferDeducer<Args...>::type;
564
565/**
566 * @brief Single-instance callable buffer with type-erased invocation with
567 * polymorphic allocator.
568 * @details Stores exactly one callable of any type in a contiguous byte buffer
569 * with embedded function pointers for type-safe dispatch. Optimized for
570 * use-cases where a single command or handler needs to be stored and invoked
571 * without virtual dispatch or heap allocation per instance.
572 *
573 * The allocator parameter is optional. If the first template argument is a
574 * function signature (`void(Args...)`), the default allocator is used.
575 * Otherwise, the first argument is treated as an allocator.
576 *
577 * @tparam Args Function signatures in the form void(Args...) for the operations
578 * to support.
579 *
580 * @code
581 * // Single operation
582 * PmrCallableBuffer<void(World&)> cmd(&resource);
583 * cmd.Set(SpawnEntityCmd{entity});
584 * cmd.Invoke(world);
585 *
586 * // Multiple operations
587 * PmrCallableBuffer<void(World&), void(Logger&)> multi_cmd(&resource);
588 * multi_cmd.Set<&Cmd::Execute, &Cmd::Log>(Cmd{data});
589 * multi_cmd.Invoke<0>(world);
590 * multi_cmd.Invoke<1>(logger);
591 * @endcode
592 */
593template <typename... Signatures>
596 Signatures...>;
597
598} // namespace helios::container
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Implementation class for a single-instance callable buffer with explicit allocator.
bool Empty() const noexcept
Checks if a callable is currently stored.
CallableBufferImpl(const allocator_type &alloc)
Constructs with a custom allocator.
CallableBufferImpl & operator=(CallableBufferImpl &&other) noexcept
void Invoke(UArgs &&... args) noexcept
Invokes operation N on the stored callable.
friend void swap(CallableBufferImpl &lhs, CallableBufferImpl &rhs) noexcept(std::is_nothrow_swappable_v< BufferType >)
CallableBufferImpl(CallableBufferImpl &&other) noexcept
void Swap(CallableBufferImpl &other) noexcept(std::is_nothrow_swappable_v< BufferType >)
Swaps contents with another buffer.
CallableBufferImpl & operator=(const CallableBufferImpl &)=delete
CallableBufferImpl(const CallableBufferImpl &)=delete
CallableBufferImpl(std::nullptr_t)=delete
CallableBufferImpl(std::pmr::memory_resource *resource)
Constructs with a PMR memory resource.
allocator_type GetAllocator() const noexcept
Gets the allocator used by the buffer.
std::allocator_traits< allocator_type >::template rebind_alloc< std::byte > byte_allocator_type
void Set(T &&callable)
Stores a callable with custom member function pointers.
size_type CapacityBytes() const noexcept
Gets the current capacity in bytes of the internal buffer.
CallableBufferImpl()=default
Default constructor. Creates an empty buffer with no callable.
typename details::CallableBufferDeducer< Args... >::type CallableBuffer
Single-instance callable buffer with type-erased invocation.
CallableBufferImpl< std::pmr::polymorphic_allocator< std::byte >, Signatures... > PmrCallableBuffer
Single-instance callable buffer with type-erased invocation with polymorphic allocator.
constexpr size_t AlignUp(size_t value, size_t alignment) noexcept
Aligns value up to alignment.
Definition common.hpp:226
STL namespace.
CallableBufferImpl< std::allocator< std::byte >, FirstSig, RestSigs... > type
Deduces the CallableBufferImpl type from signature arguments.