Comment convertir une variable de type enum en chaîne?


128

Comment faire printf pour afficher les valeurs des variables qui sont de type enum? Par exemple:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

et ce dont j'ai besoin est quelque chose comme

printenum(OS_type, "My OS is %s", myOS);

qui doit afficher une chaîne "Linux", pas un entier.

Je suppose que je dois d'abord créer un tableau de chaînes indexées sur la valeur. Mais je ne sais pas si c'est la plus belle façon de le faire. Est-ce possible du tout?


2
Faites votre choix: ici , ici , ici . C'est un gâchis, ce sont tous des semi-doublons.
rubenvb

Un autre , celui-ci est plus complet.
bit2shift


Tant de réponses encore pas de "enum class"
Vivick

Réponses:


70

Il n'y a vraiment pas de belle façon de faire cela. Configurez simplement un tableau de chaînes indexées par l'énumération.

Si vous faites beaucoup de sortie, vous pouvez définir un opérateur << qui prend un paramètre d'énumération et effectue la recherche à votre place.


2
Vous pouvez également vérifier au moment de la compilation que votre tableau contient le nombre attendu de chaînes.
markh44

2
Je sais que je suis dans la grande minorité avec cela, mais pour les programmeurs comme moi qui ne souhaitent pas s'appuyer sur d'énormes bibliothèques tierces et / ou du code marco-criblé pour résoudre les lacunes natives de la langue, je trouve que c'est la solution la plus simple et la plus pure pour la norme d'aujourd'hui. +1
Syndog

13
@Syndog Ensuite, la longue énumération des 56 énumérateurs dans votre code de production est mise à jour par ce programmeur sous beaucoup de pression pour libérer une fonctionnalité en retard, et il oublie de mettre à jour ce tableau indexé enum. Cela passe inaperçu, car la fonction d'impression associée n'est utilisée que par le code de débogage de l'application. 2 mois plus tard, vous êtes le premier à exécuter réellement ce code de débogage: il vous donne alors les mauvaises informations, vous perdez donc une demi-journée d'hypothèses de construction basées sur ces mauvaises informations, avant de réaliser que vous deviez d'abord déboguer le code de débogage: le la conception repose sur une duplication explicite.
Annonce N

1
@AdN Cette conception est erronée. Le mappage d'énumération à une chaîne lisible par l'homme ne doit pas être implémenté en tant que tableau de chaînes indexées par la valeur enum. Votre expérience montre (vraisemblablement) pourquoi. Le mappage doit être un tableau explicite de paires (enum, string), donc si vous oubliez d'ajouter une entrée pour votre nouvelle valeur enum, vous obtenez "???" en sortie, mais au moins cela ne va pas bousiller les noms de toutes vos autres énumérations.
brewbuck

8
@AdN votre scénario est la raison pour laquelle je préfère une fonction contenant un commutateur (sans clause par défaut) plutôt qu'un tableau, et pour définir les commutateurs du compilateur dans le fichier de construction pour émettre une erreur pour un basculement sur une énumération qui ne couvre pas tout valeurs possibles. L'ajout d'une nouvelle entrée d'énumération sans mettre à jour les instructions de commutateur appropriées provoquera une erreur de compilation.
divegeek

131

La solution naïve, bien sûr, est d'écrire une fonction pour chaque énumération qui effectue la conversion en chaîne:

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

Ceci, cependant, est un désastre de maintenance. Avec l'aide de la bibliothèque Boost.Preprocessor, qui peut être utilisée avec du code C et C ++, vous pouvez facilement tirer parti du préprocesseur et le laisser générer cette fonction pour vous. La macro de génération est la suivante:

#include <boost/preprocessor.hpp>

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

La première macro (commençant par X_) est utilisée en interne par la seconde. La deuxième macro génère d'abord l'énumération, puis génère une ToStringfonction qui prend un objet de ce type et renvoie le nom de l'énumérateur sous forme de chaîne (cette implémentation, pour des raisons évidentes, nécessite que les énumérateurs mappent sur des valeurs uniques).

En C ++, vous pouvez implémenter la ToStringfonction comme une operator<<surcharge à la place, mais je pense que c'est un peu plus simple d'exiger un " ToString" explicite pour convertir la valeur en chaîne.

À titre d'exemple d'utilisation, votre OS_typeénumération serait définie comme suit:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

Alors que la macro semble au premier abord être beaucoup de travail et que la définition de OS_typesemble plutôt étrangère, rappelez-vous que vous devez écrire la macro une fois, puis vous pouvez l'utiliser pour chaque énumération. Vous pouvez y ajouter des fonctionnalités supplémentaires (par exemple, une conversion sous forme de chaîne en enum) sans trop de problèmes, et cela résout complètement le problème de maintenance, puisque vous ne devez fournir les noms qu'une seule fois, lorsque vous appelez la macro.

L'énumération peut alors être utilisée comme si elle était définie normalement:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

Les extraits de code de cet article, en commençant par la #include <boost/preprocessor.hpp>ligne, peuvent être compilés tels que publiés pour illustrer la solution.

