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