Un moyen facile d'utiliser des variables de types enum sous forme de chaîne en C?

87

Voici ce que j'essaye de faire:

typedef enum { ONE, TWO, THREE } Numbers;

J'essaie d'écrire une fonction qui ferait un cas de commutation similaire à ce qui suit:

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

Au lieu de définir à chaque cas, existe-t-il un moyen de le définir en utilisant la variable enum comme j'essaie de le faire ci-dessus?

zxcv
la source

Réponses:

14

Il n'y a pas de solution intégrée. Le moyen le plus simple consiste à utiliser un tableau char*où la valeur int de l'énumération est indexée sur une chaîne contenant le nom descriptif de cette énumération. Si vous avez unenum (celui qui ne commence pas à 0 ou qui présente des lacunes dans la numérotation) où certains des intmappages sont suffisamment élevés pour rendre un mappage basé sur un tableau impraticable, vous pouvez utiliser une table de hachage à la place.

sk.
la source
En développant là-dessus, s'il s'agit effectivement d'une liste à incrémentation linéaire, vous pouvez simplement utiliser l'outil de macro de votre éditeur pour enregistrer et résoudre chacun des noms en une chaîne. Peu de frappe supplémentaire est nécessaire et vous évitez d'avoir à définir en premier lieu. Je clique sur Enregistrer sur la dernière des macros copiées, ajoute un devis après et passe au même endroit sur la ligne suivante. Je pousse stop. J'appuie sur run X fois et j'en fais autant qu'il y en a (ou juste une seule étape). Je peux ensuite l'envelopper dans un tableau de chaînes.
user2262111
70

La technique de faire quelque chose à la fois un identifiant C et une chaîne? peut être utilisé ici.

Comme d'habitude avec de tels trucs de préprocesseur, écrire et comprendre la partie préprocesseur peut être difficile, et comprend le passage de macros à d'autres macros et implique l'utilisation des opérateurs # et ##, mais son utilisation est vraiment facile. Je trouve ce style très utile pour les énumérations longues, où maintenir deux fois la même liste peut être vraiment gênant.

Code d'usine - tapé une seule fois, généralement caché dans l'en-tête:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Usine utilisée

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

La technique peut être facilement étendue afin que les macros XX acceptent plus d'arguments, et vous pouvez également avoir préparé plus de macros pour remplacer XX pour des besoins différents, similaires aux trois que j'ai fournis dans cet exemple.

Comparaison avec les X-Macros utilisant #include / #define / #undef

Bien que cela soit similaire aux X-Macros que d'autres ont mentionnés, je pense que cette solution est plus élégante en ce qu'elle ne nécessite rien #undefing, ce qui vous permet de cacher plus de choses compliquées dans l'usine le fichier d'en-tête - le fichier d'en-tête est quelque chose que vous ne touchez pas du tout lorsque vous devez définir une nouvelle énumération, donc la nouvelle définition d'énumération est beaucoup plus courte et plus propre.

Suma
la source
2
Je ne sais pas comment vous pouvez dire que c'est mieux / pire que les x-macros - ce sont des x-macros. Le SOME_ENUM(XX)est exactement une X-macro (pour être précis, le "formulaire utilisateur" qui passe la XXfonction plutôt que d'utiliser #def #undef) et ensuite à son tour le X-MACRO entier est ensuite passé à DEFINE_ENUM qui l'utilise. Ne rien enlever à la solution - cela fonctionne bien. Juste pour clarifier qu'il s'agit d'une utilisation de macros X.
BeeOnRope
1
@BeeOnRope La différence que vous notez est significative et distingue cette solution des macros X idiomatiques (comme les exemples de Wikipedia ). L'avantage du dépassement de XXla réing #defineest que l'ancien modèle peut être utilisé dans les extensions macro. Notez que les seules autres solutions aussi concises que celle-ci nécessitent toutes la création et l'inclusion multiple d'un fichier séparé pour définir une nouvelle énumération.
pmttavara
1
Une autre astuce consiste à utiliser le nom enum comme nom de macro. Vous pouvez simplement écrire #define DEFINE_ENUM(EnumType) ..., remplacer ENUM_DEF(...)par EnumType(...)et faire dire à l'utilisateur #define SomeEnum(XX) .... Le préprocesseur C se développera contextuellement SomeEnumdans l'appel de la macro lorsqu'il est suivi de parenthèses et dans un jeton régulier dans le cas contraire. (Bien sûr, cela pose des problèmes si l'utilisateur aime utiliser SomeEnum(2)pour lancer le type enum plutôt que (SomeEnum)2ou static_cast<SomeEnum>(2).)
pmttavara
1
@pmttavara - bien sûr, si une recherche rapide est une indication, l'utilisation la plus courante des x-macros utilise un nom de macro interne fixe avec #defineet #undef. Êtes-vous en désaccord avec le fait que le «formulaire utilisateur» (suggéré par exemple au bas de cet article ) est un type de x-macro? Je l'ai certainement toujours appelé une x-macro et dans les bases de code C dans lesquelles j'ai été récemment, c'est la forme la plus courante (c'est évidemment une observation biaisée). J'ai peut-être mal analysé l'OP.
BeeOnRope
2
@BeeOnRope Le libellé actuel est le résultat d'une modification, comme vous m'aviez convaincu à l'époque qu'il s'agissait d'une x-macro, même si c'était peut-être une forme moins utilisée (ou au moins une moins mentionnée dans les articles) à l'époque.
Suma
62
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever
Bill Forster
la source
3
C'est le genre de chose pour laquelle cpp a été conçu. +1.
Derrick Turk
5
C'est une bonne réponse, cela semble être le meilleur que l'on puisse faire sans utiliser d'outils spéciaux, et j'ai déjà fait ce genre de chose; mais je ne me sens toujours jamais vraiment `` bien '' et je n'aime jamais vraiment le faire ...
Michael Burr
Petit changement: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];dans le defs.hfichier - cela déclarera votre table de noms dans les fichiers que vous l'utilisez. (Je ne peux pas trouver un bon moyen de déclarer la taille de la table, cependant.) De plus, personnellement, je laisserais le point-virgule final, mais les mérites sont discutables de toute façon.
Chris Lutz
1
@Bill, pourquoi s'embêter avec typdans la ligne #define ENUM_END(typ) };?
Pacerier
Cela ne fonctionne pas là où je veux que ma macro soit définie comme "ONE = 5"
UKMonkey
13

