C ++: affiche la valeur d'énumération sous forme de texte

89

Si j'ai une énumération comme celle-ci

enum Errors
{ErrorA=0, ErrorB, ErrorC};

Ensuite, je veux imprimer sur la console

Errors anError = ErrorA;
cout<<anError;/// 0 will be printed

mais ce que je veux, c'est le texte "ErrorA", puis-je le faire sans utiliser if / switch?
Et quelle est votre solution pour cela?

tiboo
la source
Je pense que ma réponse est plutôt bonne, cela vous dérangerait-il de jeter un œil?
Xiao
2
Pour C ++ 11 enum class: stackoverflow.com/questions/11421432/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
Énumération dans la chaîne: stackoverflow.com/questions/201593/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

63

Utilisation de la carte:

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

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    static std::map<Errors, std::string> strings;
    if (strings.size() == 0){
#define INSERT_ELEMENT(p) strings[p] = #p
        INSERT_ELEMENT(ErrorA);     
        INSERT_ELEMENT(ErrorB);     
        INSERT_ELEMENT(ErrorC);             
#undef INSERT_ELEMENT
    }   

    return out << strings[value];
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Utilisation d'un tableau de structures avec recherche linéaire:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
#define MAPENTRY(p) {p, #p}
    const struct MapEntry{
        Errors value;
        const char* str;
    } entries[] = {
        MAPENTRY(ErrorA),
        MAPENTRY(ErrorB),
        MAPENTRY(ErrorC),
        {ErrorA, 0}//doesn't matter what is used instead of ErrorA here...
    };
#undef MAPENTRY
    const char* s = 0;
    for (const MapEntry* i = entries; i->str; i++){
        if (i->value == value){
            s = i->str;
            break;
        }
    }

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Utilisation du commutateur / boîtier:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    const char* s = 0;
#define PROCESS_VAL(p) case(p): s = #p; break;
    switch(value){
        PROCESS_VAL(ErrorA);     
        PROCESS_VAL(ErrorB);     
        PROCESS_VAL(ErrorC);
    }
#undef PROCESS_VAL

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}
SigTerm
la source
12
-1. Faites simplement un switch-case au lieu d'utiliser un hash-map. Une complexité accrue n'est pas une bonne chose.
Simon
8
Bon point. La prochaine fois, je le ferai :) Mais maintenant je vois que vous avez déjà édité votre message pour ajouter le type de fonctionnalité que je recherchais. Bon travail!
Simon
1
qu'est-ce que #p? si dans le troisième exemple au lieu d'énumération j'utilise une classe enum, est-il possible d'obtenir uniquement la chaîne d'énumération sans le nom de la classe?
rh0x
2
#pest le préprocesseur stringifiant p. Donc , appeler PROCESS_VAL(ErrorA)Affichera: case(ErrorA): s = "ErrorA"; break;.
Nashenas
Je ne considère pas cela comme une solution optimale: Raisons: 1) Je dois maintenir deux fois les enumvaleurs que je pense être un NON-GO . 2) Lorsque je comprends correctement la solution, cela ne fonctionne que pour un seul enum.
Peter VARGA
28

Utilisez un tableau ou un vecteur de chaînes avec des valeurs correspondantes:

char *ErrorTypes[] =
{
    "errorA",
    "errorB",
    "errorC"
};

cout << ErrorTypes[anError];

EDIT: La solution ci-dessus est applicable lorsque l'énumération est contiguë, c'est-à-dire qu'elle commence à 0 et qu'aucune valeur n'est attribuée. Cela fonctionnera parfaitement avec l'énumération de la question.

Pour prouver davantage le cas où enum ne commence pas à partir de 0, utilisez:

