alpaqa pantr
Nonconvex constrained optimization
Loading...
Searching...
No Matches
type-erasure.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <alpaqa/export.hpp>
7
8#include <algorithm>
9#include <cassert>
10#include <cstddef>
11#include <functional>
12#include <memory>
13#include <new>
14#include <stdexcept>
15#include <type_traits>
16#include <typeinfo>
17#include <utility>
18
19namespace alpaqa::util {
20
21template <class T>
23 T *t = nullptr;
24};
25
26class ALPAQA_EXPORT bad_type_erased_type : public std::logic_error {
27 public:
28 bad_type_erased_type(const std::type_info &actual_type,
29 const std::type_info &requested_type,
30 const std::string &message = "")
31 : std::logic_error{message}, actual_type{actual_type},
32 requested_type{requested_type} {}
33
34 [[nodiscard]] const char *what() const noexcept override {
35 message = "";
36 if (const char *w = std::logic_error::what(); w && *w) {
37 message += w;
38 message += ": ";
39 }
40 message = "Type requested: " + demangled_typename(requested_type) +
41 ", type contained: " + demangled_typename(actual_type);
42 return message.c_str();
43 }
44
45 private:
46 const std::type_info &actual_type;
47 const std::type_info &requested_type;
48 mutable std::string message;
49};
50
51/// Struct that stores the size of a polymorphic object, as well as pointers to
52/// functions to copy, move or destroy the object.
53/// Inherit from this struct to add useful functions.
55
56 template <class>
57 struct required_function; // undefined
58 template <class R, class... Args>
59 struct required_function<R(Args...)> {
60 using type = R (*)(void *self, Args...);
61 };
62 template <class>
63 struct required_const_function; // undefined
64 template <class R, class... Args>
65 struct required_const_function<R(Args...)> {
66 using type = R (*)(const void *self, Args...);
67 };
68 template <class, class VTable = BasicVTable>
69 struct optional_function; // undefined
70 template <class R, class... Args, class VTable>
71 struct optional_function<R(Args...), VTable> {
72 using type = R (*)(void *self, Args..., const VTable &);
73 };
74 template <class, class VTable = BasicVTable>
75 struct optional_const_function; // undefined
76 template <class R, class... Args, class VTable>
77 struct optional_const_function<R(Args...), VTable> {
78 using type = R (*)(const void *self, Args..., const VTable &);
79 };
80 /// A required function includes a void pointer to self, in addition to the
81 /// arguments of @p F.
82 template <class F>
84 /// @copydoc required_function_t
85 /// For const-qualified member functions.
86 template <class F>
88 /// An optional function includes a void pointer to self, the arguments of
89 /// @p F, and an additional reference to the VTable, so that it can be
90 /// implemented in terms of other functions.
91 template <class F, class VTable = BasicVTable>
93 /// @copydoc optional_function_t
94 /// For const-qualified member functions.
95 template <class F, class VTable = BasicVTable>
98
99 /// Copy-construct a new instance into storage.
100 required_const_function_t<void(void *storage)> copy = nullptr;
101 /// Move-construct a new instance into storage.
102 required_function_t<void(void *storage)> move = nullptr;
103 /// Destruct the given instance.
104 required_function_t<void()> destroy = nullptr;
105 /// The original type of the stored object.
106 const std::type_info *type = &typeid(void);
107
108 BasicVTable() = default;
109
110 template <class T>
112 copy = [](const void *self, void *storage) {
113 new (storage) T(*std::launder(reinterpret_cast<const T *>(self)));
114 };
115 // TODO: require that move constructor is noexcept?
116 move = [](void *self, void *storage) noexcept {
117 new (storage)
118 T(std::move(*std::launder(reinterpret_cast<T *>(self))));
119 };
120 destroy = [](void *self) {
121 std::launder(reinterpret_cast<T *>(self))->~T();
122 };
123 type = &typeid(T);
124 }
125};
126
127namespace detail {
128template <class... ExtraArgs>
129struct Launderer {
130 private:
131 template <auto M, class V, class T, class R, class... Args>
132 [[gnu::always_inline]] static constexpr auto
133 do_invoke(V *self, Args... args, ExtraArgs...) -> R {
134 return std::invoke(M, *std::launder(reinterpret_cast<T *>(self)),
135 std::forward<Args>(args)...);
136 }
137 template <auto M, class T, class R, class... Args>
138 [[gnu::always_inline]] static constexpr auto invoker_ovl(R (T::*)(Args...)
139 const) {
140 return do_invoke<M, const void, const T, R, Args...>;
141 }
142 template <auto M, class T, class R, class... Args>
143 [[gnu::always_inline]] static constexpr auto
144 invoker_ovl(R (T::*)(Args...)) {
145 return do_invoke<M, void, T, R, Args...>;
146 }
147
148 public:
149 /// Returns a function that accepts a void pointer, casts it to the class
150 /// type of the member function @p Method, launders it, and then invokes
151 /// @p Method with it, passing on the arguments to @p Method. The function
152 /// can also accept additional arguments at the end, of type @p ExtraArgs.
153 template <auto Method>
154 [[gnu::always_inline]] static constexpr auto invoker() {
155 return invoker_ovl<Method>(Method);
156 }
157};
158} // namespace detail
159
160/// @copydoc detail::Launderer::invoker
161template <auto Method, class... ExtraArgs>
162[[gnu::always_inline]] constexpr auto type_erased_wrapped() {
163 return detail::Launderer<ExtraArgs...>::template invoker<Method>();
164}
165
166template <class VTable, class Allocator>
167inline constexpr size_t default_te_buffer_size() {
168 struct S {
169 [[no_unique_address]] Allocator allocator;
170 void *self = nullptr;
171 VTable vtable;
172 };
173 const size_t max_size = 128;
174 return max_size - std::min(max_size, sizeof(S));
175}
176
177template <class... Types>
178inline constexpr size_t required_te_buffer_size_for() {
179 constexpr size_t sizes[] = {sizeof(Types)...};
180 return *std::max_element(std::begin(sizes), std::end(sizes));
181}
182
183/// Similar to `std::in_place_t`.
184template <class T>
186/// Convenience instance of @ref te_in_place_t.
187template <class T>
189
190/// Class for polymorphism through type erasure. Saves the entire vtable, and
191/// uses small buffer optimization.
192///
193/// @todo Decouple allocation/small buffer optimization.
194template <class VTable = BasicVTable,
195 class Allocator = std::allocator<std::byte>,
196 size_t SmallBufferSize = default_te_buffer_size<VTable, Allocator>()>
198 public:
199 static constexpr size_t small_buffer_size = SmallBufferSize;
200 using allocator_type = Allocator;
201
202 private:
203 using allocator_traits = std::allocator_traits<allocator_type>;
204 using buffer_type = std::array<std::byte, small_buffer_size>;
205 [[no_unique_address]] alignas(std::max_align_t) buffer_type small_buffer;
206 [[no_unique_address]] allocator_type allocator;
207
208 private:
209 /// True if @p T is not a child class of @ref TypeErased.
210 template <class T>
211 static constexpr auto no_child_of_ours =
212 !std::is_base_of_v<TypeErased, std::remove_cvref_t<T>>;
213
214 protected:
215 static constexpr size_t invalid_size =
216 static_cast<size_t>(0xDEADBEEFDEADBEEF);
217 /// Pointer to the stored object.
218 void *self = nullptr;
219 /// Size required to store the object.
221 VTable vtable;
222
223 public:
224 /// Default constructor.
225 TypeErased() noexcept(noexcept(allocator_type())) = default;
226 /// Default constructor (allocator aware).
227 template <class Alloc>
228 TypeErased(std::allocator_arg_t, const Alloc &alloc) : allocator{alloc} {}
229 /// Copy constructor.
230 TypeErased(const TypeErased &other)
231 : allocator{allocator_traits::select_on_container_copy_construction(
232 other.allocator)} {
233 do_copy_assign<false>(other);
234 }
235 /// Copy constructor (allocator aware).
237 : allocator{std::move(alloc)} {
238 do_copy_assign<false>(other);
239 }
240 /// Copy assignment.
242 // Check for self-assignment
243 if (&other == this)
244 return *this;
245 // Delete our own storage before assigning a new value
246 cleanup();
247 do_copy_assign<true>(other);
248 return *this;
249 }
250
251 /// Move constructor.
252 TypeErased(TypeErased &&other) noexcept
253 : allocator{std::move(other.allocator)} {
254 size = other.size;
255 vtable = std::move(other.vtable);
256 // If dynamically allocated, simply steal storage
257 if (size > small_buffer_size) {
258 // We stole the allocator, so we can steal the storage as well
259 self = std::exchange(other.self, nullptr);
260 }
261 // Otherwise, use the small buffer and do an explicit move
262 else if (other.self) {
263 self = small_buffer.data();
264 vtable.move(other.self, self);
265 vtable.destroy(other.self); // nothing to deallocate
266 other.self = nullptr;
267 }
268 }
269 /// Move constructor (allocator aware).
270 TypeErased(TypeErased &&other, const allocator_type &alloc) noexcept
271 : allocator{alloc} {
272 // Only continue if other actually contains a value
273 if (other.self == nullptr)
274 return;
275 size = other.size;
276 vtable = std::move(other.vtable);
277 // If dynamically allocated, simply steal other's storage
278 if (size > small_buffer_size) {
279 // Can we steal the storage because of equal allocators?
280 if (allocator == other.allocator) {
281 self = std::exchange(other.self, nullptr);
282 }
283 // If the allocators are not the same, we cannot steal the
284 // storage, so do an explicit move
285 else {
286 self = allocator.allocate(size);
287 vtable.move(other.self, self);
288 // Cannot call other.cleanup() here because we stole the vtable
289 vtable.destroy(other.self);
290 other.deallocate();
291 }
292 }
293 // Otherwise, use the small buffer and do an explicit move
294 else if (other.self) {
295 self = small_buffer.data();
296 vtable.move(other.self, self);
297 // Cannot call other.cleanup() here because we stole the vtable
298 vtable.destroy(other.self); // nothing to deallocate
299 other.self = nullptr;
300 }
301 }
302 /// Move assignment.
303 TypeErased &operator=(TypeErased &&other) noexcept {
304 // Check for self-assignment
305 if (&other == this)
306 return *this;
307 // Delete our own storage before assigning a new value
308 cleanup();
309 // Check if we are allowed to steal the allocator
310 static constexpr bool prop_alloc =
311 allocator_traits::propagate_on_container_move_assignment::value;
312 if constexpr (prop_alloc)
313 allocator = std::move(other.allocator);
314 // Only assign if other contains a value
315 if (other.self == nullptr)
316 return *this;
317
318 size = other.size;
319 vtable = std::move(other.vtable);
320 // If dynamically allocated, simply steal other's storage
321 if (size > small_buffer_size) {
322 // Can we steal the storage because of equal allocators?
323 if (prop_alloc || allocator == other.allocator) {
324 self = std::exchange(other.self, nullptr);
325 }
326 // If the allocators are not the same, we cannot steal the
327 // storage, so do an explicit move
328 else {
329 self = allocator.allocate(size);
330 vtable.move(other.self, self);
331 vtable.destroy(other.self);
332 // Careful, we might have moved other.allocator!
333 auto &deallocator = prop_alloc ? allocator : other.allocator;
334 using pointer_t = typename allocator_traits::pointer;
335 auto &&other_pointer = static_cast<pointer_t>(other.self);
336 deallocator.deallocate(other_pointer, size);
337 other.self = nullptr;
338 }
339 }
340 // Otherwise, use the small buffer and do an explicit move
341 else if (other.self) {
342 self = small_buffer.data();
343 vtable.move(other.self, self);
344 vtable.destroy(other.self); // nothing to deallocate
345 other.self = nullptr;
346 }
347 return *this;
348 }
349
350 /// Destructor.
352
353 /// Main constructor that type-erases the given argument.
354 template <class T, class Alloc>
355 explicit TypeErased(std::allocator_arg_t, const Alloc &alloc, T &&d)
356 : allocator{alloc} {
357 construct_inplace<std::remove_cvref_t<T>>(std::forward<T>(d));
358 }
359 /// Main constructor that type-erases the object constructed from the given
360 /// argument.
361 template <class T, class Alloc, class... Args>
362 explicit TypeErased(std::allocator_arg_t, const Alloc &alloc,
363 te_in_place_t<T>, Args &&...args)
364 : allocator{alloc} {
365 construct_inplace<std::remove_cvref_t<T>>(std::forward<Args>(args)...);
366 }
367 /// @copydoc TypeErased(std::allocator_arg_t, const Alloc &, T &&)
368 /// Requirement prevents this constructor from taking precedence over the
369 /// copy and move constructors.
370 template <class T>
371 requires no_child_of_ours<T>
372 explicit TypeErased(T &&d) {
373 construct_inplace<std::remove_cvref_t<T>>(std::forward<T>(d));
374 }
375 /// Main constructor that type-erases the object constructed from the given
376 /// argument.
377 template <class T, class... Args>
378 explicit TypeErased(te_in_place_t<T>, Args &&...args) {
379 construct_inplace<std::remove_cvref_t<T>>(std::forward<Args>(args)...);
380 }
381
382 /// Construct a type-erased wrapper of type Ret for an object of type T,
383 /// initialized in-place with the given arguments.
384 template <class Ret, class T, class Alloc, class... Args>
385 requires std::is_base_of_v<TypeErased, Ret>
386 static Ret make(std::allocator_arg_t tag, const Alloc &alloc,
387 Args &&...args) {
388 Ret r{tag, alloc};
389 r.template construct_inplace<T>(std::forward<Args>(args)...);
390 return r;
391 }
392 /// Construct a type-erased wrapper of type Ret for an object of type T,
393 /// initialized in-place with the given arguments.
394 template <class Ret, class T, class... Args>
395 requires no_leading_allocator<Args...>
396 static Ret make(Args &&...args) {
397 return make<Ret, T>(std::allocator_arg, allocator_type{},
398 std::forward<Args>(args)...);
399 }
400
401 /// Check if this wrapper wraps an object. False for default-constructed
402 /// objects.
403 explicit operator bool() const noexcept { return self != nullptr; }
404
405 /// Get a copy of the allocator.
406 allocator_type get_allocator() const noexcept { return allocator; }
407
408 /// Query the contained type.
409 [[nodiscard]] const std::type_info &type() const noexcept {
410 return *vtable.type;
411 }
412
413 /// Convert the type-erased object to the given type. The type is checked
414 /// in debug builds only, use with caution.
415 template <class T>
416 T &as() & {
417 if (typeid(T) != type())
418 throw bad_type_erased_type(type(), typeid(T));
419 return *reinterpret_cast<T *>(self);
420 }
421 /// @copydoc as()
422 template <class T>
423 const T &as() const & {
424 if (typeid(T) != type())
425 throw bad_type_erased_type(type(), typeid(T));
426 return *reinterpret_cast<const T *>(self);
427 }
428 /// @copydoc as()
429 template <class T>
430 const T &&as() && {
431 if (typeid(T) != type())
432 throw bad_type_erased_type(type(), typeid(T));
433 return std::move(*reinterpret_cast<T *>(self));
434 }
435
436 private:
437 /// Deallocates the storage when destroyed.
438 struct Deallocator {
441 Deallocator(const Deallocator &) = delete;
444 : instance{std::exchange(o.instance, nullptr)} {}
445 Deallocator &operator=(Deallocator &&) noexcept = delete;
446 void release() noexcept { instance = nullptr; }
448 };
449
450 /// Ensure that storage is available, either by using the small buffer if
451 /// it is large enough, or by calling the allocator.
452 /// Returns a RAII wrapper that deallocates the storage unless released.
454 assert(!self);
455 assert(size != invalid_size);
457 : allocator.allocate(size);
458 this->size = size;
459 return {this};
460 }
461
462 /// Deallocate the memory without invoking the destructor.
463 void deallocate() {
464 assert(size != invalid_size);
465 using pointer_t = typename allocator_traits::pointer;
467 allocator.deallocate(reinterpret_cast<pointer_t>(self), size);
468 self = nullptr;
469 }
470
471 /// Destroy the type-erased object (if not empty), and deallocate the memory
472 /// if necessary.
473 void cleanup() {
474 if (self) {
475 vtable.destroy(self);
476 deallocate();
477 }
478 }
479
480 template <bool CopyAllocator>
481 void do_copy_assign(const TypeErased &other) {
482 constexpr bool prop_alloc =
483 allocator_traits::propagate_on_container_copy_assignment::value;
484 if constexpr (CopyAllocator && prop_alloc)
485 allocator = other.allocator;
486 if (!other)
487 return;
488 vtable = other.vtable;
489 auto storage_guard = allocate(other.size);
490 // If copy constructor throws, storage should be deallocated and self
491 // set to null, otherwise the TypeErased destructor will attempt to call
492 // the contained object's destructor, which is undefined behavior if
493 // construction failed.
494 vtable.copy(other.self, self);
495 storage_guard.release();
496 }
497
498 protected:
499 /// Ensure storage and construct the type-erased object of type T in-place.
500 template <class T, class... Args>
501 void construct_inplace(Args &&...args) {
502 static_assert(std::is_same_v<T, std::remove_cvref_t<T>>);
503 // Allocate memory
504 auto storage_guard = allocate(sizeof(T));
505 // Construct the stored object
506 using destroyer = std::unique_ptr<T, noop_delete<T>>;
507 destroyer object_guard{new (self) T{std::forward<Args>(args)...}};
508 vtable = VTable{VTableTypeTag<T>{object_guard.get()}};
509 object_guard.release();
510 storage_guard.release();
511 }
512
513 /// Call the vtable function @p f with the given arguments @p args,
514 /// implicitly passing the @ref self pointer and @ref vtable reference if
515 /// necessary.
516 template <class Ret, class... FArgs, class... Args>
517 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(const void *, FArgs...),
518 Args &&...args) const {
519 assert(f);
520 assert(self);
521 using LastArg = util::last_type_t<FArgs...>;
522 if constexpr (std::is_same_v<LastArg, const VTable &>)
523 return f(self, std::forward<Args>(args)..., vtable);
524 else
525 return f(self, std::forward<Args>(args)...);
526 }
527 /// @copydoc call
528 template <class Ret, class... FArgs, class... Args>
529 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(void *, FArgs...),
530 Args &&...args) {
531 assert(f);
532 assert(self);
533 using LastArg = util::last_type_t<FArgs...>;
534 if constexpr (std::is_same_v<LastArg, const VTable &>)
535 return f(self, std::forward<Args>(args)..., vtable);
536 else
537 return f(self, std::forward<Args>(args)...);
538 }
539 /// @copydoc call
540 template <class Ret>
541 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(const void *)) const {
542 assert(f);
543 assert(self);
544 return f(self);
545 }
546 /// @copydoc call
547 template <class Ret>
548 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(void *)) {
549 assert(f);
550 assert(self);
551 return f(self);
552 }
553 /// @copydoc call
554 template <class Ret>
555 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(const void *,
556 const VTable &)) const {
557 assert(f);
558 assert(self);
559 return f(self, vtable);
560 }
561 /// @copydoc call
562 template <class Ret>
563 [[gnu::always_inline]] decltype(auto) call(Ret (*f)(void *,
564 const VTable &)) {
565 assert(f);
566 assert(self);
567 return f(self, vtable);
568 }
569};
570
571} // namespace alpaqa::util
Class for polymorphism through type erasure.
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...
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.
void construct_inplace(Args &&...args)
Ensure storage and construct the type-erased object of type T in-place.
void deallocate()
Deallocate the memory without invoking the destructor.
static constexpr size_t small_buffer_size
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...
void cleanup()
Destroy the type-erased object (if not empty), and deallocate the memory if necessary.
static constexpr size_t invalid_size
const T && as() &&
Convert the type-erased object to the given type.
TypeErased(const TypeErased &other, allocator_type alloc)
Copy constructor (allocator aware).
decltype(auto) call(Ret(*f)(void *))
Call the vtable function f with the given arguments args, implicitly passing the self pointer and vta...
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 auto no_child_of_ours
True if T is not a child class of TypeErased.
TypeErased(const TypeErased &other)
Copy constructor.
TypeErased(TypeErased &&other) noexcept
Move constructor.
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...
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...
size_t size
Size required to store the object.
const T & as() const &
Convert the type-erased object to the given type.
TypeErased(te_in_place_t< T >, Args &&...args)
Main constructor that type-erases the object constructed from the given argument.
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...
TypeErased() noexcept(noexcept(allocator_type()))=default
Default constructor.
void do_copy_assign(const TypeErased &other)
std::allocator_traits< allocator_type > allocator_traits
T & as() &
Convert the type-erased object to the given type.
std::array< std::byte, small_buffer_size > buffer_type
TypeErased & operator=(TypeErased &&other) noexcept
Move assignment.
TypeErased(std::allocator_arg_t, const Alloc &alloc, T &&d)
Main constructor that type-erases the given argument.
allocator_type get_allocator() const noexcept
Get a copy of the allocator.
void * self
Pointer to the stored object.
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...
TypeErased(T &&d)
Main constructor that type-erases the given argument.
TypeErased(TypeErased &&other, const allocator_type &alloc) noexcept
Move constructor (allocator aware).
const std::type_info & type() const noexcept
Query the contained type.
TypeErased(std::allocator_arg_t, const Alloc &alloc, te_in_place_t< T >, Args &&...args)
Main constructor that type-erases the object constructed from the given argument.
const std::type_info & actual_type
const std::type_info & requested_type
bad_type_erased_type(const std::type_info &actual_type, const std::type_info &requested_type, const std::string &message="")
const char * what() const noexcept override
std::string demangled_typename(const std::type_info &t)
Get the pretty name of the given type as a string.
constexpr size_t required_te_buffer_size_for()
typename last_type< Pack... >::type last_type_t
Definition: type-traits.hpp:38
constexpr auto type_erased_wrapped()
Returns a function that accepts a void pointer, casts it to the class type of the member function Met...
constexpr te_in_place_t< T > te_in_place
Convenience instance of te_in_place_t.
constexpr size_t default_te_buffer_size()
Similar to std::in_place_t.
Struct that stores the size of a polymorphic object, as well as pointers to functions to copy,...
required_const_function_t< void(void *storage)> copy
Copy-construct a new instance into storage.
typename 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...
const std::type_info * type
The original type of the stored object.
typename required_function< F >::type required_function_t
A required function includes a void pointer to self, in addition to the arguments of F.
required_function_t< void()> destroy
Destruct the given instance.
typename required_const_function< F >::type required_const_function_t
A required function includes a void pointer to self, in addition to the arguments of F.
required_function_t< void(void *storage)> move
Move-construct a new instance into storage.
BasicVTable(VTableTypeTag< T >) noexcept
typename optional_const_function< F, VTable >::type optional_const_function_t
An optional function includes a void pointer to self, the arguments of F, and an additional reference...
Deallocates the storage when destroyed.
Deallocator & operator=(const Deallocator &)=delete
Deallocator(const Deallocator &)=delete
Deallocator & operator=(Deallocator &&) noexcept=delete
Deallocator(Deallocator &&o) noexcept
Deallocator(TypeErased *instance) noexcept
static constexpr auto do_invoke(V *self, Args... args, ExtraArgs...) -> R
static constexpr auto invoker_ovl(R(T::*)(Args...) const)
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...))