Biblioteca para polimorfismo dinámico eficiente mediante borrado de tipos (C++17 o posterior)
Los objetivos son eficiencia, comprensibilidad y extensibilidad.
sonido metálico, gcc, msvc
How to build?
La base de la parte de borrado de tipos de la biblioteca son los Métodos : una descripción de qué parte del tipo queremos usar después de borrar.
Creemos uno para borrar tipos con void draw()
:
Hay una macro anyany_method
en <anyany/anyany_macro.hpp>. Por ejemplo, para el método 'foo', que acepta int y float + devuelve float
# include < anyany/anyany_macro.hpp >
anyany_method (foo, (&self, int i, float f) requires(self.foo(i, f)) -> float);
...
void example (aa::any_with<foo> obj) {
if (obj. has_value ())
float x = obj. foo ( 5 , 3 . 14f ); // all works
obj = some_type_with_foo{};
obj = some_other_type_with_foo ();
}
// For each type T do value.draw()
struct Draw {
template < typename T>
static void do_invoke ( const T& self) {
self. draw ();
}
};
Podemos usar Draw
para borrar tipos:
# include < anyany/anyany.hpp >
using any_drawable = aa::any_with<Draw>;
Y ahora podemos usar any_drawable
para almacenar cualquier tipo con .draw()
// some types with .draw()
struct Circle {
void draw () const {
std::cout << " Draw Circle n " ;
}
};
struct Square {
void draw () const {
std::cout << " Draw Square n " ;
}
};
int main () {
any_drawable shape = Circle{};
aa::invoke<Draw>(shape); // prints "Draw Circle"
shape = Square{};
aa::invoke<Draw>(shape); // prints "Draw Square"
// see /examples folder for more
}
¡No hay funciones virtuales, herencia, punteros, gestión de memoria, etc.! ¡Lindo!
Puede agregar cualquier número de métodos :
using any_my = aa::any_with<Draw, Run, aa::copy>;
Espera, ¿copiar...? Sí, de forma predeterminada aa::any_with
solo tiene un destructor, puede agregar el método aa::copy
para hacerlo copiable y movible o aa::move
para que solo se mueva
Métodos predefinidos:
hace que any_with
sea copiable y movible, habilita aa::materialize
para referencias (esto también requiere el método aa::destroy
)
También hay copy_with<Alloc, SooS>
, esto permite copiar cuando se utiliza el asignador personalizado y el tamaño de optimización de objetos pequeños ( aa::basic_any_with
).
hace que 'any_with' sea móvil
Nota: mueva el constructor y mueva el operador de asignación para any_with
siempre noexcept
habilita la especialización std::hash
para any_with
, poly_ref
/...etc. Si any_with
está vacío, entonces hash == 0.
Nota: poly_ptr
tiene especialización en std::hash
de forma predeterminada, pero es un hash similar a un puntero.
habilita aa::any_cast
, aa::type_switch
, aa::visit_invoke
agregando RTTI en vtable.
También agrega .type_descriptor() -> aa::descriptor_t
para any_with
/ poly_ref
/...etc.
habilita operator==
para any_with
/ poly_ref
/...etc.
Dos objetos son iguales si contienen el mismo tipo (o ambos vacíos) y los valores almacenados son iguales.
habilita operator<=>
y operator==
para any_with
/ poly_ref
/...etc.
Nota: operador<=> siempre devuelve std::partial_ordering
.
Si dos objetos no contienen el mismo tipo, devuelve unordered
; de lo contrario, devuelve el resultado del operator <=>
para los objetos contenidos.
Nota: devuelve std::partial_ordering::equivalent
si ambos están vacíos
agrega R operator()(Args...)
para any_with
/ poly_ref
/...etc.
Nota: también se admiten firmas const
, noexcept
y const noexcept
ejemplo:
// stateful::cref is a lightweight thing,
// it stores vtable in itself(in this case only one function ptr)
// and const void* to value
// This is most effective way to erase function
template < typename Signature>
using function_ref = aa::stateful::cref<aa::call<Signature>>;
void foo (function_ref< int ( float ) const > ref) {
int result = ref ( 3.14 );
}
agrega destructor ( any_with
lo tiene de forma predeterminada), pero tal vez quieras usarlo con aa::poly_ptr
para administrar manualmente la vida útil. También habilita aa::materialize
para referencias (también requiere aa::copy)
Consulte el concepto method
en anyany.hpp si desea obtener todos los detalles sobre los métodos.
Tipos polimórficos:
any_with<Methods...>
basic_any_with<Methods...>
poly_ref<Methods...>
poly_ptr<Methods...>
cref<Methods...>
cptr<Methods...>
stateful::ref<Methods...>
stateful::cref<Methods...>
Comportamiento:
any_cast<T>
invoke<Method>
type_switch
visit_invoke
Contenedores polimórficos:
variant_swarm<Ts...>
data_parallel_vector<T, Alloc>
template < typename T>
concept method = /* ... */ ; // see anyany.hpp
// true if type can be used as poly traits argument in any_cast / type_switch / visit_invoke etc
template < typename T>
concept poly_traits = requires(T val, int some_val) {
{ val. get_type_descriptor (some_val) } -> std::same_as< descriptor_t >;
{ val. to_address (some_val) };
};
Puede definir rasgos para sus jerarquías polimórficas (identificadores de tipos similares a LLVM, funciones virtuales, etc.).
La biblioteca tiene dos de estos rasgos integrados (puede usarlos como ejemplo para implementar el suyo propio):
Por ejemplo, desea tener el método .foo() en el tipo creado por any_with
o poly_ref
con su método .
Entonces deberías usar plugins
:
struct Foo {
template < typename T>
static void do_invoke (T&) { /* do something */ }
// any_with<Foo> will inherit plugin<any_with<Foo>>
template < typename CRTP>
struct plugin {
void foo () const {
auto & self = * static_cast < const CRTP*>( this );
// Note: *this may not contain value, you can check it by calling self.has_value()
aa::invoke<Foo>(self);
}
};
};
//
any_with<Foo> val = /* ... */ ;
val.foo(); // we have method .foo from plugin!
Nota: Puede 'sombrear/anular' otros complementos heredándolos en su complemento (incluso si la herencia es privada)
Consulte aa::spaceship/aa::copy_with complementos, por ejemplo
También puedes especializar aa::plugin<Cualquiera, Método> para tu Método o incluso para 'Cualquiera' con algunos requisitos
any_with
Acepta cualquier número de métodos y crea un tipo que puede contener cualquier valor y que admite esos métodos . Similar al concepto de tiempo de ejecución
Nota: Hay una etiqueta 'aa::force_stable_pointers' para forzar la asignación, por lo que poly_ptr/cptr
a any_with<...>
no se invalidará después del movimiento.
Nota: aa::unreachable_allocator
, lo que interrumpirá la compilación si basic_any_with<unreachable_allocator, ...>
intenta asignar memoria. Entonces puedes forzar la no asignación de tipos
// All constructors and move assign operators are exception safe
// move and move assign always noexcept
// See 'construct_interface' alias in anyany.hpp and 'struct plugin'for details how it works(comments)
struct Any : construct_interface<basic_any<Alloc, SooS, Methods...>, Methods...>{
// aliases to poly ref/ptr
using ptr = /* ... */ ;
using ref = /* ... */ ;
using const_ptr = /* ... */ ;
using const_ref = /* ... */ ;
// operator & for getting poly ptr
poly_ptr<Methods...> operator &() noexcept ;
const_poly_ptr<Methods...> operator &() const noexcept ;
// main constructors
// creates empty
constexpr Any ();
Any ( auto && value); // from any type
Any ( const Any&) requires aa::copy
Any& operator =( const Any&) requires aa::move and aa::copy
Any (Any&&) noexcept requires aa::move;
Any& operator =(Any&&) requires method aa::move;
// observers
bool has_value () const noexcept ;
// returns true if poly_ptr/ref to *this will not be invalidated after moving value
bool is_stable_pointers () const noexcept
// returns count of bytes sufficient to store current value
// (not guaranteed to be smallest)
// return 0 if !has_value()
size_t sizeof_now() const noexcept ;
// returns descriptor_v<void> if value is empty
type_descriptor_t type_descriptor () const noexcept requires aa::type_info;
// forces that after creation is_stable_pointers() == true (allocates memory)
template < typename T>
Any ( force_stable_pointers_t , T&& value);
// also emplace constructors(std::in_place_type_t<T>), initiaizer list versions
// same with force_stable_pointers_t tag etc etc
// same with Alloc...
// modifiers
// emplaces value in any, if exception thrown - any is empty(use operator= if you need strong exception guarantee here)
template < typename T, typename ... Args>
std:: decay_t <T>& emplace (Args&&...); // returns reference to emplaced value
template < typename T, typename U, typename ... Args>
std:: decay_t <T>& emplace (std::initializer_list<U> list, Args&&... args)
// postcondition: has_value() == false
void reset() noexcept ;
// see aa::equal_to description for behavior
bool operator ==( const Any&) const requires aa::spaceship || aa::equal_to;
// see aa::spaceship description for behavior
std::partial_ordering operator <=>( const Any&) const requires aa::spaceship;
// ... interface from plugins for Methods if presented ...
};
Todos los constructores y operadores de asignación de copiar/mover tienen una sólida garantía de excepción
Nota: si su tipo no tiene un constructor de movimiento excepto, realmente puede aumentar el rendimiento (como en el caso std::vector).
Ejemplo:
using any_printable = aa::any_with<Print, aa::move>;
basic_any_with
Igual que any_with
, pero con asignación personalizada y tamaño de búfer de optimización de objetos pequeños; si necesita un basic_any_with
copiable, use copy_with
template < typename Alloc, size_t SooS, TTA... Methods>
using basic_any_with = /* ... */ ;
poly_ref
No propietario, siempre no nulo, ligero (~=void*)
poly_ref<Methods...>
implícitamente convertible a un número menor de métodos.
poly_ref<A, B, C>
es convertible a poly_ref<A, B>
, poly_ref<A>
, poly_ref<B>
... etc, etc.
Esto significa que puede agregar en la interfaz de funciones solo los métodos que realmente requieren. Luego, si agrega Método a su tipo any_with
, NO habrá interrupciones abi/api.
// you can invoke this function with any poly_ref<..., A, ...>
void foo (poly_ref<A>);
template < template < typename > typename ... Methods>
struct poly_ref {
poly_ref ( const poly_ref&) = default ;
poly_ref (poly_ref&&) = default ;
poly_ref& operator =(poly_ref&&) = default ;
poly_ref& operator =( const poly_ref&) = default ;
// only explicit rebind reference after creation
void operator =( auto &&) = delete ;
descriptor_t type_descriptor () const noexcept requires aa::type_info;
// from mutable lvalue
template <not_const_type T> // not shadow copy ctor
poly_ref (T& value) noexcept
poly_ptr<Methods...> operator &() const noexcept ;
// ... interface from plugins for Methods if presented ...
}
const_poly_ref
Igual que poly_ref
, pero se puede crear a partir de poly_ref
y const T&
aa::cref
es un alias de plantilla para aa::const_poly_ref
Nota: no extienda la vida útil
poly_ptr
No propietario, anulable, ligero (~=void*)
poly_ptr<Methods...>
implícitamente convertible a un número menor de métodos.
poly_ptr<A, B, C>
es convertible a poly_ptr<A, B>
, poly_ptr<A>
, poly_ptr<B>
... etc, etc.
Esto significa que puede agregar en la interfaz de funciones solo los métodos que realmente requieren. Luego, si agrega Método a su tipo any_with
, NO habrá interrupciones abi/api.
// you can invoke this function with any poly_ptr<..., A, ...>
void foo (poly_ptr<A>);
Nota: poly_ptr
y const_poly_ptr
se pueden copiar trivialmente, por lo que std::atomic<poly_ptr<...>>
funciona.
template < template < typename > typename ... Methods>
struct poly_ptr {
poly_ptr () = default ;
poly_ptr (std:: nullptr_t ) noexcept ;
poly_ptr& operator =(std:: nullptr_t ) noexcept ;
poly_ptr (not_const_type auto * ptr) noexcept ;
// from non-const pointer to Any with same methods
template <any_x Any>
poly_ptr (Any* ptr) noexcept ;
// observers
// returns raw pointer to value
void * raw () const noexcept ;
// NOTE: returns unspecified value if *this == nullptr
const vtable<Methods...>* raw_vtable_ptr () const noexcept ;
// returns descriptor_v<void> is nullptr
descriptor_t type_descriptor () const noexcept requires aa::type_info;
bool has_value () const noexcept ;
bool operator ==(std:: nullptr_t ) const noexcept ;
explicit operator bool () const noexcept ;
// similar to common pointer operator* returns reference
poly_ref<Methods...> operator *() const noexcept ;
const poly_ref<Methods...>* operator ->() const noexcept ;
}
const_poly_ptr
Igual que poly_ptr
, pero se puede crear a partir de poly_ptr
y const T*
/ Any*
aa::cptr
es un alias de plantilla para aa::const_poly_ptr
stateful_ref
aa::stateful::ref<Methods...>
contiene vtable en sí mismo.
También puede contener referencias a matrices C y funciones sin desintegración.
Tiene una interfaz bastante simple, solo crea desde T&/poly_ref
e invoca (mediante aa::invoke, por ejemplo)
Tendrá el máximo rendimiento si necesita borrar 1 o 2 métodos y no necesita usar any_cast
.
Caso de uso típico: crear una function_ref
template < typename Signature>
using function_ref = aa::stateful::cref<aa::call<Signature>>;
bool foo ( int ) { return true ; }
void example (function_ref< bool ( int ) const > ref) {
ref ( 5 );
}
int main () {
example (&foo);
example ([]( int x) { return false ; });
}
stateful_cref
Igual que stateful::ref
, pero se puede crear a partir de const T&
y aa::cref
any_cast
requiere el método aa::type_info
Objeto funcional con operador():
Funciona como std::any_cast: puede convertir a T(copiar), T&(tomar referencia) (lanza aa::bad_cast si la conversión es incorrecta)
O puede pasar el puntero (o poly_ptr) (devuelve nullptr, si la conversión es incorrecta)
T* ptr = any_cast<T>(&any);
Ejemplo:
using any_comparable = aa::any_with<aa::copy, aa::spaceship, aa::move>;
void Foo () {
any_comparable value = 5 ;
value. emplace <std::vector< int >>({ 1 , 2 , 3 , 4 }); // constructed in-place
// any_cast returns pointer to vector<int>(or nullptr if any do not contain vector<int>)
aa::any_cast<std::vector< int >>( std::addressof (value))-> back () = 0 ;
// version for reference
aa::any_cast<std::vector< int >&>(value). back () = 0 ;
// version which returns by copy (or move, if 'value' is rvalue)
auto vec = aa::any_cast<std::vector< int >>(value);
}
invoke
Objeto funcional con operador(), que acepta any_with/ref/cref/stateful::ref/stateful::cref
como primer argumento y luego todos los argumentos del método e invoca el método
Si arg es const any_with
o cref
, entonces solo se permiten métodos const .
condición previa: any.has_value() == verdadero
Ejemplo:
void example (any_with<Say> pet) {
if (!pet. has_value ())
return ;
// invokes Method `Say`, passes std::cout as first argument
aa::invoke<Say>(pet, std::cout);
}
void foo (std::vector<aa::poly_ref<Foo>> vec) {
// invokes Method `Foo` without arguments for each value in `vec`
std::ranges::for_each (vec, aa::invoke<Foo>);
}
type_switch
Selecciona .case según el tipo dinámico del argumento de entrada e invoca visitor
con este tipo dinámico o función predeterminada
También admite poly_traits
como segundo argumento de plantilla, por lo que admite cualquier tipo para el que tenga rasgos poli.
template < typename Result = void , poly_traits Traits = anyany_poly_traits>
struct type_switch_fn {
type_switch_fn (poly_ref<...>);
// invokes Fn if T contained
template < typename T, typename Fn>
type_switch_impl& case_ (Fn&& f);
// If value is one of Ts... F invoked (invokes count <= 1)
template < typename ... Ts, typename Fn>
type_switch_impl& cases (Fn&& f);
// if no one case succeded invokes 'f' with input poly_ref argument
template < typename Fn>
Result default_ (Fn&& f);
// if no one case succeded returns 'v'
Result default_ (Result v);
// if no one case succeded returns 'nullopt'
std::optional<Result> no_default ();
};
Ejemplo:
Result val = aa::type_switch<Result>(value)
.case_< float >(foo1)
.case_< bool >(foo2)
.cases< char , int , unsigned char , double >(foo3)
.default_( 15 );
visit_invoke
Es... ¡resolución de sobrecarga en tiempo de ejecución! aa::make_visit_invoke<Foos...>
crea un objeto de conjunto de sobrecarga con el método .resolve(Args...)
, que realiza la resolución de sobrecarga basada en los tipos de tiempo de ejecución de Args....
Resolve devuelve nullopt
si no existe dicha función para aceptar argumentos de entrada
Este ejemplo es muy básico; consulte también /examples/visit_invoke_example.hpp para obtener más información.
Ejemplo:
auto ship_asteroid = [](spaceship s, asteroid a) -> std::string { ... }
auto ship_star = [](spaceship s, star) -> std::string { ... }
auto star_star = [](star a, star b) -> std::string { ... }
auto ship_ship = [](spaceship a, spaceship b) -> std::string { ... }
// Create multidispacter
constexpr inline auto collision = aa::make_visit_invoke<std::string>(
ship_asteroid,
ship_star,
star_star,
ship_ship);
...
// Perform runtime overload resolution
std::optional<std::string> foo (any_with<A> a, any_with<B> b) {
return collision. resolve (a, b);
}
variant_swarm
Adaptador de contenedor polimórfico, que se comporta como Container<std::variant<Types...>>
, pero mucho más efectivo.
Soporta operaciones:
visit<Types...>(visitor)
- invoca visitor
con todos los valores contenidos de Types
view<T>
- devuelve la referencia al contenedor de todos los valores almacenados de tipo T
El contenedor es un std::vector
por defecto.
template < template < typename > typename Container, typename ... Ts>
struct basic_variant_swarm {
// modifiers
void swap (basic_variant_swarm& other) noexcept ;
friend void swap (basic_variant_swarm& a, basic_variant_swarm& b) noexcept ;
// selects right container and inserts [it, sent) into it
template <std::input_iterator It>
requires (tt::one_of<std:: iter_value_t <It>, std::ranges:: range_value_t <Container<Ts>>...>)
auto insert (It it, It sent);
// insert and erase overloads for each type in Ts...
using inserters_type::erase;
using inserters_type::insert;
// observe
bool empty () const noexcept ;
// returns count values, stored in container for T
template <tt::one_of<Ts...> T>
requires (std::ranges::sized_range<container_for<T>>)
auto count () const ;
template <std:: size_t I>
requires (std::ranges::sized_range<decltype(std::get<I>(containers))>)
auto count () const ;
// returns count of values stored in all containers
constexpr auto size () const requires(std::ranges::sized_range<container_for<Ts>> && ...);
// returns tuple of reference to containers #Is
template <std:: size_t ... Is>
auto view ();
template <std:: size_t ... Is>
auto view () const ;
// returns tuple of reference to containers for Types
template <tt::one_of<Ts...>... Types>
auto view ();
template <tt::one_of<Ts...>... Types>
auto view () const ;
// visit
// visits with 'v' and passes its results into 'out_visitor' (if result is not void)
template <tt::one_of<Ts...>... Types>
void visit (visitor_for<Types...> auto && v, auto && out_visitor);
// ignores visitor results
template <tt::one_of<Ts...>... Types>
void visit (visitor_for<Types...> auto && v);
// visits with 'v' and passes its results into 'out_visitor' (if result is not void)
void visit_all (visitor_for<Ts...> auto && v, auto && out_visitor);
// ignores visitor results
constexpr void visit_all (visitor_for<Ts...> auto && v);
template <tt::one_of<Ts...>... Types, std::input_or_output_iterator Out>
constexpr Out visit_copy (visitor_for<Types...> auto && v, Out out);
template <tt::one_of<Ts...>... Types, std::input_or_output_iterator Out>
constexpr Out visit_copy (Out out);
// visits with 'v' and passes its results into output iterator 'out', returns 'out" after all
template <std::input_or_output_iterator Out>
constexpr Out visit_copy_all (visitor_for<Ts...> auto && v, Out out);
// passes all values into 'out' iterator, returns 'out' after all
template <std::input_or_output_iterator Out>
constexpr Out visit_copy_all (Out out);
// ...also const versions for visit...
};
Ejemplo:
aa::variant_swarm< int , double , std::string> f;
// no runtime dispatching here, its just overloads
f.inesrt( " hello world " );
f.insert( 5 );
f.insert( 3.14 );
auto visitor = []( auto && x) {
std::cout << x << ' t ' ;
};
f.visit_all(visitor); // prints 5, 3.14, "hello world"
data_parallel_vector
Este contenedor se comporta como std::vector<T>
, pero almacena los campos por separado.
Operación admitida: view<T>
/ view<I>
para abarcar todos los campos de este índice
T
debe ser de tipo agregado o tipo tupla
Nota: data_parallel_vector
es un rango de acceso aleatorio Nota: ignora la especialización std::vector<bool>
, se comporta como un vector normal para bools
template < typename T, typename Alloc>
struct data_parallel_vector {
using value_type = T;
using allocator_type = Alloc;
using difference_type = std:: ptrdiff_t ;
using size_type = std:: size_t ;
using reference = proxy; // similar to vector<bool>::reference type
using const_reference = const_proxy;
void swap (data_parallel_vector&) noexcept ;
friend void swap (data_parallel_vector&) noexcept ;
data_parallel_vector () = default ;
explicit data_parallel_vector ( const allocator_type& alloc);
data_parallel_vector (size_type count, const value_type& value,
const allocator_type& alloc = allocator_type());
explicit data_parallel_vector (size_type count, const allocator_type& alloc = allocator_type());
template <std::input_iterator It>
data_parallel_vector (It first, It last, const allocator_type& alloc = allocator_type());
data_parallel_vector ( const data_parallel_vector& other, const allocator_type& alloc);
data_parallel_vector (data_parallel_vector&& other, const allocator_type& alloc);
data_parallel_vector (std::initializer_list<value_type> init,
const allocator_type& alloc = allocator_type());
// copy-move all default
data_parallel_vector& operator =(std::initializer_list<T> ilist);
using iterator;
using const_iterator;
iterator begin ();
const_iterator begin () const ;
iterator end ();
const_iterator end () const ;
const_iterator cbegin () const ;
const_iterator cend () const ;
reference front ();
const_reference front () const ;
reference back ();
reference back () const ;
reference operator [](size_type pos);
const_reference operator [](size_type pos) const ;
size_type capacity () const ;
size_type max_size () const ;
// returns tuple of spans to underlying containers
template < typename ... Types>
auto view ();
template < typename ... Types>
auto view () const ;
template <std:: size_t ... Nbs>
auto view ();
template <std:: size_t ... Nbs>
auto view () const ;
bool empty () const ;
size_type size () const ;
bool operator ==( const data_parallel_impl&) const = default ;
iterator emplace (const_iterator pos, element_t <Is>... fields);
reference emplace_back ( element_t <Is>... fields);
void push_back ( const value_type& v);
void push_back (value_type&& v);
iterator erase (const_iterator pos);
iterator erase (const_iterator b, const_iterator e);
iterator insert (const_iterator pos, const value_type& value);
iterator insert (const_iterator pos, value_type&& value);
iterator insert (const_iterator pos, size_type count, const T& value);
template <std::input_iterator It>
iterator insert (const_iterator pos, It first, It last);
iterator insert (const_iterator pos, std::initializer_list<value_type> ilist);
void assign (size_type count, const value_type& value);
template <std::input_iterator It>
void assign (It first, It last);
void assign (std::initializer_list<T> ilist);
void clear ();
void pop_back ();
void reserve (size_type new_cap);
void resize (size_type sz);
void resize (size_type sz, const value_type& v);
void shrink_to_fit ();
};
Ejemplo:
struct my_type {
int x;
float y;
bool l;
};
void foo () {
aa::data_parallel_vector<my_type> magic;
// ints, floats, bools are spans to all stored fields of my_type (&::x, &::y, &::l)
auto [ints, floats, bools] = magic;
magic. emplace_back ( 5 , 6 . f , true );
};
Obtener contenido:
include (FetchContent)
FetchContent_Declare(
AnyAny
GIT_REPOSITORY https://github.com/kelbon/AnyAny
GIT_TAG origin/main
)
FetchContent_MakeAvailable(AnyAny)
target_link_libraries (MyTargetName anyanylib)
add_subdirectory (AnyAny)
target_link_libraries (MyTargetName PUBLIC anyanylib)
build
git clone https://github.com/kelbon/AnyAny
cd AnyAny
cmake . -B build
cmake --build build
git clone https://github.com/kelbon/AnyAny
cd AnyAny/examples
cmake . -B build