cout << ErrorTypes[anError - ErrorA];
Igor Oks
la source
4
malheureusement, enum nous permet d'attribuer des valeurs aux éléments. Comment abordez-vous le travail si vous avez des énumérations non contiguos, ligne 'enum Status {OK = 0, Fail = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000} `(afin que vous puissiez plus tard ajouter IOErrors à la gamme -1001-1999)
Nordic Mainframe
@Luther: Oui, cela ne fonctionnera qu'avec des énumérations contiguës, ce que sont la plupart des énumérations . Dans le cas où l'énumération n'est pas contiguë, vous devrez utiliser une autre approche, c'est-à-dire des cartes. Mais en cas d'énumération contiguë, je suggérerais d'utiliser cette approche et de ne pas trop compliquer.
Igor Oks
2
Donc, si mon collègue ajoute NewValue à une énumération et ne met pas à jour le tableau ErrorTypes, alors ErrorTypes [NewValue] donne quoi? Et comment gérer les valeurs d'énumération négatives?
Nordic Mainframe
2
@Luther: Vous devrez garder ErrorTypes à jour. Encore une fois, il y a un compromis entre la simplicité et l'universalité, cela dépend de ce qui est le plus important pour l'utilisateur. Quel est le problème avec les valeurs d'énumération négatives?
Igor Oks
1
Ce tableau ne devrait-il pas être statique pour l'efficacité de la mémoire? et const pour la sécurité?
Jonathan
15

Voici un exemple basé sur Boost.Preprocessor:

#include <iostream>

#include <boost/preprocessor/punctuation/comma.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>


#define DEFINE_ENUM(name, values)                               \
  enum name {                                                   \
    BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)          \
  };                                                            \
  inline const char* format_##name(name val) {                  \
    switch (val) {                                              \
      BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)       \
    default:                                                    \
        return 0;                                               \
    }                                                           \
  }

#define DEFINE_ENUM_VALUE(r, data, elem)                        \
  BOOST_PP_SEQ_HEAD(elem)                                       \
  BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),      \
               = BOOST_PP_SEQ_TAIL(elem), )                     \
  BOOST_PP_COMMA()

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


DEFINE_ENUM(Errors,
            ((ErrorA)(0))
            ((ErrorB))
            ((ErrorC)))

int main() {
  std::cout << format_Errors(ErrorB) << std::endl;
}
Philipp
la source
2
+1, Cette solution ne repose pas sur un outil externe, comme la réponse lua ci-dessus, mais est du C ++ pur, elle suit le principe DRY, et la syntaxe utilisateur est lisible (si formatée correctement. BTW, vous n'avez pas besoin des barres obliques inverses lors de l'utilisation de DEFINE_ENUM, qui semble un peu plus naturel, IMO)
Fabio Fracassi
3
@Fabio Fracassi: "Cette solution ne repose pas sur un outil externe" Boost est un outil externe - bibliothèque C ++ non standard. En plus, c'est un peu trop long. La solution à un problème doit être aussi simple que possible. Celui-ci n'est pas admissible ...
SigTerm
2
En fait, c'est tout ce que vous pouvez mettre la plupart du code (en fait, tout sauf la définition réelle) peut être mis dans un seul en-tête. c'est donc la solution la plus courte présentée ici. Et pour que le boost soit externe, oui, mais moins qu'un script hors langage pour le prétraitement de parties de la source comme le script lua ci-dessus. En outre, boost est si proche du standard qu'il devrait être dans toutes les boîtes à outils de programmeurs C ++. Juste à mon humble avis, bien sûr
Fabio Fracassi
[J'ai supprimé l'échappement inutile des nouvelles lignes lors de l'invocation de macro. Ils ne sont pas nécessaires: un appel de macro peut s'étendre sur plusieurs lignes.]
James McNellis
La macro DEFINE_ENUMme donne l'erreur multiple definition of `format_ProgramStatus(ProgramStatus)'lorsque j'essaie de l'utiliser.
HelloGoodbye
6

Vous pouvez utiliser une astuce de pré-processeur plus simple si vous êtes prêt à lister vos enumentrées dans un fichier externe.

/* file: errors.def */
/* syntax: ERROR_DEF(name, value) */
ERROR_DEF(ErrorA, 0x1)
ERROR_DEF(ErrorB, 0x2)
ERROR_DEF(ErrorC, 0x4)

Ensuite, dans un fichier source, vous traitez le fichier comme un fichier d'inclusion, mais vous définissez ce que vous voulez ERROR_DEFfaire.

enum Errors {
#define ERROR_DEF(x,y) x = y,
#include "errors.def"
#undef ERROR_DEF
};

static inline std::ostream & operator << (std::ostream &o, Errors e) {
    switch (e) {
    #define ERROR_DEF(x,y) case y: return o << #x"[" << y << "]";
    #include "errors.def"
    #undef ERROR_DEF
    default: return o << "unknown[" << e << "]";
    }
}

Si vous utilisez un outil de navigation source (comme cscope), vous devrez lui faire part du fichier externe.

jxh
la source
4

Il y a eu une discussion ici qui pourrait aider: Y a - t-il un moyen simple de convertir une énumération C ++ en chaîne?

UPDATE: Ici # un script pour Lua qui crée un opérateur << pour chaque énumération nommée qu'il rencontre. Cela peut nécessiter du travail pour que cela fonctionne pour les cas les moins simples [1]:

