Bibliothèque pour un polymorphisme dynamique efficace grâce à l'effacement de type (C++ 17 ou version ultérieure)
Les objectifs sont l’efficacité, la compréhensibilité et l’extensibilité.
clang, gcc, msvc
How to build?
Fondement de la bibliothèque, une partie d'effacement de type est une méthode - une description de la partie du type que nous voulons utiliser après l'effacement.
Créons-en un pour effacer les types avec void draw()
:
Il existe une macro anyany_method
dans <anyany/anyany_macro.hpp> Par exemple, pour la méthode 'foo', qui accepte int et float + renvoie 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 ();
}
};
Nous pouvons utiliser Draw
pour l'effacement de type :
# include < anyany/anyany.hpp >
using any_drawable = aa::any_with<Draw>;
Et maintenant nous pouvons utiliser any_drawable
pour stocker n'importe quel type avec .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
}
Il n'y a pas de fonctions virtuelles, d'héritage, de pointeurs, de gestion de mémoire, etc ! Bon!
Vous pouvez ajouter n'importe quel nombre de méthodes :
using any_my = aa::any_with<Draw, Run, aa::copy>;
Attends, copie... ? Oui, par défaut aa::any_with
n'a qu'un destructeur, vous pouvez ajouter la méthode aa::copy
pour le rendre copiable et mobile ou aa::move
pour le faire bouger uniquement
Méthodes prédéfinies :
rend any_with
copiable et mobile, active aa::materialize
pour les références (cela nécessite également la méthode aa::destroy
)
Il existe également copy_with<Alloc, SooS>
, cela permet la copie lorsque vous utilisez un allocateur personnalisé et une taille d'optimisation des petits objets ( aa::basic_any_with
)
rend 'any_with' mobile
Remarque : déplacer le constructeur et l'opérateur d'affectation de déplacement pour any_with
toujours nosauf
active la spécialisation std::hash
pour any_with
, poly_ref
/...etc. Si any_with
est vide, alors hash == 0.
Remarque : poly_ptr
a une spécialisation std::hash
par défaut, mais il s'agit d'un hachage de type pointeur.
active aa::any_cast
, aa::type_switch
, aa::visit_invoke
en ajoutant RTTI dans vtable.
Ajoute également .type_descriptor() -> aa::descriptor_t
pour any_with
/ poly_ref
/...etc.
active operator==
pour any_with
/ poly_ref
/...etc.
Deux objets sont égaux s'ils contiennent le même type (ou les deux vides) et que les valeurs stockées sont égales.
active operator<=>
et operator==
pour any_with
/ poly_ref
/...etc.
Remarque : operpator<=> renvoie toujours std::partial_ordering
.
Si deux objets ne contiennent pas le même type, renvoie unordered
, sinon renvoie le résultat de operator <=>
pour les objets contenus.
Remarque : renvoie std::partial_ordering::equivalent
si les deux sont vides
ajoute R operator()(Args...)
pour any_with
/ poly_ref
/...etc.
Remarque : également pris en charge les signatures const
, noexcept
et const noexcept
exemple:
// 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 );
}
ajoute un destructeur ( any_with
l'a par défaut), mais vous souhaitez peut-être l'utiliser avec aa::poly_ptr
pour gérer manuellement la durée de vie. Active également aa::materialize
pour les références (nécessite également aa::copy)
Voir le concept method
dans anyany.hpp si vous souhaitez tous les détails sur les méthodes
Types polymorphes :
any_with<Methods...>
basic_any_with<Methods...>
poly_ref<Methods...>
poly_ptr<Methods...>
cref<Methods...>
cptr<Methods...>
stateful::ref<Methods...>
stateful::cref<Methods...>
Actes:
any_cast<T>
invoke<Method>
type_switch
visit_invoke
Conteneurs polymorphes :
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) };
};
Vous pouvez définir des traits pour vos hiérarchies polymorphes (identifiants de type de type LLVM, fonctions virtuelles, etc.).
La bibliothèque intègre deux de ces caractéristiques (vous pouvez les utiliser comme exemple pour implémenter les vôtres) :
Par exemple, vous souhaitez que la méthode .foo() soit créée par any_with
ou poly_ref
avec votre Method .
Ensuite, vous devez utiliser 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!
Remarque : vous pouvez « masquer/remplacer » d'autres plugins en héritant d'eux dans votre plugin (même si l'héritage est privé)
Voir aa::spaceship/aa::copy_with plugins par exemple
Vous pouvez également spécialiser aa::plugin<Any, Method> pour votre méthode ou même pour 'Any' avec certaines exigences
any_with
Accepte n'importe quel nombre de Methods et crée un type pouvant contenir n'importe quelle valeur, qui prend en charge ces Methods . Similaire au concept d'exécution
Remarque : Il existe la balise 'aa::force_stable_pointers' pour forcer l'allocation, donc poly_ptr/cptr
vers any_with<...>
ne sera pas invalidé après le déplacement.
Remarque : aa::unreachable_allocator
, ce qui interrompra la compilation si basic_any_with<unreachable_allocator, ...>
tente d'allouer de la mémoire. Vous pouvez donc forcer la non-allocation dans les types
// 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 ...
};
Tous les constructeurs et opérateurs d'affectation de copie/déplacement bénéficient d'une forte garantie d'exception
Remarque : si votre type n'a pas de constructeur de déplacement, cela peut vraiment augmenter les performances (comme dans le cas std :: vector).
Exemple:
using any_printable = aa::any_with<Print, aa::move>;
basic_any_with
Identique à any_with
, mais avec une allocation personnalisée et une taille de tampon d'optimisation de petits objets - si vous avez besoin d'un basic_any_with
copiable, utilisez copy_with
template < typename Alloc, size_t SooS, TTA... Methods>
using basic_any_with = /* ... */ ;
poly_ref
Non propriétaire, toujours non nul, léger (~=void*)
poly_ref<Methods...>
implicitement convertible en un plus petit nombre de méthodes.
poly_ref<A, B, C>
est convertible en poly_ref<A, B>
, poly_ref<A>
, poly_ref<B>
... etc etc.
Cela signifie que vous pouvez ajouter dans l'interface des fonctions uniquement les méthodes dont elles ont réellement besoin. Ensuite, si vous ajoutez Method à votre type any_with
, il n'y a AUCUNE rupture 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
Identique à poly_ref
, mais peut être créé à partir de poly_ref
et const T&
aa::cref
est un alias de modèle pour aa::const_poly_ref
Remarque : ne prolonge pas la durée de vie
poly_ptr
Non propriétaire, nullable, léger (~=void*)
poly_ptr<Methods...>
implicitement convertible en un plus petit nombre de méthodes.
poly_ptr<A, B, C>
est convertible en poly_ptr<A, B>
, poly_ptr<A>
, poly_ptr<B>
... etc etc.
Cela signifie que vous pouvez ajouter dans l'interface des fonctions uniquement les méthodes dont elles ont réellement besoin. Ensuite, si vous ajoutez Method à votre type any_with
, il n'y a AUCUNE pause abi/api.
// you can invoke this function with any poly_ptr<..., A, ...>
void foo (poly_ptr<A>);
Remarque : poly_ptr
et const_poly_ptr
sont trivialement copiables, donc std::atomic<poly_ptr<...>>
fonctionne.
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
Identique à poly_ptr
, mais peut être créé à partir de poly_ptr
et const T*
/ Any*
aa::cptr
est un alias de modèle pour aa::const_poly_ptr
stateful_ref
aa::stateful::ref<Methods...>
contient une table virtuelle en elle-même.
Peut également contenir des références à des tableaux C et à des fonctions sans dégradation
Il a une interface assez simple, créant uniquement à partir de T&/poly_ref
et appelant (par aa::invoke par exemple)
Il aura des performances maximales si vous devez effacer 1-2 méthodes et n'avez pas besoin d'utiliser any_cast
.
Cas d'utilisation typique : création d'une 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
Identique à stateful::ref
, mais peut être créé à partir de const T&
et aa::cref
any_cast
nécessite la méthode aa::type_info
Objet fonctionnel avec opérateur() :
Fonctionne comme std::any_cast - vous pouvez convertir en T(copy), T&(take ref) (lance aa::bad_cast si le casting est mauvais)
Ou vous pouvez passer le pointeur (ou poly_ptr) (renvoie nullptr, si le casting est mauvais)
T* ptr = any_cast<T>(&any);
Exemple:
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
Objet fonctionnel avec Operator(), qui accepte any_with/ref/cref/stateful::ref/stateful::cref
comme premier argument, puis tous les arguments de Method et invoque Method
Si arg est const any_with
ou cref
, alors seules les méthodes const sont autorisées.
condition préalable : any.has_value() == true
Exemple:
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
Sélectionne .case en fonction du type dynamique d'argument d'entrée et appelle visitor
avec ce type dynamique ou cette fonction par défaut
Prend également en charge poly_traits
comme deuxième argument de modèle, il prend donc en charge tout type pour lequel vous avez des 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 ();
};
Exemple:
Result val = aa::type_switch<Result>(value)
.case_< float >(foo1)
.case_< bool >(foo2)
.cases< char , int , unsigned char , double >(foo3)
.default_( 15 );
visit_invoke
C'est... une résolution de surcharge d'exécution ! aa::make_visit_invoke<Foos...>
crée un objet d'ensemble de surcharge avec la méthode .resolve(Args...)
, qui effectue une résolution de surcharge basée sur les types d'exécution Args....
Resolve renvoie nullopt
si aucune fonction de ce type n'existe pour accepter les arguments d'entrée
Cet exemple est très basique, voir aussi /examples/visit_invoke_example.hpp pour en savoir plus
Exemple:
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
Adaptateur de conteneur polymorphe, qui se comporte comme Container<std::variant<Types...>>
, mais beaucoup plus efficace.
Prend en charge les opérations :
visit<Types...>(visitor)
- appelle visitor
avec toute la valeur contenue des types Types
view<T>
- renvoie la référence au conteneur de toutes les valeurs stockées de type T
Le conteneur est un std::vector
par défaut.
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...
};
Exemple:
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
Ce conteneur se comporte comme std::vector<T>
, mais stocke les champs séparément.
Opération prise en charge : view<T>
/ view<I>
pour obtenir l'étendue de tous les champs de cet index
T
doit être de type agrégé ou de type tuple
Remarque : data_parallel_vector
est une plage à accès aléatoire. Remarque : ignore la spécialisation std::vector<bool>
, se comporte comme un vecteur normal pour les booléens.
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 ();
};
Exemple:
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 );
};
Récupérer du contenu :
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