Cette solution particulière est pour C ++ car elle utilise une syntaxe spécifique à C ++ (par exemple, non typedef enum) et une surcharge de fonctions, mais il serait simple de faire fonctionner cela aussi avec C.


7
+1, La machine de mise en œuvre est effrayante mais l'interface finale est difficile à battre pour l'élégance.
deft_code

4
Existe-t-il de toute façon pour que cela vous permette également de donner les valeurs entières enum. Par exemple, Windows serait 3, Linux 5 et Apple 7?
Marquez

4
Oui, vous pouvez changer (Windows)en (Windows, 3)puis remplacer le BOOST_PP_SEQ_ENUMpar un BOOST_PP_SEQ_FOR_EACH. Je n'ai pas d'exemple à portée de main, mais je peux en écrire un si vous le souhaitez.
James McNellis

2
@JamesMcNellis J'aimerais vraiment un exemple de code qui accomplit ce que Mark a demandé, auriez-vous l'amabilité de nous montrer le chemin? :)
Omer Raviv

2
REMARQUE: le préprocesseur boost a une limite stricte de 256 éléments. Pour les énumérations plus importantes, une solution différente est nécessaire.
dshin

32

Ceci est le bloc pré-processeur

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

Définition enum

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
} END_ENUM(Os_type)

Appeler en utilisant

GetStringOs_type(winblows);

Pris d' ici . À quel point cela est cool ? :)


1
C'est la seule solution qui fonctionne lorsque votre énumération contient plus de 256 éléments.
dshin

8

Utilisez std::map<OS_type, std::string>et remplissez-le avec enum comme clé et une représentation sous forme de chaîne comme valeurs, vous pouvez alors faire ceci:

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;

7

Le problème avec les énumérations C est que ce n'est pas un type qui lui est propre, comme c'est le cas en C ++. Une énumération en C est un moyen de mapper des identificateurs à des valeurs intégrales. Juste ça. C'est pourquoi une valeur enum est interchangeable avec des valeurs entières.

Comme vous le devinez correctement, un bon moyen est de créer un mappage entre la valeur enum et une chaîne. Par exemple:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};

J'ai supposé - apparemment incorrectement - que le langage de programmation était limité à C.
Andrew

1
vous êtes un peu décalé, enum sont des types en C. Les constantes de type énumération intégrale sont de type intet non du enumtype par lequel elles sont définies, c'est peut-être ce que vous vouliez dire. Mais je ne vois pas du tout ce que cela a à voir avec la question.
Jens Gustedt

7

J'ai combiné les solutions de James , Howard et Éder et créé une implémentation plus générique:

  • La valeur int et la représentation sous forme de chaîne personnalisée peuvent être définies en option pour chaque élément enum
  • "enum class" est utilisé

Le code complet est écrit ci-dessous (utilisez "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" pour définir une énumération) ( démo en ligne ).

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}

C'est la meilleure réponse à ce jour
Arnout

6

Cet exemple simple a fonctionné pour moi. J'espère que cela t'aides.

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}

13
Ne fonctionnera pas si vous avez DIRECTION a = NORTH; puis écrivez ENUM_TO_STR (a)
mathreadler

5

Avez-vous essayé ceci:

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

La stringify()macro peut être utilisée pour transformer n'importe quel texte de votre code en chaîne, mais uniquement le texte exact entre les parenthèses. Il n'y a pas de déréférencement de variables ou de substitutions de macros ou toute autre sorte de chose faite.

http://www.cplusplus.com/forum/general/2949/


Celui-ci sera en fait le meilleur, même si le premier suffirait :)
pholat

Fonctionne bien, mais vous devez ajouter #ifndef stringify en haut pour éviter une erreur de compilation. J'ai également changé le type d'énumération en std :: string comme le suggérait dgmz.
astarakastara le

5

Il y a beaucoup de bonnes réponses ici, mais j'ai pensé que certaines personnes pourraient trouver la mienne utile. Je l'aime parce que l'interface que vous utilisez pour définir la macro est à peu près aussi simple que possible. C'est aussi pratique car vous n'avez pas besoin d'inclure de bibliothèques supplémentaires - tout est livré avec C ++ et il ne nécessite même pas une version très tardive. J'ai extrait des articles de divers endroits en ligne, donc je ne peux pas m'en attribuer le mérite, mais je pense que c'est assez unique pour justifier une nouvelle réponse.

Commencez par créer un fichier d'en-tête ... appelez-le EnumMacros.h ou quelque chose comme ça, et mettez-y ceci:

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

Ensuite, dans votre programme principal, vous pouvez le faire ...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

Où serait la sortie >> La valeur de 'Apple' est: 2 sur 4

Prendre plaisir!


La principale chose que j'aime dans cette approche particulière est qu'elle fonctionne avec la syntaxe normale séparée par des virgules de l'énumération (tant qu'elle n'inclut aucune attribution de paramètre de valeur dans l'énumération). Dans mon cas, je devais travailler avec une énumération existante avec un grand nombre de membres, donc c'était beaucoup plus facile à utiliser que l'approche boost.
CuriousKea

4

En supposant que votre énumération est déjà définie, vous pouvez créer un tableau de paires:

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