function make_enum_printers(s)
    for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do
    print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){') 
    for k in string.gmatch(body,"([%w_]+)[^,]*") do
    print('  case '..k..': return o<<"'..k..'";')
    end
    print('  default: return o<<"(invalid value)"; }}')
    end
end

local f=io.open(arg[1],"r")
local s=f:read('*a')
make_enum_printers(s)

Compte tenu de cette entrée:

enum Errors
{ErrorA=0, ErrorB, ErrorC};

enum Sec {
    X=1,Y=X,foo_bar=X+1,Z
};

Cela produit:

ostream& operator<<(ostream &o,Errors n) { switch(n){
  case ErrorA: return o<<"ErrorA";
  case ErrorB: return o<<"ErrorB";
  case ErrorC: return o<<"ErrorC";
  default: return o<<"(invalid value)"; }}
ostream& operator<<(ostream &o,Sec n) { switch(n){
  case X: return o<<"X";
  case Y: return o<<"Y";
  case foo_bar: return o<<"foo_bar";
  case Z: return o<<"Z";
  default: return o<<"(invalid value)"; }}

C'est donc probablement un début pour vous.

[1] énumérations dans des étendues différentes ou non d'espace de noms, énumérations avec des expressions d'initialisation qui contiennent un komma, etc.

Mainframe nordique
la source
N'est-ce pas une coutume ici de commenter un «-1» pour donner à l'affiche l'occasion de corriger sa réponse? Juste demander ..
Nordic Mainframe
2
Je pense que la solution Boost PP ci-dessous (de Philip) est meilleure, car l'utilisation d'outils externes est très coûteuse en termes de maintenance. mais non -1 parce que la réponse est par ailleurs valable
Fabio Fracassi
4
Le Boost PP est également un problème de maintenance, car il faut que tout le monde parle le métalangage Boost PP, ce qui est terrible , facile à casser (donnant des messages d'erreur généralement inutilisables) et seulement d'une utilisabilité limitée (lua / python / perl peut générer du code à partir de données externes). Cela renforce votre liste de dépendances, ce qui peut même ne pas être autorisé en raison de la stratégie du projet. En outre, il est invasif car il vous oblige à définir vos énumérations dans un DSL. Votre outil de code source ou IDE préféré pourrait avoir des problèmes avec cela. Et le dernier mais non le moindre: vous ne pouvez pas définir de point d'arrêt dans l'extension.
Nordic Mainframe
4

J'utilise un tableau de chaînes chaque fois que je définis une énumération:

Profile.h

#pragma once

struct Profile
{
    enum Value
    {
        Profile1,
        Profile2,
    };

    struct StringValueImplementation
    {
        const wchar_t* operator[](const Profile::Value profile)
        {
            switch (profile)
            {
            case Profile::Profile1: return L"Profile1";
            case Profile::Profile2: return L"Profile2";
            default: ASSERT(false); return NULL;
            }
        }
    };

    static StringValueImplementation StringValue;
};

Profile.cpp

#include "Profile.h"

Profile::StringValueImplementation Profile::StringValue;
Mark Ingram
la source
4

C'est un bon moyen,

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING };

Imprimez-le avec un tableau de tableaux de caractères

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ;

Comme ça

std::cout << rank_txt[m_rank - 1]
MrPickles7
la source
2
Et si mon énumération commence à 2000? Cette solution ne fonctionnera pas.
Sitesh
3
#include <iostream>
using std::cout;
using std::endl;

enum TEnum
{ 
  EOne,
  ETwo,
  EThree,
  ELast
};

#define VAR_NAME_HELPER(name) #name
#define VAR_NAME(x) VAR_NAME_HELPER(x)

#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x);

const char *State2Str(const TEnum state)
{
  switch(state)
  {
    CHECK_STATE_STR(EOne);
    CHECK_STATE_STR(ETwo);
    CHECK_STATE_STR(EThree);
    CHECK_STATE_STR(ELast);
    default:
      return "Invalid";
  }
}

int main()
{
  int myInt=12345;
  cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl;

  for(int i = -1; i < 5;   i)
    cout << i << " " << State2Str((TEnum)i) << endl;
  return 0;
}
Vladimir Chernyshev
la source
2

Vous pouvez utiliser un conteneur de carte stl ...

typedef map<Errors, string> ErrorMap;

ErrorMap m;
m.insert(ErrorMap::value_type(ErrorA, "ErrorA"));
m.insert(ErrorMap::value_type(ErrorB, "ErrorB"));
m.insert(ErrorMap::value_type(ErrorC, "ErrorC"));

Errors error = ErrorA;

cout << m[error] << endl;
Adrian Regan
la source
4
En quoi est-ce une carte meilleure que switch(n) { case XXX: return "XXX"; ... }? Lequel a une recherche O (1) et n'a pas besoin d'être initialisé? Ou les énumérations changent-elles d'une manière ou d'une autre pendant l'exécution?
Nordic Mainframe
Je suis d'accord avec @Luther Blissett sur l'utilisation de l'instruction switch (ou d'un pointeur de fonction aussi)
KedarX
1
Eh bien, il voudra peut-être afficher "Ceci mon cher ami Luther est l'erreur A ou" Ceci mon cher ami Adrian est l'erreur B. "De plus, l'utilisation de map supprime la dépendance aux signatures iostream, de sorte qu'il est libre de l'utiliser ailleurs dans le code avec concaténation de chaîne par exemple, string x = "Hello" + m [ErrorA], etc.
Adrian Regan
Je suis sûr que std :: map contient beaucoup de if et de commutateurs. Je lirais ceci comme `` comment puis-je faire cela sans me demander d' écrire si et commutateurs ''
Nordic Mainframe
Je suis sûr que c'est le cas, mais cela ne vous oblige certainement pas à écrire un script en Lua pour résoudre le problème ...
Adrian Regan
1