Il existe certainement un moyen de le faire - utilisez les macros X () . Ces macros utilisent le préprocesseur C pour construire des énumérations, des tableaux et des blocs de code à partir d'une liste de données source. Il vous suffit d'ajouter de nouveaux éléments à la #define contenant la macro X (). L'instruction switch se développerait automatiquement.

Votre exemple peut s'écrire comme suit:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

Il existe des moyens plus efficaces (c'est-à-dire utiliser des macros X pour créer un tableau de chaînes et un index enum), mais c'est la démonstration la plus simple.

JayG
la source
8

Je sais que vous avez quelques bonnes réponses solides, mais connaissez-vous l'opérateur # dans le préprocesseur C?

Il vous permet de faire ceci:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}
socle
la source
char const *kConstStr[]
Anne van Rossum
6

C ou C ++ ne fournit pas cette fonctionnalité, même si j'en ai souvent besoin.

Le code suivant fonctionne, bien qu'il soit mieux adapté aux énumérations non éparses.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Par non clairsemé, je veux dire pas de la forme

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

car cela comporte d'énormes lacunes.

L'avantage de cette méthode est qu'elle rapproche les définitions des énumérations et des chaînes; avoir une instruction switch dans une fonction les lance. Cela signifie que vous êtes moins susceptible de changer l'un sans l'autre.

paxdiablo
la source
6

BAISER. Vous ferez toutes sortes d'autres choses avec vos enums, alors pourquoi l'impression devrait-elle être différente? Oublier un cas dans votre routine d'impression n'est pas un problème si l'on considère qu'il y a environ 100 autres endroits où vous pouvez oublier un cas. Compilez simplement -Wall, qui avertira des correspondances de cas non exhaustives. N'utilisez pas "default" car cela rendra le commutateur exhaustif et vous n'obtiendrez pas d'avertissements. Au lieu de cela, laissez le commutateur se fermer et gérez le cas par défaut comme ceci ...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}
Samuel Danielson
la source
4

L'utilisation de boost :: preprocessor rend possible une solution élégante comme la suivante:

Étape 1: inclure le fichier d'en-tête:

#include "EnumUtilities.h"

Étape 2: déclarez l'objet d'énumération avec la syntaxe suivante:

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

Étape 3: utilisez vos données:

Obtenir le nombre d'éléments:

td::cout << "Number of Elements: " << TestDataCount << std::endl;

Obtention de la chaîne associée:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

Obtenir la valeur enum de la chaîne associée:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

Cela semble propre et compact, sans fichiers supplémentaires à inclure. Le code que j'ai écrit dans EnumUtilities.h est le suivant:

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

Il y a quelques limitations, à savoir celles de boost :: preprocessor. Dans ce cas, la liste des constantes ne peut pas dépasser 64 éléments.

En suivant la même logique, vous pouvez également penser à créer une énumération éparse:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

Dans ce cas, la syntaxe est:

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

L'utilisation est similaire à celle ci-dessus (moins la fonction eName ## 2Enum, que vous pourriez essayer d'extrapoler à partir de la syntaxe précédente).