Maintenant, vous pouvez créer une carte:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Maintenant, vous pouvez utiliser la carte. Si votre énumération est modifiée, vous devez ajouter / supprimer une paire des paires de tableaux []. Je pense que c'est le moyen le plus élégant d'obtenir une chaîne d'énumération en C ++.


2
Mis à part le commentaire juste qu'il n'y a pas besoin de Qt ici, un autre point est que l'on pourrait vouloir utiliser Boost bimapau cas où l'on voudrait analyser les noms et les transformer en énumérations (par exemple, à partir d'un fichier XML).
Dmitri Nesteruk le

4
Ne devrait pas utiliser de types Qt dans une question C ++ générique.
Vecteur

3

Pour C99, il y a P99_DECLARE_ENUMdans P99 qui vous permet simplement de déclarer enumcomme ceci:

P99_DECLARE_ENUM(color, red, green, blue);

puis utilisez color_getname(A)pour obtenir une chaîne avec le nom de la couleur.


2

Voici mon code C ++:

/* 
 * File:   main.cpp
 * Author: y2k1234
 *
 * Created on June 14, 2013, 9:50 AM
 */

#include <cstdlib>
#include <stdio.h>

using namespace std;


#define MESSAGE_LIST(OPERATOR)                          \
                                       OPERATOR(MSG_A), \
                                       OPERATOR(MSG_B), \
                                       OPERATOR(MSG_C)
#define GET_LIST_VALUE_OPERATOR(msg)   ERROR_##msg##_VALUE
#define GET_LIST_SRTING_OPERATOR(msg)  "ERROR_"#msg"_NAME"

enum ErrorMessagesEnum
{
   MESSAGE_LIST(GET_LIST_VALUE_OPERATOR)
};
static const char* ErrorMessagesName[] = 
{
   MESSAGE_LIST(GET_LIST_SRTING_OPERATOR)
};

int main(int argc, char** argv) 
{

    int totalMessages = sizeof(ErrorMessagesName)/4;

    for (int i = 0; i < totalMessages; i++)
    {
        if (i == ERROR_MSG_A_VALUE)
        {
                printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_B_VALUE)
        {
                printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_C_VALUE)
        {
                printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else
        {
                printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
    }   

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)

2

Un peu tard à la fête, mais voici ma solution C ++ 11:

namespace std {
    template<> struct hash<enum_one> {
        std::size_t operator()(const enum_one & e) const {
            return static_cast<std::size_t>(e);
        }
    };
    template<> struct hash<enum_two> { //repeat for each enum type
        std::size_t operator()(const enum_two & e) const {
            return static_cast<std::size_t>(e);
        }
    };
}

const std::string & enum_name(const enum_one & e) {
    static const std::unordered_map<enum_one, const std::string> names = {
    #define v_name(n) {enum_one::n, std::string(#n)}
        v_name(value1),
        v_name(value2),
        v_name(value3)
    #undef v_name
    };
    return names.at(e);
}

const std::string & enum_name(const enum_two & e) { //repeat for each enum type
    .................
}

1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra

2

Ma propre préférence est de minimiser à la fois le typage répétitif et les macros difficiles à comprendre et d'éviter d'introduire des définitions de macro dans l'espace général du compilateur.

Donc, dans le fichier d'en-tête:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

et l'implémentation cpp est:

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }

Notez le #undef de la macro dès que nous en avons terminé avec elle.


2

Ma solution, ne pas utiliser boost:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

#define MAKE_STRING_1(str     ) #str
#define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__)
#define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__)
#define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__)
#define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__)
#define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__)
#define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__)
#define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__)

#define PRIMITIVE_CAT(a, b) a##b
#define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N)     (__VA_ARGS__)


#define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0
#define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())

#define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ };            \
  struct NAME##_str {                                              \
    static const char * get(const NAME et) {                       \
      static const char* NAME##Str[] = {                           \
                MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) };  \
      return NAME##Str[et];                                        \
      }                                                            \
    };

#endif /* EN2STR_HXX_ */

Et voici comment l'utiliser

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }

2

Un autre en retard à la fête, utilisant le préprocesseur:

 1  #define MY_ENUM_LIST \
 2      DEFINE_ENUM_ELEMENT(First) \
 3      DEFINE_ENUM_ELEMENT(Second) \
 4      DEFINE_ENUM_ELEMENT(Third) \
 5  
 6  //--------------------------------------
 7  #define DEFINE_ENUM_ELEMENT(name) , name
 8  enum MyEnum {
 9      Zeroth = 0
10      MY_ENUM_LIST
11  };
12  #undef DEFINE_ENUM_ELEMENT
13 
14  #define DEFINE_ENUM_ELEMENT(name) , #name
15  const char* MyEnumToString[] = {
16      "Zeroth"
17      MY_ENUM_LIST
18  };
19  #undef DEFINE_ENUM_ELEMENT
20
21  #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name;
22  enum MyEnum StringToMyEnum(const char* s){
23      if (strcmp(s, "Zeroth")==0) return Zeroth;
24      MY_ENUM_LIST
25      return NULL;
26  }
27  #undef DEFINE_ENUM_ELEMENT