Pour ce problème, je fais une fonction d'aide comme celle-ci:

const char* name(Id id) {
    struct Entry {
        Id id;
        const char* name;
    };
    static const Entry entries[] = {
        { ErrorA, "ErrorA" },
        { ErrorB, "ErrorB" },
        { 0, 0 }
    }
    for (int it = 0; it < gui::SiCount; ++it) {
        if (entries[it].id == id) {
            return entries[it].name;
        }
    }
   return 0;
}

La recherche linéaire est généralement plus efficace que std::mappour les petites collections comme celle-ci.

Johan Kotlinski
la source
1

Cette solution ne vous oblige pas à utiliser des structures de données ou à créer un fichier différent.

En gros, vous définissez toutes vos valeurs d'énumération dans un #define, puis vous les utilisez dans l'opérateur <<. Très similaire à la réponse de @ jxh.

lien ideone pour l'itération finale: http://ideone.com/hQTKQp

Code complet:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\
ERROR_VALUE(FILE_NOT_FOUND)\
ERROR_VALUE(LABEL_UNINITIALISED)

enum class Error
{
#define ERROR_VALUE(NAME) NAME,
    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) case Error::NAME: return os << "[" << errVal << "]" #NAME;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

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

Production:

Error: [0]NO_ERROR
Error: [1]FILE_NOT_FOUND
Error: [2]LABEL_UNINITIALISED

Une bonne chose à faire de cette façon est que vous pouvez également spécifier vos propres messages personnalisés pour chaque erreur si vous pensez en avoir besoin:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised")

enum class Error
{
#define ERROR_VALUE(NAME,DESCR) NAME,
    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,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        return os << errVal;
    }
}

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

Production:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised

Si vous aimez rendre vos codes d'erreur / descriptions très descriptifs, vous ne les voudrez peut-être pas dans les versions de production. Les désactiver pour que seule la valeur soit imprimée est facile:

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
    #ifndef PRODUCTION_BUILD // Don't print out names in production builds
    #define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
        ERROR_VALUES
    #undef ERROR_VALUE
    #endif
    default:
        return os << errVal;
    }
}

Production:

Error: 0
Error: 1
Error: 2

Si tel est le cas, la recherche du numéro d'erreur 525 serait un PITA. Nous pouvons spécifier manuellement les nombres dans l'énumération initiale comme ceci:

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh")

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

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE  "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
        return os <<errVal;
    }
}
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
    {
        // If the error value isn't found (shouldn't happen)
        return os << static_cast<int>(err);
        break;
    }
    }
}

Production:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised
Error: [-1]UKNOWN_ERROR; Uh oh
Xiao
la source
0

Que dis-tu de ça?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

    std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl;

etc ... Je sais que c'est un exemple très artificiel mais je pense qu'il a une application là où c'est applicable et nécessaire et est certainement plus court que d'écrire un script pour cela.

user633658
la source
0

Utilisez le préprocesseur:

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \
    FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC)

enum Errors
{
    #define ENUMFIRST_ERROR(E)  E=0,
    #define ENUMMIDDLE_ERROR(E) E,
    #define ENUMLAST_ERROR(E)   E
    VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR)
    // you might undefine the 3 macros defined above
};

std::string toString(Error e)
{
    switch(e)
    {
    #define CASERETURN_ERROR(E)  case E: return #E;
    VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR)
    // you might undefine the above macro.
    // note that this will produce compile-time error for synonyms in enum;
    // handle those, if you have any, in a distinct macro

    default:
        throw my_favourite_exception();
    }
}

L'avantage de cette approche est que: - elle est toujours simple à comprendre, mais - elle permet diverses visites (pas seulement de la chaîne)

Si vous êtes prêt à abandonner le premier, créez vous-même une macro FOREACH (), puis #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)écrivez vos visiteurs en termes de FOREACH (). Ensuite, essayez de passer une revue de code :).

Lorro
la source