Библиотека для эффективного динамического полиморфизма посредством стирания типов (C++17 или новее).
Цели — эффективность, понятность и расширяемость.
лязг, gcc, msvc
How to build?
Основой части стирания типа библиотеки являются методы — описание, какую часть типа мы хотим использовать после стирания.
Давайте создадим один для стирания типов с помощью void draw()
:
В <anyany/anyany_macro.hpp> есть макрос anyany_method
Например, для метода «foo», который принимает целые числа и числа с плавающей запятой + возвращает число с плавающей запятой.
# 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 ();
}
};
Мы можем использовать Draw
для стирания типа:
# include < anyany/anyany.hpp >
using any_drawable = aa::any_with<Draw>;
И теперь мы можем использовать any_drawable
для хранения любого типа с помощью .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
}
Никаких виртуальных функций, наследования, указателей, управления памятью и т. д.! Хороший!
Вы можете добавить любое количество методов :
using any_my = aa::any_with<Draw, Run, aa::copy>;
Подожди, скопировать...? Да, по умолчанию aa::any_with
есть только деструктор, вы можете добавить метод aa::copy
чтобы сделать его копируемым и перемещаемым, или aa::move
, чтобы он мог только перемещаться.
Предопределенные методы :
делает any_with
копируемым и перемещаемым, включает aa::materialize
для ссылок (для этого также требуется метод aa::destroy
)
Также есть copy_with<Alloc, SooS>
, это позволяет копировать при использовании специального распределителя и размера оптимизации малых объектов ( aa::basic_any_with
).
делает 'any_with' подвижным
Примечание. Конструктор перемещения и оператор присваивания перемещения для any_with
всегда имеют значение noException.
включает специализацию std::hash
для any_with
, poly_ref
/...и т.д. Если any_with
пуст, то хэш == 0.
Примечание. poly_ptr
по умолчанию имеет специализацию std::hash
, но это хэш, похожий на указатель.
включает aa::any_cast
, aa::type_switch
, aa::visit_invoke
путем добавления RTTI в vtable.
Также добавляется .type_descriptor() -> aa::descriptor_t
для any_with
/ poly_ref
/...и т.д.
включает operator==
для any_with
/ poly_ref
/... и т. д.
Два объекта равны, если они содержат один и тот же тип (или оба пусты) и сохраненные значения равны.
включает operator<=>
и operator==
для any_with
/ poly_ref
/... и т. д.
Примечание.operpator<=> всегда возвращает std::partial_ordering
.
Если два объекта не содержат одного и того же типа, возвращается unordered
, в противном случае возвращается результат operator <=>
для содержащихся объектов.
Примечание: возвращает std::partial_ordering::equivalent
если оба пусты.
добавляет R operator()(Args...)
для any_with
/ poly_ref
/...и т.д.
Примечание. Также поддерживаются подписи const
, noexcept
и const noexcept
пример:
// 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 );
}
добавляет деструктор (по умолчанию он есть any_with
), но, возможно, вы захотите использовать его с aa::poly_ptr
для ручного управления временем жизни. Также включает aa::materialize
для ссылок (также требуется aa::copy)
См. концепцию method
в Anyany.hpp, если вам нужны все подробности о методах.
Полиморфные типы:
any_with<Methods...>
basic_any_with<Methods...>
poly_ref<Methods...>
poly_ptr<Methods...>
cref<Methods...>
cptr<Methods...>
stateful::ref<Methods...>
stateful::cref<Methods...>
Действия:
any_cast<T>
invoke<Method>
type_switch
visit_invoke
Полиморфные контейнеры:
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) };
};
Вы можете определить признаки для своих полиморфных иерархий (идентификаторы типов в стиле LLVM, виртуальные функции и т. д.).
В библиотеке есть две встроенные особенности (вы можете использовать их в качестве примера для реализации своих собственных):
Например, вы хотите иметь метод .foo() типа, созданного any_with
или poly_ref
с вашим Method .
Тогда вам следует использовать 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!
Примечание. Вы можете «затенить/переопределить» другие плагины, наследующие их в своем плагине (даже если наследование является частным).
См., например, плагины aa::spaceship/aa::copy_with.
Также вы можете специализировать aa::plugin<Any, Method> для своего метода или даже для «Любого» с некоторыми требованиями.
any_with
Принимает любое количество методов и создает тип, который может содержать любое значение, поддерживающее эти методы . Похоже на концепцию времени выполнения
Примечание. Существует тег «aa::force_stable_pointers» для принудительного выделения, поэтому poly_ptr/cptr
для any_with<...>
не будет признан недействительным после перемещения.
Примечание: aa::unreachable_allocator
, который прервет компиляцию, если basic_any_with<unreachable_allocator, ...>
попытается выделить память. Таким образом, вы можете принудительно не выделять типы
// 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 ...
};
Все конструкторы и операторы присваивания копирования/перемещения имеют строгую гарантию исключений.
Примечание. Если ваш тип не имеет конструктора перемещения noException, он действительно может повысить производительность (как в случае с std::vector).
Пример:
using any_printable = aa::any_with<Print, aa::move>;
basic_any_with
То же, что и any_with
, но с настраиваемым размером буфера выделения и оптимизации небольших объектов. Если вам нужен копируемый basic_any_with
, используйте copy_with
template < typename Alloc, size_t SooS, TTA... Methods>
using basic_any_with = /* ... */ ;
poly_ref
Невладеющий, всегда не нулевой, легкий (~=void*)
poly_ref<Methods...>
неявно преобразуется в меньшее количество методов.
poly_ref<A, B, C>
можно преобразовать в poly_ref<A, B>
, poly_ref<A>
, poly_ref<B>
... и т. д. и т. д.
Это означает, что вы можете добавлять в интерфейс функций только те методы, которые им действительно необходимы. Затем, если вы добавите метод к типу any_with
, НЕТ разрыва 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
То же, что и poly_ref
, но может быть создано из poly_ref
и const T&
aa::cref
— это псевдоним шаблона для aa::const_poly_ref
Примечание: не продлевает срок службы
poly_ptr
Невладеющий, допускающий значение NULL, легкий (~=void*)
poly_ptr<Methods...>
неявно преобразуется в меньшее количество методов.
poly_ptr<A, B, C>
можно преобразовать в poly_ptr<A, B>
, poly_ptr<A>
, poly_ptr<B>
... и т. д. и т. д.
Это означает, что вы можете добавлять в интерфейс функций только те методы, которые им действительно необходимы. Затем, если вы добавите метод к типу any_with
, НЕТ разрыва abi/api.
// you can invoke this function with any poly_ptr<..., A, ...>
void foo (poly_ptr<A>);
Примечание. poly_ptr
и const_poly_ptr
легко копируются, поэтому std::atomic<poly_ptr<...>>
работает.
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
То же, что и poly_ptr
, но может быть создан из poly_ptr
и const T*
/ Any*
aa::cptr
— это псевдоним шаблона для aa::const_poly_ptr
stateful_ref
aa::stateful::ref<Methods...>
содержит vtable.
Также может содержать ссылки на C-массивы и функции без распада.
Он имеет довольно простой интерфейс, только создание из T&/poly_ref
и вызов (например, с помощью aa::invoke)
Он будет иметь максимальную производительность, если вам нужно стереть 1-2 метода и не использовать any_cast
.
Типичный вариант использования — создание 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
То же, что и stateful::ref
, но может быть создан из const T&
и aa::cref
any_cast
требуется метод aa::type_info
Функциональный объект с оператором():
Работает как std::any_cast - вы можете преобразовать в T(copy), T&(take ref) (выдает aa::bad_cast, если приведение неверное)
Или вы можете передать указатель (или poly_ptr) (возвращает nullptr, если приведение неверное)
T* ptr = any_cast<T>(&any);
Пример:
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
Функциональный объект с оператором(), который принимает any_with/ref/cref/stateful::ref/stateful::cref
в качестве первого аргумента, а затем все аргументы метода и вызывает метод.
Если arg — const any_with
или cref
, то разрешены только константные методы .
Предварительное условие: Any.has_value() == true
Пример:
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
Выбирает .case на основе динамического типа входного аргумента и вызывает visitor
с этим динамическим типом или функцией по умолчанию.
Также поддерживает poly_traits
в качестве второго аргумента шаблона, поэтому он поддерживает любой тип, для которого у вас есть поли-типы.
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 ();
};
Пример:
Result val = aa::type_switch<Result>(value)
.case_< float >(foo1)
.case_< bool >(foo2)
.cases< char , int , unsigned char , double >(foo3)
.default_( 15 );
visit_invoke
Это... разрешение перегрузки во время выполнения! aa::make_visit_invoke<Foos...>
создает объект набора перегрузок с помощью метода .resolve(Args...)
, который выполняет разрешение перегрузки на основе типов времени выполнения Args....
Resolve возвращает nullopt
если не существует такой функции, которая могла бы принимать входные аргументы.
Этот пример очень простой, дополнительную информацию см. также в /examples/visit_invoke_example.hpp.
Пример:
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
Адаптер полиморфного контейнера, который ведет себя как Container<std::variant<Types...>>
, но гораздо более эффективен.
Поддерживает операции:
visit<Types...>(visitor)
— вызывает visitor
со всеми содержащимися значениями типов Types
view<T>
— возвращает ссылку на контейнер всех сохраненных значений типа T
Контейнер по умолчанию является std::vector
.
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...
};
Пример:
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
Этот контейнер ведет себя как std::vector<T>
, но хранит поля отдельно.
Поддерживаемая операция: view<T>
/ view<I>
для получения охвата всех полей этого индекса.
T
должен быть агрегатным или кортежным типом.
Примечание: data_parallel_vector
— это диапазон произвольного доступа. Примечание: игнорирует специализацию std::vector<bool>
, ведет себя как обычный вектор для логических значений.
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 ();
};
Пример:
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 );
};
Получить контент:
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