(Je viens de mettre des numéros de ligne pour qu'il soit plus facile d'en parler.) Les lignes 1 à 4 sont ce que vous éditez pour définir les éléments de l'énumération. (Je l'ai appelé une "macro de liste", car c'est une macro qui fait une liste de choses. @Lundin m'informe qu'il s'agit d'une technique bien connue appelée X-macros.)

La ligne 7 définit la macro interne afin de remplir la déclaration d'énumération réelle aux lignes 8-11. La ligne 12 annule la définition de la macro interne (juste pour faire taire l'avertissement du compilateur).

La ligne 14 définit la macro interne de manière à créer une version chaîne du nom d'élément enum. Ensuite, les lignes 15 à 18 génèrent un tableau qui peut convertir une valeur d'énumération en chaîne correspondante.

Les lignes 21 à 27 génèrent une fonction qui convertit une chaîne en valeur d'énumération, ou retourne NULL si la chaîne ne correspond à aucune.

C'est un peu encombrant dans la façon dont il gère le 0ème élément. J'ai en fait travaillé autour de cela dans le passé.

J'admets que cette technique dérange les gens qui ne veulent pas penser que le préprocesseur lui-même peut être programmé pour écrire du code à votre place. Je pense que cela illustre fortement la différence entre la lisibilité et la maintenabilité . Le code est difficile à lire, mais si l'énumération contient quelques centaines d'éléments, vous pouvez ajouter, supprimer ou réorganiser des éléments tout en vous assurant que le code généré ne contient aucune erreur.


Les "macros X" sont rarement une solution élégante à un problème. Dans ce cas, il serait beaucoup plus lisible de simplement définir les éléments de macro comme #define TEST_1 hello #define TEST_2 worldalors typedef enum { TEST_1, TEST_2 } test_t;, puis de créer une table de recherche de chaînes qui utilise une macro stringify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; Il existe déjà plusieurs réponses suggérant des solutions similaires. Beaucoup plus lisible.
Lundin

@Lundin: Je prétends seulement que 1) cela fonctionne avec même le compilateur C le plus primitif, et 2) l'ajout ou la suppression d'un élément est une modification d'une ligne.
Mike Dunlavey

J'ai publié ma propre réponse: stackoverflow.com/a/39877228/584518 . Espérons que cela sauvera une pauvre âme des solutions x macros.
Lundin

1
J'ai utilisé votre solution. Je pense que c'est le meilleur. La syntaxe C est toujours là pour que vous compreniez ce qui se passe et la liste n'est définie qu'une seule fois. Vous pouvez supprimer le 0e élément en plaçant la virgule après l'entrée dans votre DEFINE_ENUM_ELEMENT.
terminé

1

Voici la méthode Old Skool (utilisée pour être largement utilisée dans gcc) en utilisant uniquement le pré-processeur C. Utile si vous générez des structures de données discrètes mais que vous devez garder l'ordre cohérent entre elles. Les entrées de mylist.tbl peuvent bien sûr être étendues à quelque chose de beaucoup plus complexe.

test.cpp:

enum {
#undef XX
#define XX(name, ignore) name ,
#include "mylist.tbl"
  LAST_ENUM
};

char * enum_names [] = {
#undef XX
#define XX(name, ignore) #name ,
#include "mylist.tbl"
   "LAST_ENUM"
};

Et puis mylist.tbl:

/*    A = enum                  */
/*    B = some associated value */
/*     A        B   */
  XX( enum_1 , 100)
  XX( enum_2 , 100 )
  XX( enum_3 , 200 )
  XX( enum_4 , 900 )
  XX( enum_5 , 500 )

1
Cette technique s'appelle x macros!
Watusimoto

0

En C ++ comme ceci:

enum OS_type{Linux, Apple, Windows};

std::string ToString( const OS_type v )
{
  const std::map< OS_type, std::string > lut =
    boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows");
  std::map< OS_type, std::string >::const_iterator it = lut.find( v );
  if ( lut.end() != it )
    return it->second;
  return "NOT FOUND";
}

0
#include <EnumString.h>

sur http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C et après

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

insérer

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

Fonctionne très bien si les valeurs de l'énumération ne sont pas dupliquées.

Exemple de code pour convertir une valeur d'énumération en chaîne:

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

Exemple de code pour exactement le contraire:

assert( EnumString< FORM >::To( f, str ) );

0

Merci James pour votre suggestion. C'était très utile, donc j'ai mis en œuvre l'inverse pour contribuer d'une manière ou d'une autre.

#include <iostream>
#include <boost/preprocessor.hpp>

using namespace std;

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data,  elem) \
    case data::elem : return BOOST_PP_STRINGIZE(elem);

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \
    if (BOOST_PP_SEQ_TAIL(data) ==                                     \
            BOOST_PP_STRINGIZE(elem)) return                           \
            static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)         \
    enum class name {                                                  \
        BOOST_PP_SEQ_ENUM(enumerators)                                 \
    };                                                                 \
                                                                       \
    inline const char* ToString(name v)                                \
    {                                                                  \
        switch (v)                                                     \
        {                                                              \
            BOOST_PP_SEQ_FOR_EACH(                                     \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,   \
                name,                                                  \
                enumerators                                            \
            )                                                          \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";  \
        }                                                              \
    }                                                                  \
                                                                       \
    inline int ToEnum(std::string s)                                   \
    {                                                                  \
        BOOST_PP_SEQ_FOR_EACH(                                         \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF,       \
                (name)(s),                                             \
                enumerators                                            \
            )                                                          \
        return -1;                                                     \
    }


DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows));

