C'est une balise de compilation et tapez:
template<class T>
struct tag_t { constexpr tag_t() {}; };
template<class T> constexpr tag_t tag<T>{};
Voici une liste des types:
template<class...>struct types_t{constexpr types_t(){}; using type=types_t;};
template<class...Ts>constexpr types_t<Ts...> types{};
Cette mappe le contenu d'une liste de types:
template<template<class...>class Z, class types>
struct fmap{};
template<template<class...>class Z, class types>
using fmap_t=typename fmap<Z,types>::type;
template<template<class...>class Z, template<class...>class types, class...Ts>
struct fmap<Z,types<Ts...>> {
using type=types<Z<Ts...>>;
};
Maintenant, faisons une liste d'usines:
template<class...Args>
struct build_tagged_sig {
template<class T>
using result = std::unique_ptr<T>(tag_t<T>,Args...);
};
template<template<class...>class Out, class types, class...Args>
using tagged_factories =
fmap_t<
std::function,
fmap_t<
build_tagged_sig<Args...>::template result,
fmap_t< Out, types >
>
>;
Cela applique un types
à un modèle:
template<template<class...>class Z, class types>
struct apply_types {};
template<template<class...>class Z, class types>
using apply_types_t = typename apply_types<Z,types>::type;
template<template<class...>class Z, template<class...>class types, class...Ts>
struct apply_types<Z, types<Ts...>> {
using type=Z<Ts...>;
};
template<template<class...>class Z>
struct applier {
template<class types>
using result=apply_types_t<Z,types>;
};
This SO post shows how to overload multiple lambdas or std::function
s.
using my_types = types_t< std::int8_t, std::int16_t, std::int32_t, std::int64_t >;
using magic_factory =
apply_types_t<
overload,
tagged_factories< Base, my_types, const char* > >
>;
qui est une surcharge sur
std::function< std::unique_ptr<Base<std::int8_t>>(tag_t<Base<std::int8_t>>, const char*) >,
std::function< std::unique_ptr<Base<std::int16_t>>(tag_t<Base<std::int16_t>>, const char*) >,
std::function< std::unique_ptr<Base<std::int32_t>>(tag_t<Base<std::int32_t>>, const char*) >,
std::function< std::unique_ptr<Base<std::int64_t>>(tag_t<Base<std::int64_t>>, const char*) >
Nous écrivons maintenant une usine de registre:
template<class F, class...Ts>
magic_factory make_poly_factory(types_t<Ts...>, F&& f) {
return magic_factory(
(void(tag<Ts>), f)...
);
}
template<class F>
magic_factory make_poly_factory(F&& f) {
return make_poly_factory(my_types{}, f);
}
qui crée des copies N de f
et stocke chacun dans un std::function
en un seul objet. En prenant la valeur de retour, vous pouvez appeler un individu par une résolution de surcharge.
template<class T>
std::unique_ptr<Base<T>> factory_A_impl(tag_t<Base<T>>, const char* param) {
return new DerivedA<T>(param);
}
auto magic = magic_factory(my_types{}, [](auto tag, const char* param){
return factory_A_impl(tag, param);
});
std::unique_ptr<Base<std::int8_t>> bob = magic(tag<std::int8_t>, "hello");
et bob
est un unique_ptr
à un Base<std::int8_t>
qui est en fait un DerivedA<std::int8_t>
lors de l'exécution.
Ceci a probablement des tpyos.
La plupart de ce post était la métaprogrammation pour configurer l'objet unique qui surcharge chacun des tag_t<T0>
à tag_t<T1>
sans me répéter. Vous pouvez le faire manuellement pour vos 4 types. La surcharge que j'ai utilisée suppose que nous ne prenons pas un seul lambda avec des arguments de template et un type qui efface chaque surcharge, mais un ensemble de lambdas. Le premier ferait quelques choses au-dessus d'un contact plus facile.
L'étudiant de l'utilisateur final a juste pour créer un objet de fonction qui prend un tag_t<X>
et un const char*
et retourne un unique_ptr<X>
et ne coûte pas cher à copier, puis créer un magic_factory
de celui-ci (qui type efface en une liasse de std::function
s).
DescriptionBase devient:
struct Description {
char const* description = 0;
char const* name = 0;
magic_factory factory;
template<class T>
std::unique_ptr<Base<T>>
make(const char *param) {
return factory(tag<T>, param);
}
};
Le polymorphisme est maintenant à magic_factory
. Stockez les instances de Description
, pas de pointeurs sur elles, car elles sont un polymorphisme de type valeur.
Voir, facile. L'ajout de plusieurs paramètres de modèle ajoute simplement un peu plus de complexité aux choses fmap
, et plus de responsabilités au créateur de la magic_factory
.
Vous devez effectuer une opération inter-produits pour générer les 64 ensembles de types différents à partir de la liste de 4 éléments. Ce sera un types_t
de types_t
.
Appelez cela my_many_types
.
Puis
using magic_factory =
apply_types_t<
overload,
tagged_factories< applier<Base>::template result, my_types, const char* > >
>;
et fait, nous avons maintenant 64 avec des signatures comme les surcharges:
std::unique_ptr<Base<std::int8_t, std::int16_t, std::int8_t>>(
tag_t<Base<std::int8_t, std::int16_t, std::int8_t>>,
const char*
)
Maintenant, nous pourrions tout simplement faire tout cela manuellement.
Construire une table comme ceci:
template<class T>
using factory_ptr = std::unique_ptr<T>(void*, tag_t<T>, const char*);
using factory_table = std::tuple<
factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int16_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int32_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int64_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int8_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int16_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int32_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int64_t > >,
...
factory_ptr< Base< std::int64_t, std::int64_t, std::int64_t > >
>;
maintenant une usine magique:
struct magic_factory {
std::unique_ptr<void, void(*)(void*)> state;
factory_table table;
template<class T0, class T1, class T2>
std::unique_ptr<Base<T0, T1, T2>> make(char const* param) {
auto f = std::get<factory_ptr< Base< T0, T1, T2 > >>(table);
return f(state.get(), param);
}
magic_factory(magic_factory&&)=default;
template<class T,
class=std::enable_if_t<!std::is_same<std::decay_t<T>, magic_factory>::value>
>
magic_factory(T&& t) {
ptr = {new std::decay_t<T> >(std::forward<T>(t)),
[](void* ptr){
delete static_cast< std::decay_t<T>* >(ptr);
}
};
// 64 lines like this:
std::get<factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >>(table)
= +[](void* pvoid, tag_t<Base< std::int8_t, std::int8_t, std::int8_t >> tag, char const* param)->std::unique_ptr<Base< std::int8_t, std::int8_t, std::int8_t >>
{
auto*pt = static_cast<std::decay_t<T>*>(pvoid);
return (pt)(tag, param);
};
}
};
où nous construisons une tuple
de fonctions de type effacement et un pointeur vide à son argument et nous répartir nous-mêmes.
Vous pouvez également utiliser certaines des machines ci-dessus pour automatiser cela. La table manuelle donne de l'efficacité, car nous ne dupliquons pas l'état de l'objet appelable N fois comme avec la version std::function
. Une autre approche consiste à utiliser une solution my type erasing type erasure.
Nous écrivons un ensemble de modèles any_method
s qui font le tour de tag pour créer l'objet Base<A,B,C>
. Nous créons ensuite un super_any
sur chacun des any_method
s.
Puis nous héritons de cela et enveloppons votre make
pour expédier à ceux any_method
s.
Cela pourrait être à peu près aussi efficace que l'approche manuelle juste au-dessus.
template<class T0, class T1, class T2>
auto make_base = make_any_method<std::unique_ptr<Base<T0, T1, T2>>(char const* name)>(
[](auto* p, char const* name)
{
return p->make<T0, T1, T2>(name);
}
);
template<class T0, class T1, class T2>
using base_maker = decltype(make_base);
Maintenant, notre usine est:
super_any< base_maker<std::int8_t, std::int8_t, std::int8_t > > bob;
bob
peut stocker un pointeur vers une classe qui implémente make<T0, T1, T2>
où ils correspondent int8_t, int8_t, int8_t
.
Ajouter 64 lignes supplémentaires et terminé.
Les types bob
magasins n'ont pas besoin d'avoir une classe de base commune du tout. Il n'utilise pas l'héritage C++ pour implémenter le polymorphisme, mais plutôt l'effacement manuel.
Pour ce que vous appelez simplement
auto r = (bob->*make_base<std::int8_t, std::int8_t, std::int8_t>)("hello");
passer naturellement plus de types super_any
et il suppports plusieurs types de make_base
.
Si vous ne connaissez pas le type de la valeur de retour, vous ne pouvez pas utiliser le constructeur de manière judicieuse, donc ils doivent avoir un type de retour commun (donc vous pouvez réellement l'affecter à quelque chose) ou ils doivent être usines nommées séparément. Le système de type ne peut pas savoir quel type est un "fred" ou un "george", mais il doit déterminer au moment de la compilation si l'appel est valide. Il est important de se rappeler que Base et Base ne sont aucunement liés au point de vue du système de type. –
xaxxon
Je n'ai pas lu votre code mais vous pouvez le faire en ayant une carte de chaînes pour les foncteurs qui retournent un objet correspondant à une interface. Les classes s'enregistrent en insérant un foncteur dans cette carte. – imreal
@xaxxon J'ai modifié le code pour être plus clair. "fred" et "george" étaient des paramètres pour le constructeur, pas les noms de type. Base et base ne sont pas destinés à être liés. Je suis resté bloqué au moment de construire le 'descriptionList' dans la bibliothèque (qui, bien sûr, ne sait pas quels paramètres de modèle les utilisateurs choisiront). –
Zack