Je l'ai testé sur mac et linux, mais sachez que boost :: preprocessor n'est peut-être pas entièrement portable.

Giacomo M.
la source
3

En fusionnant certaines des techniques ici, j'ai trouvé la forme la plus simple:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}
Juan Gonzalez Burgos
la source
2

Si vous utilisez gcc, il est possible d'utiliser:

const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};

Alors appelez simplement par exemple

enum_to_string_map[enum1]
Janisj
la source
1

Découvrez les idées dans Mu Dynamics Research Labs - Blog Archive . J'ai trouvé cela plus tôt cette année - j'oublie le contexte exact où je l'ai rencontré - et je l'ai adapté dans ce code. Nous pouvons débattre de l'intérêt d'ajouter un E à l'avant; il est applicable au problème spécifique traité, mais ne fait pas partie d'une solution générale. Je l'ai caché dans mon dossier «vignettes» - où je garde des bribes de code intéressantes au cas où je les voudrais plus tard. J'ai honte de dire que je n'ai pas gardé une note de l'origine de cette idée à l'époque.

En-tête: paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

Exemple de source:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

Ce n'est pas nécessairement l'utilisation la plus propre au monde du préprocesseur C - mais cela empêche l'écriture du matériel plusieurs fois.

Jonathan Leffler
la source
0

Si l'index enum est basé sur 0, vous pouvez placer les noms dans un tableau de char * et les indexer avec la valeur enum.

Colen
la source
0

J'ai créé une classe simple basé sur un modèle streamable_enumque les utilisations flux opérateurs <<et >>et est basé sur le std::map<Enum, std::string>:

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

#include <iostream>
#include <string>
#include <map>

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

Usage:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}
Robert Husák
la source
0

Voici une solution utilisant des macros avec les fonctionnalités suivantes:

  1. n'écrivez chaque valeur de l'énumération qu'une seule fois, il n'y a donc pas de double liste à maintenir

  2. ne gardez pas les valeurs d'énumération dans un fichier séparé qui sera plus tard #inclus, afin que je puisse l'écrire où je veux

  3. ne remplacez pas l'énumération elle-même, je veux toujours que le type enum soit défini, mais en plus de cela, je veux pouvoir mapper chaque nom enum à la chaîne correspondante (pour ne pas affecter le code hérité)

  4. la recherche doit être rapide, donc de préférence pas de boîtier de commutation, pour ces énormes énumérations

https://stackoverflow.com/a/20134475/1812866

muqker
la source
0

Je pensais qu'une solution comme Boost.Fusion one pour adapter les structures et les classes serait bien, ils l'ont même eue à un moment donné, d'utiliser les énumérations comme une séquence de fusion.

J'ai donc fait quelques petites macros pour générer le code pour imprimer les énumérations. Ce n'est pas parfait et n'a rien à voir avec le code standard généré par Boost.Fusion, mais peut être utilisé comme les macros Boost Fusion. Je veux vraiment faire générer les types nécessaires à Boost.Fusion à intégrer dans cette infrastructure qui permet d'imprimer les noms des membres de la structure, mais cela se produira plus tard, pour l'instant ce ne sont que des macros:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

L'ancienne réponse ci-dessous est plutôt mauvaise, veuillez ne pas l'utiliser. :)

Ancienne réponse:

J'ai cherché un moyen de résoudre ce problème sans trop changer la syntaxe de la déclaration enums. Je suis venu à une solution qui utilise le préprocesseur pour récupérer une chaîne à partir d'une déclaration d'énumération stringifiée.

Je suis capable de définir des énumérations non éparses comme ceci:

SMART_ENUM(State, 
    enum State {
        RUNNING,
        SLEEPING, 
        FAULT, 
        UNKNOWN
    })

Et je peux interagir avec eux de différentes manières:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

Basé sur les définitions suivantes:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

Quand j'aurai besoin de support pour les enum clairsemés et quand j'aurai plus de temps , j'améliorerai les implémentations to_string et from_string avec boost :: xpressive, mais cela coûtera en temps de compilation en raison de l'important templating effectué et l'exécutable généré est susceptible d'être vraiment plus grand. Mais cela a l'avantage qu'il sera plus lisible et maintenable que ce vilain code de manipulation manuelle de chaînes.:RÉ

Sinon, j'ai toujours utilisé boost :: bimap pour effectuer de tels mappages entre la valeur enums et la chaîne, mais il doit être maintenu manuellement.

daminetreg
la source
0

Parce que je préfère ne pas utiliser de macros pour toutes les raisons habituelles, j'ai utilisé une solution de macro plus limitée qui a l'avantage de garder la macro de déclaration enum libre. Les inconvénients comprennent le fait de devoir copier-coller la définition de macro pour chaque énumération et d'ajouter explicitement un appel de macro lors de l'ajout de valeurs à l'énumération.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
Gérardw
la source