int main(void)
{
    OS_type t = OS_type::Windows;

    cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl;

    cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl;

    return 0;
}

0

Pour étendre la réponse de James, quelqu'un veut un exemple de code pour prendre en charge enum define avec une valeur int, j'ai aussi cette exigence, alors voici ma façon:

La première est la macro d'utilisation interne, qui est utilisée par FOR_EACH:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem)         \
    BOOST_PP_IF(                                                                \
        BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2),                           \
        BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem),            \
        BOOST_PP_TUPLE_ELEM(0, elem) ),

Et voici la macro de définition:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

Donc, lorsque vous l'utilisez, vous aimerez peut-être écrire comme ceci:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

qui s'étendra à:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

L'idée de base est de définir un SEQ, dont chaque élément est un TUPLE, afin que nous puissions mettre une valeur supplémentaire pour le membre enum. Dans la boucle FOR_EACH, vérifiez la taille de l'élément TUPLE, si la taille est 2, développez le code en KEY = VALUE, sinon gardez simplement le premier élément de TUPLE.

Parce que l'entrée SEQ est en fait TUPLEs, donc si vous voulez définir des fonctions STRINGIZE, vous devrez peut-être pré-traiter les énumérateurs d'entrée, voici la macro pour faire le travail:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem)           \
    BOOST_PP_TUPLE_ELEM(0, elem),

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators)         \
    BOOST_PP_SEQ_SUBSEQ(                                                        \
        BOOST_PP_TUPLE_TO_SEQ(                                                  \
            (BOOST_PP_SEQ_FOR_EACH(                                             \
                DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \
            )),                                                                 \
            0,                                                                  \
            BOOST_PP_SEQ_SIZE(enumerators))

La macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQne conservera que le premier élément de chaque TUPLE, puis se convertira en SEQ, modifiez maintenant le code de James, vous aurez toute la puissance.

Mon implémentation n'est peut-être pas la plus simple, donc si vous ne trouvez pas de code propre, la mienne pour votre référence.


0

Solution propre et sûre dans le pur standard C:

#include <stdio.h>

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

int main()
{  
  _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
                 "Incorrect number of items in enum or look-up table");

  printf("%d %s\n", hello, test_str[hello]);
  printf("%d %s\n", world, test_str[world]);
  test_t x = world;
  printf("%d %s\n", x, test_str[x]);

  return 0;
}

Production

0 hello
1 world
1 world

Raisonnement

Lors de la résolution du problème principal "avoir des constantes d'énumération avec les chaînes correspondantes", un programmeur sensé présentera les exigences suivantes:

  • Évitez la répétition du code (principe «DRY»).
  • Le code doit être évolutif, maintenable et sûr même si des éléments sont ajoutés ou supprimés à l'intérieur de l'énumération.
  • Tout le code doit être de haute qualité: facile à lire, facile à entretenir.

La première exigence, et peut-être aussi la seconde, peut être remplie avec diverses solutions macro désordonnées telles que la tristement célèbre astuce "x macro", ou d'autres formes de macro-magie. Le problème avec de telles solutions est qu'elles vous laissent avec un désordre complètement illisible de macros mystérieuses - elles ne répondent pas à la troisième exigence ci-dessus.

La seule chose nécessaire ici est en fait d'avoir une table de recherche de chaînes, à laquelle nous pouvons accéder en utilisant la variable enum comme index. Un tel tableau doit naturellement correspondre directement à l'énumération et vice versa. Lorsque l'un d'eux est mis à jour, l'autre doit également être mis à jour, sinon cela ne fonctionnera pas.


Explication du code

Supposons que nous ayons une énumération comme

typedef enum
{
  hello,
  world
} test_t;

Cela peut être changé en

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Avec l'avantage que ces constantes de macro peuvent maintenant être utilisées ailleurs, par exemple pour générer une table de recherche de chaînes. La conversion d'une constante de pré-processeur en chaîne peut être effectuée avec une macro "stringify":

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

Et c'est tout. En utilisant hello, nous obtenons la constante enum avec la valeur 0. En utilisant, test_str[hello]nous obtenons la chaîne "hello".

Pour faire correspondre directement l'énumération et la table de recherche, nous devons nous assurer qu'elles contiennent le même nombre d'éléments. Si quelqu'un maintenait le code et ne modifiait que l'énumération, et non la table de recherche, ou vice versa, cette méthode ne fonctionnera pas.

La solution est d'avoir l'énumération pour vous dire combien d'éléments elle contient. Il existe une astuce C couramment utilisée pour cela, ajoutez simplement un élément à la fin, ce qui ne remplit que le but de dire combien d'éléments l'énumération a:

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

