Having:
Mapper<A, B> mapper( { {A::FirstA, B::SecondB}, {A::SecondA, B::FirstB} }, {A::InvalidA, B::InvalidB} // this is for conversions, where no mapping is specified );
Comment activer quelque chose comme ça?
B b = mapper[A::FirstA]; A a = mapper[B::SecondB];
Une solution possible est de créer un Mapper classe de modèle, qui permet de spécifier le mappage via la liste d'initialisation dans le constructeur, quelque chose comme:
enum class A : int { FirstA, SecondA, InvalidB }; enum class B : int { FirstB, SecondB, InvalidB };
Mais en interne, cela nécessitera des compromis - soit deux cartes (de A à B
et de B
à A
) ou une carte, par exemple de A
à B
et une recherche linéaire des conversions de B
à A
).
Est-il possible d'implémenter cela dans le standard C ++ 14
afin que:
Avec les exigences, que:
A
ne sont pas mappées aux mêmes valeurs sous-jacentes de B
A
et B
peuvent différer ?
3 Réponses :
Vous pouvez le faire assez facilement en utilisant des modèles de fonctions et une spécialisation complète. Vous faites en sorte que le modèle principal renvoie la casse non valide, puis les spécialisations renverront les mappages que vous voulez.
Si vous avez
MAKE_ENUM_MAP(A, A::InvalidA, B, B::InvalidB)
, vous pouvez ajouter tous les mappages des valeurs comme
#define MAKE_ENUM_MAP(from, from_value, to, to_value) \ template<from> \ auto mapper() { return to_value; } \ template<to> \ auto mapper() { return from_value; }
et ensuite vous l'appelleriez comme
MAKE_ENUM_MAP(A, B) ADD_MAPPING(A::FirstA, B::SecondB) ADD_MAPPING(A::SecondA, B::FirstB)
Cela ne vous laisse aucun conteneur. Vous pouvez même créer des macros pour rendre cela plus facile comme
#define MAKE_ENUM_MAP(from, to) \ template<from> \ auto mapper() { return to::Invalid; } \ template<to> \ auto mapper() { return from::Invalid; } #define ADD_MAPPING(from_value, to_value) \ template<> \ auto mapper<from_value>() { return to_value; } \ template<> \ auto mapper<to_value>() { return from_value; }
, puis vous les utiliserez comme
B b = mapper<A::FirstA>(); A a = mapper<B::SecondB>();
pour générer tout le code pour vous. La version ci-dessus utilise une valeur d'énumération unique de Invalid
pour la casse non valide des mappages. Si vous ne le souhaitez pas, vous pouvez ajouter à la macro les valeurs from
et to
à utiliser pour les mappages non valides comme
template<> B mapper<A::FirstA>() { return B::SecondB; } template<> B mapper<A::SecondA>() { return B::FirstB; } template<> A mapper<B::FirstB>() { return A::SecondA; } template<> A mapper<B::SecondB>() { return A::FirstA; }
et vous l'appelleriez comme
template<A> B mapper() { return B::InvalidB; } template<B> A mapper() { return A::InvalidA; }
La solution de Nathan est difficile à battre en termes d'élégance de mise en œuvre. Mais si vous avez désespérément besoin d'une solution qui ne repose pas sur des macros ou qui peut également être utilisée au moment de l'exécution, en voici une où vous spécifiez le mappage dans une simple liste de paires.
Au cœur de celle-ci, nous sommes en utilisant le fait que les deux énumérations doivent avoir des valeurs intégrales sous-jacentes contiguës (commençant à zéro), ce qui signifie que nous pouvons représenter les mappages dans les deux directions comme de simples tableaux. Tout cela est constexpr
donc aucune surcharge dans le cas de la compilation. Pour une utilisation au moment de l'exécution, cela stocke les informations deux fois pour permettre une recherche instantanée, mais ne prend que le stockage N (sizeof (A) + sizeof (B))
. Je ne connais aucune structure de données qui fasse mieux (c'est-à-dire ne stocke aucune donnée supplémentaire au-delà de l'un des deux tableaux et est meilleure que la recherche linéaire dans les deux sens). Notez que cela prend le même stockage que le stockage des paires elles-mêmes (mais ne gagne rien de la bijectivité du mappage).
using MyMappingList = PairList< MyMappingPair<A::A1, B::B2>, MyMappingPair<A::A2, B::B3>, MyMappingPair<A::A3, B::B4>, MyMappingPair<A::A4, B::B1> >; auto mapper = makeMapper<A, B>(MyMappingList{});
(Cela utilise des expressions de pliage + un opérateur virgule pour attribuer des valeurs aux éléments du tableau, voir par exemple ici a>).
Le code utilisateur fournit une liste de paires de mappages à utiliser et c'est fait:
template<class TA, class TB, class ... Pairs> struct Mapper { constexpr static std::array<TA, sizeof...(Pairs)> generateAIndices() { std::array<TA, sizeof...(Pairs)> ret{}; ((void)((ret[static_cast<std::size_t>(Pairs::tb)] = Pairs::ta), 0), ...); return ret; } constexpr static std::array<TB, sizeof...(Pairs)> generateBIndices() { std::array<TB, sizeof...(Pairs)> ret{}; ((void)((ret[static_cast<std::size_t>(Pairs::ta)] = Pairs::tb), 0), ...); return ret; } constexpr TB operator[](TA ta) { return toB[static_cast<std::size_t>(ta)]; } constexpr TA operator[](TB tb) { return toA[static_cast<std::size_t>(tb)]; } static constexpr std::array<TA, sizeof...(Pairs)> toA = generateAIndices(); static constexpr std::array<TB, sizeof...(Pairs)> toB = generateBIndices(); };
Démo comprenant des cas de test complets au moment de la compilation et un code d'exécution extrêmement efficace (littéralement juste mov
).
Voici une version précédente qui fonctionne uniquement à la compilation (voir aussi l'historique des révisions): https://godbolt.org/z/GCkAhn
@Hiroki Ne vous inquiétez pas, j'ai d'abord eu un autre et je l'ai supprimé pendant que je travaillais sur le même que vous.
@Hiroki Cependant, j'ai l'impression qu'en utilisant une supercherie xor trop intelligente, on pourrait être en mesure de réduire de moitié l'espace de stockage requis (pensez à une liste xor-doublement liée). Vous avez peut-être une idée?
@ MaxLanghof Ah je comprends. Vous êtes intelligent à ce stade :).
Si vous devez effectuer la recherche au moment de l'exécution, la méthode suivante fonctionnerait avec la complexité O (1) dans les deux sens.
Étant donné que tous vos énumérateurs de A
et B
ne sont pas initialisés, le premier énumérateur a la valeur zéro, le second a la valeur 1
, et ainsi de suite.
En considérant ces entiers à zéro comme des indices de tableaux, nous pouvons construire une carte bidirectionnelle en utilisant deux tableaux.
Par exemple, en supposant que le mappage actuel est
// compile-time construction. constexpr biEnumMap<3> mapper({{ {A::FirstA , B::SecondB }, {A::SecondA , B::FirstB }, {A::InvalidA, B::InvalidB} }}); // compile-time tests, A to B. static_assert(mapper(A::FirstA ) == B::SecondB ); static_assert(mapper(A::SecondA ) == B::FirstB ); static_assert(mapper(A::InvalidA) == B::InvalidB); // compile-time tests, B to A. static_assert(mapper(B::FirstB ) == A::SecondA ); static_assert(mapper(B::SecondB ) == A::FirstA ); static_assert(mapper(B::InvalidB) == A::InvalidA); // run-time tests, A to B. std::vector<A> vA = {A::FirstA, A::SecondA, A::InvalidA}; assert(mapper(vA[0]) == B::SecondB ); assert(mapper(vA[1]) == B::FirstB ); assert(mapper(vA[2]) == B::InvalidB); // run-time tests, B to A. std::vector<B> vB = {B::FirstB, B::SecondB, B::InvalidB}; assert(mapper(vB[0]) == A::SecondA ); assert(mapper(vB[1]) == A::FirstA ); assert(mapper(vB[2]) == A::InvalidA);
, définissons les deux tableaux suivants
template<std::size_t N> class biENumMap { A arrA[N]; B arrB[N]; public: constexpr biENumMap(const std::array<std::pair<A,B>, N>& init) : arrA(), arrB() { for(std::size_t i = 0; i < N; ++i) { const auto& p = init[i]; arrA[static_cast<std::size_t>(p.second)] = p.first; arrB[static_cast<std::size_t>(p.first) ] = p.second; } } constexpr A operator()(B b) const{ return arrA[static_cast<std::size_t>(b)]; } constexpr B operator()(A a) const{ return arrB[static_cast<std::size_t>(a)]; } };
où arrA [i]
est l'énumérateur de A
correspondant à i
-ième énumérateur de B
, et vice versa.
Dans cette configuration, nous pouvons effectuer une recherche de A a
à B
comme arrB [std :: size (a)]
, et vice versa , avec la complexité O (1).
La classe suivante biENumMap
est un exemple d'implémentation de la méthode bidirectionnelle ci-dessus avec C ++ 14 et plus.
Veuillez noter que puisque le étendu constexpr est disponible à partir de C ++ 14 , ici le ctor peut également être une expression constante.
Deux surcharges operator ()
sont des fonctions de recherche de A
et B
, respectivement.
Celles-ci peuvent également être des expressions constantes et cette classe nous permet d'effectuer une recherche bidirectionnelle à la fois à la compilation et à l'exécution:
A arrA[2] = {A::SecondA, A::FirstA}, B arrB[2] = {B::SecondB, B::FirstB},
Nous pouvons utiliser cette classe comme suit: p>
A::FirstA (=0) <--> B::SecondB (=1), A::SecondA (=1) <--> B::FirstB (=0),
Pourquoi devez-vous éviter d'utiliser deux cartes? Cela ne consomme que 2 fois plus de mémoire qu'une carte, ce qui n'est pas beaucoup supposé qu'ils ont une valeur enum (ont la taille d'environ un int), et surtout vous ne vous souciez pas trop de l'implémentation interne?
La carte est-elle connue en temps constant?
@ user202729, c'est dans le domaine intégré, et nécessaire pour le mappage non seulement entre deux énumérations, mais plus encore, donc avoir le
2 * [mappings_for_one_enum * 2 * int_size]
est pertinent.@ user202729 si par
temps constant
vous voulez direcompilation
, alors oui.La valeur entière des énumérations est-elle garantie d'être consécutive?
@Timo, eh bien, c'est la solution que j'ai également proposée dans la question, mais elle comporte des mises en garde.
Peut-être boost :: bimap
@ user202729, oui, peut également être garanti que les valeurs commencent à
0
- les énumérations sont générées à partir du document de spécification, il est donc possible de s'en assurer.@NathanOliver, je suis également tombé sur
boost :: bimap
lors de la recherche d'une solution en premier, mais malheureusement, boost n'est pas une option, devrait se faire au moyen du C ++ standard.Si les deux énumérations ont la même valeur, vous pouvez simplement les lancer entre elles
Dans ce cas, vous pouvez soit utiliser une métaprogrammation de modèle pour que le mappeur consomme de la mémoire O (1), soit une recherche de tableau (ils consommeront de la mémoire O (N) où N = nombre d'éléments d'énumération) Dans les deux cas, la recherche O (1) .
@AlanBirtles, non, ce n'est pas une carte d'identité.
Veuillez alors fournir un exemple minimal reproductible , votre exemple peut être implémenté avec un cast
Je suis tenté de fermer pour dupliquer avec stackoverflow.com/questions/21760343/...
@AlanBirtles, mis à jour, devrait être exhaustif maintenant.
@Pixelchemist Dans ce cas, les valeurs sont des énumérations dans une plage et sont connues en temps constant, donc une solution plus efficace existe.
@Pixelchemist, a manqué cette autre question lors de la recherche, mais bien que la réponse soit acceptée, elle ne résout pas le problème de la double carte, bien qu'elle améliore la solution originale dans l'autre article ...
Pour être parfaitement clair: la recherche au moment de la compilation est-elle suffisante? Ou est-il nécessaire que le mappage puisse également être spécifié au moment de l'exécution?
@MaxLanghof, a mis à jour la question (mentionnée dans le commentaire plus tôt) - le mappage est connu au moment de la compilation.