Maintenant, nous pouvons vérifier au moment de la compilation que le nombre d'éléments dans l'énumération est égal au nombre d'éléments dans la table de recherche, de préférence avec une assertion statique C11:

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(Il existe également des moyens horribles mais entièrement fonctionnels de créer des assertions statiques dans les anciennes versions du standard C, si quelqu'un insiste pour utiliser des compilateurs de dinosaures. Quant au C ++, il prend également en charge les assertions statiques.)


En remarque, en C11, nous pouvons également obtenir une sécurité de type plus élevée en modifiant la macro stringify:

#define STRINGIFY(x) _Generic((x), int : STRF(x))

( intcar les constantes d'énumération sont en fait de type int, non test_t)

Cela empêchera le code comme STRINGIFY(random_stuff)de se compiler.


Je comprends ce que vous dites, mais le point demeure. Les modifications prévisibles typiques devraient nécessiter des modifications minimes (comme 1 ligne). (Je pense que c'est la raison derrière DRY.) Donc, ici, si la taille de l'énumération est de 500, et que vous voulez insérer un nouvel élément au milieu (ou supprimer / renommer / permuter), combien de lignes de code devez-vous changer, et combien de vérifications devez-vous faire pour être sûr que vous n'avez pas fait d'erreur? Il peut également y avoir d'autres morceaux de code qui font quelque chose d'uniforme pour chaque élément de la liste.
Mike Dunlavey

Merci de m'avoir dit que cela s'appelle X-macros . Je ne savais pas ça. Ce que je ne vois pas, ce sont les gens qui les dénigrent en général.
Mike Dunlavey

@MikeDunlavey Quelle que soit la taille de l'énumération, vous devrez changer exactement 3 lignes: ajoutez un #define, ajoutez une référence à cette définition dans la déclaration d'énumération et la table de recherche. Si vous faites une gaffe lors de l'ajout de ces lignes, le programme ne se compilera pas. Les chiffres que j'ai ajoutés aux identifiants ne sont en aucun cas obligatoires, vous pouvez aussi bien écrire #define APPLES helloet #define ORANGES worldsuivis de typedef enum { APPES, ORANGES, TEST_N } test_t;et ainsi de suite.
Lundin

@MikeDunlavey Concernant les macros X, les arguments contre elles sont les mêmes que les arguments contre toutes les macros de type fonction. Vous n'aurez pas besoin de chercher loin pour trouver de nombreuses critiques très valables contre les macros de type fonction.
Lundin

0

Ce que j'ai fait est une combinaison de ce que j'ai vu ici et dans des questions similaires sur ce site. J'ai fait ceci est Visual Studio 2013. Je ne l'ai pas testé avec d'autres compilateurs.

Tout d'abord, je définis un ensemble de macros qui feront les astuces.

// concatenation macros
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B)  CONCAT_(A, B)

// generic expansion and stringification macros
#define EXPAND(X)           X
#define STRINGIFY(ARG)      #ARG
#define EXPANDSTRING(ARG)   STRINGIFY(ARG)        

// number of arguments macros
#define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N
#define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1))

// argument extraction macros
#define FIRST_ARG(ARG, ...) ARG
#define REST_ARGS(ARG, ...) __VA_ARGS__

// arguments to strings macros
#define ARGS_STR__(N, ...)  ARGS_STR_##N(__VA_ARGS__)
#define ARGS_STR_(N, ...)   ARGS_STR__(N, __VA_ARGS__)
#define ARGS_STR(...)       ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define ARGS_STR_1(ARG)     EXPANDSTRING(ARG)
#define ARGS_STR_2(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_3(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_4(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_5(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_6(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_7(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_8(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_9(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_10(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_11(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_12(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_13(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_14(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_15(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_16(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_17(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_18(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_19(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_20(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__)))
// expand until _100 or as much as you need

Ensuite, définissez une macro unique qui créera la classe enum et les fonctions pour obtenir les chaînes.

#define ENUM(NAME, ...)                                                                                             \
    enum class NAME                                                                                                 \
    {                                                                                                               \
        __VA_ARGS__                                                                                                 \
    };                                                                                                              \
                                                                                                                    \
    static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) };  \
                                                                                                                    \
    inline const std::string& ToString(NAME value)                                                                  \
    {                                                                                                               \
        return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)];                         \
    }                                                                                                               \
                                                                                                                    \
    inline std::ostream& operator<<(std::ostream& os, NAME value)                                                   \
    {                                                                                                               \
        os << ToString(value);                                                                                      \
        return os;                                                                                                  \
    }

Maintenant, définir un type enum et avoir des chaînes pour cela devient vraiment facile. Tout ce que vous avez à faire est:

ENUM(MyEnumType, A, B, C);

Les lignes suivantes peuvent être utilisées pour le tester.

int main()
{
    std::cout << MyEnumTypeStrings.size() << std::endl;

    std::cout << ToString(MyEnumType::A) << std::endl;
    std::cout << ToString(MyEnumType::B) << std::endl;
    std::cout << ToString(MyEnumType::C) << std::endl;

    std::cout << MyEnumType::A << std::endl;
    std::cout << MyEnumType::B << std::endl;
    std::cout << MyEnumType::C << std::endl;

    auto myVar = MyEnumType::A;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::B;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::C;
    std::cout << myVar << std::endl;

    return 0;
}

Cela produira:

3
A
B
C
A
B
C
A
B
C

Je pense qu'il est très propre et facile à utiliser. Il y a quelques limitations:

  • Vous ne pouvez pas attribuer de valeurs aux membres d'énumération.
  • Les valeurs du membre enum sont utilisées comme index, mais cela devrait être bien, car tout est défini dans une seule macro.
  • Vous ne pouvez pas l'utiliser pour définir un type enum dans une classe.

Si vous pouvez contourner ce problème. Je pense, surtout comment l'utiliser, c'est joli et maigre. Avantages:

  • Facile à utiliser.
  • Aucun fractionnement de chaîne requis au moment de l'exécution.
  • Des chaînes distinctes sont disponibles au moment de la compilation.
  • Facile à lire. Le premier ensemble de macros peut nécessiter une seconde supplémentaire, mais n'est pas vraiment si compliqué.

0

Une solution propre à ce problème serait:

#define RETURN_STR(val, e) {if (val == e) {return #e;}}

std::string conv_dxgi_format_to_string(int value) {
    RETURN_STR(value, DXGI_FORMAT_UNKNOWN);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT);

    /* ... */

    return "<UNKNOWN>";
}

La bonne chose à propos de cette solution est qu'elle est simple et que la construction de la fonction peut également être effectuée facilement via copier et remplacer. Notez que si vous allez effectuer beaucoup de conversions et que votre énumération a trop de valeurs possibles, cette solution peut devenir gourmande en ressources processeur.


0

Je suis un peu en retard mais voici ma solution utilisant g ++ et uniquement des bibliothèques standard. J'ai essayé de minimiser la pollution des espaces de noms et de supprimer tout besoin de retaper les noms d'énumérations.

Le fichier d'en-tête "my_enum.hpp" est:

#include <cstring>

namespace ENUM_HELPERS{
    int replace_commas_and_spaces_with_null(char* string){
        int i, N;
        N = strlen(string);
        for(i=0; i<N; ++i){
            if( isspace(string[i]) || string[i] == ','){
                string[i]='\0';
            }
        }
        return(N);
    }

    int count_words_null_delim(char* string, int tot_N){
        int i;
        int j=0;
        char last = '\0';
        for(i=0;i<tot_N;++i){
            if((last == '\0') && (string[i]!='\0')){
                ++j;
            }
            last = string[i];
        }
        return(j);
    }

    int get_null_word_offsets(char* string, int tot_N, int current_w){
        int i;
        int j=0;
        char last = '\0';
        for(i=0; i<tot_N; ++i){
            if((last=='\0') && (string[i]!='\0')){
                if(j == current_w){
                    return(i);
                }
                ++j;
            }
            last = string[i];
        }
        return(tot_N); //null value for offset
    }

    int find_offsets(int* offsets, char* string, int tot_N, int N_words){
        int i;
        for(i=0; i<N_words; ++i){
            offsets[i] = get_null_word_offsets(string, tot_N, i);
        }
        return(0);
    }
}


#define MAKE_ENUM(NAME, ...)                                            \
namespace NAME{                                                         \
    enum ENUM {__VA_ARGS__};                                            \
    char name_holder[] = #__VA_ARGS__;                                  \
    int name_holder_N =                                                 \
        ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \
    int N =                                                             \
        ENUM_HELPERS::count_words_null_delim(                           \
            name_holder, name_holder_N);                                \
    int offsets[] = {__VA_ARGS__};                                      \
    int ZERO =                                                          \
        ENUM_HELPERS::find_offsets(                                     \
            offsets, name_holder, name_holder_N, N);                    \
    char* tostring(int i){                                              \
       return(&name_holder[offsets[i]]);                                \
    }                                                                   \
}

Exemple d'utilisation:

#include <cstdio>
#include "my_enum.hpp"

MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS)

int main(int argc, char** argv){    
    Planets::ENUM a_planet = Planets::EARTH;
    printf("%s\n", Planets::tostring(Planets::MERCURY));
    printf("%s\n", Planets::tostring(a_planet));
}

Cela produira:

MERCURY
EARTH

Vous n'avez qu'à tout définir une seule fois, votre espace de noms ne doit pas être pollué et tout le calcul n'est fait qu'une seule fois (le reste n'est que des recherches). Cependant, vous n'obtenez pas la sécurité de type des classes enum (ce ne sont encore que des entiers courts), vous ne pouvez pas assigner de valeurs aux enums, vous devez définir des enums quelque part où vous pouvez définir des espaces de noms (par exemple globalement).

Je ne sais pas à quel point les performances sont bonnes ou si c'est une bonne idée (j'ai appris le C avant le C ++, donc mon cerveau fonctionne toujours de cette façon). Si quelqu'un sait pourquoi c'est une mauvaise idée, n'hésitez pas à le signaler.


0

Nous sommes en 2017 mais la question est toujours vivante

Encore une autre façon:

#include <iostream>

#define ERROR_VALUES \
ERROR_VALUE(NO_ERROR, 0, "OK") \
ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \
ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage")

enum Error
{
#define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << NO_ERROR << std::endl;
    std::cout << "Error: " << FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << LABEL_UNINITIALISED << std::endl;
    return 0;
}

Les sorties:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage

0
#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <algorithm>

namespace StringifyEnum
{
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { ++rit; }
    return std::string(it, rit.base());
}

static std::vector<std::string> SplitEnumArgs(const char* szArgs, int     nMax)
{
    std::vector<std::string> enums;
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        enums.push_back(StringifyEnum::TrimEnumString(strSub));
        ++nIdx;
    }
    return std::move(enums);
}    
}

#define DECLARE_ENUM_SEQ(ename, n, ...) \
    enum class ename { __VA_ARGS__ }; \
    const int MAX_NUMBER_OF_##ename(n); \
    static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
    inline static std::string ename##ToString(ename e) { \
        return ename##Strings.at((int)e); \
    } \
    inline static ename StringTo##ename(const std::string& en) { \
        const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
        if (it != ename##Strings.end()) \
            return (ename) std::distance(ename##Strings.begin(), it); \
        throw std::runtime_error("Could not resolve string enum value");     \
    }

Il s'agit d'une version d'énumération étendue de classe élaborée ... elle n'ajoute aucune autre valeur d'énumération autre que celles fournies.

Utilisation: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)


0

J'avais besoin que cela fonctionne dans les deux sens ET j'intègre fréquemment mes énumérations dans une classe contenant, et j'ai donc commencé avec la solution de James McNellis, bien au sommet de ces réponses, mais j'ai fait cette solution. Notez également que je préfère enum class plutôt que simplement enum, ce qui complique quelque peu la réponse.

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

Pour l'utiliser dans une classe, vous pouvez faire quelque chose comme ceci:

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

Et j'ai écrit un test CppUnit, qui montre comment l'utiliser:

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

Vous devez choisir la macro à utiliser, soit DEFINE_ENUMERATION ou DEFINE_ENUMERATION_INSIDE_CLASS. Vous verrez que j'ai utilisé ce dernier lors de la définition de ComponentStatus :: Status mais j'ai utilisé le premier lors de la définition de Status. La différence est simple. Dans une classe, je préfixe les méthodes to / from comme "static" et si ce n'est pas dans une classe, j'utilise "inline". Des différences triviales, mais nécessaires.

Malheureusement, je ne pense pas qu'il existe un moyen propre d'éviter d'avoir à faire cela:

const char * valueStr = ComponentStatus::ToString(value);

bien que vous puissiez créer manuellement une méthode en ligne après votre définition de classe qui enchaîne simplement à la méthode de classe, quelque chose comme:

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }

0

Ma propre réponse, ne pas utiliser boost - utiliser ma propre approche sans magie de définition lourde, et cette solution a une limitation de ne pas pouvoir définir une valeur d'énumération spécifique.

#pragma once
#include <string>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name> {                                         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");

    WARNING: At the moment assigning enum value to specific number is not supported.
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = (int)t;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (id == 0)
            return std::string(token, next);
        id--;
    } while (*next != 0);

    return std::string();
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = 0;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (strncmp(token, enumName, next - token) == 0)
        {
            t = (T)id;
            return true;
        }

        id++;
    } while (*next != 0);

    return false;
}

La dernière version peut être trouvée sur github ici:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h


0

Il y a beaucoup d'autres réponses à cela, mais je pense qu'une meilleure façon est d'utiliser les fonctionnalités de C ++ 17 et d'utiliser constexpr afin que les traductions soient effectuées au moment de la compilation. Il s'agit d'un type sécurisé et nous n'avons pas besoin de jouer avec les macros. Voir ci-dessous:

//enum.hpp
#include <array>
#include <string_view>

namespace Enum
{

template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Value not in map":
        (std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
        findKey(value, map, index - 1);
};

template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Key not in map":
        (map[index - 1].first == key) ? map[index- 1].second:
        findValue(key, map, index - 1);
};

}

//test_enum.hpp
#include "enum.hpp"

namespace TestEnum
{
    enum class Fields
    {
        Test1,
        Test2,
        Test3,
        //This has to be at the end
        NUMBER_OF_FIELDS
    };

    constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
    {
        std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
        {
            {
                    {Fields::Test1, "Test1"},
                    {Fields::Test2, "Test2"},
                    {Fields::Test3, "Test3"},
            }
        };
        return map;
    };

    constexpr Fields StringToEnum(const char * value)
    {
        return Enum::findKey(value, GetMap());
    }

    constexpr const char * EnumToString(Fields key)
    {
        return Enum::findValue(key, GetMap());
    }

}

Cela peut ensuite être facilement utilisé pour que les erreurs de clé de chaîne soient détectées au moment de la compilation:

#include "test_enum.hpp"

int main()
{
    auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
    auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
    auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
    return 0;
}

Le code est plus détaillé que certaines autres solutions, mais nous pouvons facilement effectuer une conversion Enum vers String et une conversion String vers Enum au moment de la compilation et détecter les erreurs de type. Avec certaines des futures fonctionnalités de C ++ 20, cela peut probablement être un peu plus simplifié.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.