Supposons que nous ayons des énumérations nommées:
enum MyEnum {
FOO,
BAR = 0x50
};
Ce que je recherche dans Google, c’est un script (n’importe quel langage) qui analyse tous les en-têtes de mon projet et génère un en-tête avec une fonction par énumération.
char* enum_to_string(MyEnum t);
Et une implémentation avec quelque chose comme ça:
char* enum_to_string(MyEnum t){
switch(t){
case FOO:
return "FOO";
case BAR:
return "BAR";
default:
return "INVALID ENUM";
}
}
Le gotcha est vraiment avec des énumérations typées, et des énumérations de style C sans nom. Est-ce que quelqu'un sait quelque chose pour cela?
EDIT: La solution ne doit pas modifier mon source, sauf pour les fonctions générées. Les enums étant dans une API, l’utilisation des solutions proposées jusqu’à présent n’est tout simplement pas une option.
Vous voudrez peut-être consulter _ GCCXML .
L'exécution de GCCXML sur votre exemple de code produit:
<GCC_XML>
<Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
<Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
<Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
<EnumValue name="FOO" init="0"/>
<EnumValue name="BAR" init="80"/>
</Enumeration>
<File id="f0" name="my_enum.h"/>
</GCC_XML>
Vous pouvez utiliser la langue de votre choix pour extraire les balises Enumeration et EnumValue et générer le code souhaité.
Les macros X sont la meilleure solution. Exemple:
#include <iostream>
enum Colours {
# define X(a) a,
# include "colours.def"
# undef X
ColoursCount
};
char const* const colours_str[] = {
# define X(a) #a,
# include "colours.def"
# undef X
0
};
std::ostream& operator<<(std::ostream& os, enum Colours c)
{
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c];
}
int main()
{
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}
colours.def:
X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
Cependant, je préfère généralement la méthode suivante, de sorte qu'il est possible d'ajuster un peu la chaîne.
#define X(a, b) a,
#define X(a, b) b,
X(Red, "red")
X(Green, "green")
// etc.
@hydroo: Sans le fichier supplémentaire:
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable{
SOME_ENUM(MAKE_ENUM)
};
#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] = {
SOME_ENUM(MAKE_STRINGS)
};
Ce que j'ai tendance à faire est de créer un tableau C avec les noms dans le même ordre et la même position que les valeurs enum.
par exemple.
enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };
alors vous pouvez utiliser le tableau dans les endroits où vous voulez une valeur lisible par l'homme, par exemple
colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];
Vous pouvez expérimenter un peu l’opérateur de stringing (voir # dans la référence de votre préprocesseur) qui fera ce que vous voulez, dans certaines circonstances, par exemple:
#define printword(XX) cout << #XX;
printword(red);
imprimera "rouge" sur la sortie standard. Malheureusement, cela ne fonctionnera pas pour une variable (vous obtiendrez le nom de la variable imprimé)
J'ai une macro incroyablement simple à utiliser qui le fait complètement DRY. Cela implique des macros variadiques et de la magie d'analyse simple. Voici:
#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
else if(str[i] == ',') { \
strings.Push_back(temp.str()); \
temp.str(std::string());\
} \
else temp<< str[i]; \
} \
strings.Push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;}
Pour utiliser ceci dans votre code, faites simplement:
AWESOME_MAKE_ENUM(Animal,
DOG,
CAT,
HORSE
);
QT est capable de tirer celui de (grâce au compilateur de méta-objets): link
Je viens de réinventer cette roue aujourd'hui et je pensais la partager.
Cette implémentation nécessite non aucune modification du code définissant les constantes, qu'il s'agisse d'énumérations ou de #define
s, ou de tout autre élément déviant à un entier. Cela fonctionne également bien avec des valeurs clairsemées. Il permet même plusieurs noms pour la même valeur, renvoyant le premier toujours. Le seul inconvénient est que vous devez créer un tableau des constantes, qui peuvent devenir obsolètes lorsque de nouvelles sont ajoutées, par exemple.
struct IdAndName
{
int id;
const char * name;
bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }
const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
std::stable_sort(table_begin, table_end);
IdAndName searchee = { id, NULL };
IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
return (p == table_end || p->id != id) ? NULL : p->name;
}
template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
return IdToName(id, &table[0], &table[N]);
}
Un exemple de comment vous l'utiliseriez:
static IdAndName WindowsErrorTable[] =
{
ID_AND_NAME(INT_MAX), // flag value to indicate unsorted table
ID_AND_NAME(NO_ERROR),
ID_AND_NAME(ERROR_INVALID_FUNCTION),
ID_AND_NAME(ERROR_FILE_NOT_FOUND),
ID_AND_NAME(ERROR_PATH_NOT_FOUND),
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
ID_AND_NAME(ERROR_ACCESS_DENIED),
ID_AND_NAME(ERROR_INVALID_HANDLE),
ID_AND_NAME(ERROR_ARENA_TRASHED),
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
ID_AND_NAME(ERROR_INVALID_BLOCK),
ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
ID_AND_NAME(ERROR_BAD_FORMAT),
ID_AND_NAME(ERROR_INVALID_ACCESS),
ID_AND_NAME(ERROR_INVALID_DATA),
ID_AND_NAME(ERROR_INVALID_DRIVE),
ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
ID_AND_NAME(ERROR_NO_MORE_FILES)
};
const char * error_name = IdToName(GetLastError(), WindowsErrorTable);
La fonction IdToName
s'appuie sur std::lower_bound
pour effectuer des recherches rapides, ce qui nécessite que la table soit triée. Si les deux premières entrées de la table sont en panne, la fonction les triera automatiquement.
Edit: Un commentaire m'a fait penser à une autre façon d'utiliser le même principe. Une macro simplifie la génération d'une grande instruction switch
.
#define ID_AND_NAME(x) case x: return #x
const char * WindowsErrorToName(int id)
{
switch(id)
{
ID_AND_NAME(ERROR_INVALID_FUNCTION);
ID_AND_NAME(ERROR_FILE_NOT_FOUND);
ID_AND_NAME(ERROR_PATH_NOT_FOUND);
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
ID_AND_NAME(ERROR_ACCESS_DENIED);
ID_AND_NAME(ERROR_INVALID_HANDLE);
ID_AND_NAME(ERROR_ARENA_TRASHED);
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
ID_AND_NAME(ERROR_INVALID_BLOCK);
ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
ID_AND_NAME(ERROR_BAD_FORMAT);
ID_AND_NAME(ERROR_INVALID_ACCESS);
ID_AND_NAME(ERROR_INVALID_DATA);
ID_AND_NAME(ERROR_INVALID_DRIVE);
ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
ID_AND_NAME(ERROR_NO_MORE_FILES);
default: return NULL;
}
}
#define stringify( name ) # name
enum MyEnum {
ENUMVAL1
};
...stuff...
stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1
Suite de la discussion sur cette méthode
Astuces de directive du préprocesseur pour les nouveaux venus
Intéressant de voir le nombre de façons. en voici un que j'ai utilisé il y a longtemps:
dans le fichier myenummap.h:
#include <map>
#include <string>
enum test{ one, two, three, five=5, six, seven };
struct mymap : std::map<unsigned int, std::string>
{
mymap()
{
this->operator[]( one ) = "ONE";
this->operator[]( two ) = "TWO";
this->operator[]( three ) = "THREE";
this->operator[]( five ) = "FIVE";
this->operator[]( six ) = "SIX";
this->operator[]( seven ) = "SEVEN";
};
~mymap(){};
};
dans main.cpp
#include "myenummap.h"
...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;
Ce n'est pas const, mais c'est pratique.
Voici une autre façon d'utiliser les fonctionnalités de C++ 11. C'est const, n'hérite pas d'un conteneur STL et est un peu plus ordonné:
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
//These stay together and must be modified together
enum test{ one, two, three, five=5, six, seven };
std::string enum_to_str(test const& e)
{
typedef std::pair<int,std::string> mapping;
auto m = [](test const& e,std::string const& s){return mapping(static_cast<int>(e),s);};
std::vector<mapping> const nummap =
{
m(one,"one"),
m(two,"two"),
m(three,"three"),
m(five,"five"),
m(six,"six"),
m(seven,"seven"),
};
for(auto i : nummap)
{
if(i.first==static_cast<int>(e))
{
return i.second;
}
}
return "";
}
int main()
{
// std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
return 0;
}
#include <stdarg.h>
#include <algorithm>
#include <string>
#include <vector>
#include <sstream>
#include <map>
#define SMART_ENUM(EnumName, ...) \
class EnumName \
{ \
private: \
static std::map<int, std::string> nameMap; \
public: \
enum {__VA_ARGS__}; \
private: \
static std::map<int, std::string> initMap() \
{ \
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.Push_back(buf_1); \
map<int, string> tmp; \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
{ \
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
} \
return tmp; \
} \
public: \
static std::string toString(int aInt) \
{ \
return nameMap[aInt]; \
} \
}; \
std::map<int, std::string> \
EnumName::nameMap = EnumName::initMap();
Usage:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);
Cela peut être fait en C++ 11
#include <map>
enum MyEnum { AA, BB, CC, DD };
static std::map< MyEnum, const char * > info = {
{AA, "This is an Apple"},
{BB, "This is a book"},
{CC, "This is a coffee"},
{DD, "This is a door"}
};
void main()
{
std::cout << info[AA] << endl
<< info[BB] << endl
<< info[CC] << endl
<< info[DD] << endl;
}
Ceci est une modification de la réponse @ user3360260. Il a les nouvelles fonctionnalités suivantes
MyEnum fromString(const string&)
supportUsage:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo); // static method
cout << foo.toString(); // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");
// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
{
cout << x.toString() << endl;
}
Voici le code
#define SMART_ENUM(EnumName, ...) \
class EnumName \
{ \
public: \
EnumName() : value(0) {} \
EnumName(int x) : value(x) {} \
public: \
enum {__VA_ARGS__}; \
private: \
static void initMap(std::map<int, std::string>& tmp) \
{ \
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.Push_back(buf_1); \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
{ \
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
} \
} \
int value; \
public: \
operator int () const { return value; } \
std::string toString(void) const { \
return toString(value); \
} \
static std::string toString(int aInt) \
{ \
return nameMap()[aInt]; \
} \
static EnumName fromString(const std::string& s) \
{ \
auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) { \
return p.second == s; \
}); \
if (it == nameMap().end()) { \
/*value not found*/ \
throw EnumName::Exception(); \
} else { \
return EnumName(it->first); \
} \
} \
class Exception : public std::exception {}; \
static std::map<int,std::string>& nameMap() { \
static std::map<int,std::string> nameMap0; \
if (nameMap0.size() ==0) initMap(nameMap0); \
return nameMap0; \
} \
static std::vector<EnumName> allValues() { \
std::vector<EnumName> x{ __VA_ARGS__ }; \
return x; \
} \
bool operator<(const EnumName a) const { return (int)*this < (int)a; } \
};
Notez que la conversion toString est une recherche rapide, alors que la conversion fromString est une recherche linéaire lente. Mais les chaînes sont quand même si chères (et le fichier IO associé) que je n’ai pas ressenti le besoin d’optimiser ou d’utiliser une bimap.
La solution macro de Suma est Nice. Vous n'avez cependant pas besoin de deux macros différentes. C++ sera heureux d'inclure un en-tête deux fois. Laissez juste le garde d'inclusion.
Donc, vous auriez un foobar.h définissant seulement
ENUM(Foo, 1)
ENUM(Bar, 2)
et vous l'incluriez comme ceci:
#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"
enumfactory.h fera 2 #include ENUMFACTORY_ARGUMENT
s. Au premier tour, il développe ENUM comme le DECLARE_ENUM
de Suma; au deuxième tour, ENUM fonctionne comme DEFINE_ENUM
.
Vous pouvez également inclure enumfactory.h plusieurs fois, à condition que vous passiez dans différentes définitions pour ENUMFACTORY_ARGUMENT
Autre réponse: dans certains contextes, il est judicieux de définir votre énumération dans un format autre que le code, tel qu'un fichier CSV, YAML ou XML, puis de générer le code d’énumération C++ et le code de chaîne de la définition. Cette approche peut être ou ne pas être pratique dans votre application, mais c'est quelque chose à garder à l'esprit.
Ajoutant encore plus de simplicité d'utilisation à La réponse fantastique de Jasper Bekkers :
Mettre en place une fois:
#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
enum enumName { \
source(MAKE_ENUM) \
};\
const char* const enumStringName[] = { \
source(MAKE_STRINGS) \
};
Ensuite, pour une utilisation:
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)
Je le fais avec des classes de wrapper énumérées côte à côte qui sont générées avec des macros. Il y a plusieurs avantages:
L’inconvénient, bien sûr, c’est que je dois dupliquer les valeurs enum dans les classes de formatage et je n’ai aucun script pour les générer. Cela mis à part, cela semble plutôt bien fonctionner.
Voici un exemple d’énumération de ma base de code, sans tout le code de framework qui implémente les macros et les modèles, mais vous pouvez en avoir l’idée:
enum EHelpLocation
{
HELP_LOCATION_UNKNOWN = 0,
HELP_LOCAL_FILE = 1,
HELP_HTML_ONLINE = 2,
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
static inline CString FormatEnum( EHelpLocation eValue )
{
switch ( eValue )
{
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
default:
return FormatAsNumber( eValue );
}
}
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;
L'idée est alors qu'au lieu d'utiliser EHelpLocation, vous utilisez SEHelpLocation; tout fonctionne de la même manière, mais vous obtenez une vérification de plage et une méthode 'Format ()' sur la variable enum elle-même. Si vous devez formater une valeur autonome, vous pouvez utiliser CEnumFormatter_EHelpLocation :: FormatEnum (...).
J'espère que c'est utile. Je me rends compte que cela ne résout pas non plus la question initiale à propos d'un script pour générer réellement l'autre classe, mais j'espère que la structure aide quelqu'un qui tente de résoudre le même problème ou d'écrire un tel script.
Notez que votre fonction de conversion devrait idéalement renvoyer un const char *.
Si vous pouvez vous permettre de mettre vos enums dans leurs fichiers d'en-tête séparés, vous pourriez peut-être faire quelque chose comme ça avec des macros (oh, ça sera moche):
#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"
Où enum_def.h a:
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME {
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END };
Et enum_conv.h a:
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) {
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value"; } }
Et enfin, color.h a:
ENUM_START(colour)
ENUM_ADD(red, 0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue, 0x0000ff)
ENUM_END
Et vous pouvez utiliser la fonction de conversion en tant que:
printf("%s", colour_to_string(colour::red));
C’est moche, mais c’est le seul moyen (au niveau du préprocesseur) qui vous permet de définir votre enum à un seul endroit de votre code. Votre code n'est donc pas sujet aux erreurs en raison de modifications apportées à l'énumération. Votre définition enum et la fonction de conversion seront toujours synchronisées. Cependant, je le répète, c'est moche :)
Voici une solution mono-fichier (basée sur une réponse élégante de @Marcin:
#include <iostream>
#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \
enum Colours {
# define X(a) a,
ENUM_TXT
# undef X
ColoursCount
};
char const* const colours_str[] = {
# define X(a) #a,
ENUM_TXT
# undef X
0
};
std::ostream& operator<<(std::ostream& os, enum Colours c)
{
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c] << std::endl;
}
int main()
{
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}
C'était ma solution avec BOOST:
#include <boost/preprocessor.hpp>
#define X_STR_ENUM_TOSTRING_CASE(r, data, elem) \
case elem : return BOOST_PP_STRINGIZE(elem);
#define X_ENUM_STR_TOENUM_IF(r, data, elem) \
else if(data == BOOST_PP_STRINGIZE(elem)) return elem;
#define STR_ENUM(name, enumerators) \
enum name { \
BOOST_PP_SEQ_ENUM(enumerators) \
}; \
\
inline const QString enumToStr(name v) \
{ \
switch (v) \
{ \
BOOST_PP_SEQ_FOR_EACH( \
X_STR_ENUM_TOSTRING_CASE, \
name, \
enumerators \
) \
\
default: \
return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \
} \
} \
\
template <typename T> \
inline const T strToEnum(QString v); \
\
template <> \
inline const name strToEnum(QString v) \
{ \
if(v=="") \
throw std::runtime_error("Empty enum value"); \
\
BOOST_PP_SEQ_FOR_EACH( \
X_ENUM_STR_TOENUM_IF, \
v, \
enumerators \
) \
\
else \
throw std::runtime_error( \
QString("[Unknown value %1 for enum %2]") \
.arg(v) \
.arg(BOOST_PP_STRINGIZE(name)) \
.toStdString().c_str()); \
}
Pour créer une énumération, déclarez:
STR_ENUM
(
SERVICE_RELOAD,
(reload_log)
(reload_settings)
(reload_qxml_server)
)
Pour les conversions:
SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);
Un problème avec la réponse 0 est que les valeurs binaires enum ne commencent pas nécessairement à 0 et ne sont pas nécessairement contiguës.
Quand j'ai besoin de ça, j'ai l'habitude de:
Vous pouvez utiliser une bibliothèque de réflexion, telle que Ponder . Vous enregistrez les enums et vous pouvez ensuite les convertir avec l’API.
enum class MyEnum
{
Zero = 0,
One = 1,
Two = 2
};
ponder::Enum::declare<MyEnum>()
.value("Zero", MyEnum::Zero)
.value("One", MyEnum::One)
.value("Two", MyEnum::Two);
ponder::EnumObject zero(MyEnum::Zero);
zero.name(); // -> "Zero"
Le script Ruby suivant tente d'analyser les en-têtes et compile les sources requises à côté des en-têtes d'origine.
#! /usr/bin/env Ruby
# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs
GLOBS = [
"toto/*.h",
"tutu/*.h",
"tutu/*.hxx"
]
enums = {}
GLOBS.each { |glob|
Dir[glob].each { |header|
enums[header] = File.open(header, 'rb') { |f|
f.read
}.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
[
enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
enum_key_and_value.split(/\s*=\s*/).first
}
]
}
}
}
# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with Ruby
require 'erb'
template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1
#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);
#endif
EOS
template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"
char* enum_to_string(<%= enum_name %> e)
{
switch (e)
{<% enum_keys.each do |enum_key| %>
case <%= enum_key %>: return "<%= enum_key %>";<% end %>
default: return "INVALID <%= enum_name %> VALUE";
}
}
EOS
enums.each { |header, enum_name_and_keys|
enum_name_and_keys.each { |enum_name, enum_keys|
File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
built_h.write(template_h.result(binding))
}
File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
built_cpp.write(template_cpp.result(binding))
}
}
}
L'utilisation d'expressions régulières rend cet "analyseur" très fragile, il peut ne pas être en mesure de gérer vos en-têtes spécifiques avec élégance.
Supposons que vous avez un en-tête toto/a.h, contenant les définitions pour les enums MyEnum et MyEnum2. Le script va construire:
toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp
Des solutions plus robustes seraient:
C'est un logiciel inédit mais il semble que BOOST_ENUM de Frank Laub pourrait convenir. Ce que j’aime bien, c’est que vous pouvez définir une énumération dans le cadre d’une classe que la plupart des énumérations basées sur des macros ne vous permettent généralement pas de faire. Il est situé dans le coffre-fort Boost à l'adresse: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.Zip&directory=& Il n'a pas connu de développement. depuis 2006, je ne sais donc pas à quel point il compile bien avec les nouvelles versions de Boost . Regardez sous libs/test pour un exemple d'utilisation.
Il n'y a pas si longtemps, j'ai fait quelque chose pour que les énumérations soient correctement affichées dans QComboBox et pour que la définition des représentations d'énum et de chaînes soit considérée comme une seule déclaration.
#pragma once
#include <boost/unordered_map.hpp>
namespace enumeration
{
struct enumerator_base : boost::noncopyable
{
typedef
boost::unordered_map<int, std::wstring>
kv_storage_t;
typedef
kv_storage_t::value_type
kv_type;
kv_storage_t const & kv() const
{
return storage_;
}
LPCWSTR name(int i) const
{
kv_storage_t::const_iterator it = storage_.find(i);
if(it != storage_.end())
return it->second.c_str();
return L"empty";
}
protected:
kv_storage_t storage_;
};
template<class T>
struct enumerator;
template<class D>
struct enum_singleton : enumerator_base
{
static enumerator_base const & instance()
{
static D inst;
return inst;
}
};
}
#define QENUM_ENTRY(K, V, N) K, N storage_.insert(std::make_pair((int)K, V));
#define QBEGIN_ENUM(NAME, C) \
enum NAME \
{ \
C \
} \
}; \
} \
#define QEND_ENUM(NAME) \
}; \
namespace enumeration \
{ \
template<> \
struct enumerator<NAME>\
: enum_singleton< enumerator<NAME> >\
{ \
enumerator() \
{
//usage
/*
QBEGIN_ENUM(test_t,
QENUM_ENTRY(test_entry_1, L"number uno",
QENUM_ENTRY(test_entry_2, L"number dos",
QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/
Maintenant, vous avez enumeration::enum_singleton<your_enum>::instance()
capable de convertir des énums en chaînes. Si vous remplacez kv_storage_t
par boost::bimap
, vous pourrez également effectuer une conversion vers l'arrière . Une classe de base commune pour le convertisseur a été introduite pour le stocker dans un objet Qt, car les objets Qt ne peuvent pas être des modèles.
Voici un programme de la CLI que j’ai écrit pour convertir facilement des énums en chaînes . Il est facile à utiliser et prend environ 5 secondes pour le faire (y compris le temps nécessaire pour accéder au répertoire contenant le programme, puis l’exécuter, en passant à il le fichier contenant l'énum).
Téléchargez ici: http://www.mediafire.com/?nttignoozzz
Sujet de discussion à ce sujet ici: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html
Exécutez le programme avec l’argument "--help" pour obtenir une description de son utilisation.
Je suis tombé sur cette question lorsque je cherchais une solution à mon propre problème pour imprimer les "mots" de l'énumération en C++. Je suis revenu pour fournir une solution simple qui répond à la question posée telle que formulée. Tout ce qui est nécessaire est de "refléter" la liste enum avec un vecteur.
enum class genre { Fiction, NonFiction, Periodical, Biography, Children };
vector<string>genre_tbl { "Fiction", "NonFiction", "Periodical", "Biography", "Children" };
Parce que l'énumération telle que tapée ci-dessus fera ceci par défaut:
Fiction = 0
NonFiction = 1
Periodical = 2
Biography = 3
Children = 4
Cela correspond aux positions vectorielles, ce qui facilite énormément la conversion énumération-chaîne.
string s1 = genre_tbl[int(genre::fiction)];
Pour mon problème, j'ai créé une classe définie par l'utilisateur appelée Book avec un membre appelé Gen de type genre. Le programme devait pouvoir imprimer le genre en tant que mot.
class book {...};
ostream& operator<<(ostream& os, genre g) { return os << genre_tbl[int(g)]; }
book b1;
b1.Gen = genre(0)
cout << b1.Gen;
Pour quelle "Fiction" imprimera à l'écran dans ce cas.
Eh bien, encore une autre option. Un cas d'utilisation typique est celui où vous avez besoin d'une constante pour les verbes HTTP, ainsi que l'utilisation de valeurs de version de chaîne.
L'exemple:
int main () {
VERB a = VERB::GET;
VERB b = VERB::GET;
VERB c = VERB::POST;
VERB d = VERB::PUT;
VERB e = VERB::DELETE;
std::cout << a.toString() << std::endl;
std::cout << a << std::endl;
if ( a == VERB::GET ) {
std::cout << "yes" << std::endl;
}
if ( a == b ) {
std::cout << "yes" << std::endl;
}
if ( a != c ) {
std::cout << "no" << std::endl;
}
}
La classe VERBE:
// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {
private:
// private constants
enum Verb {GET_=0, POST_, PUT_, DELETE_};
// private string values
static const std::string theStrings[];
// private value
const Verb value;
const std::string text;
// private constructor
VERB (Verb v) :
value(v), text (theStrings[v])
{
// std::cout << " constructor \n";
}
public:
operator const char * () const { return text.c_str(); }
operator const std::string () const { return text; }
const std::string toString () const { return text; }
bool operator == (const VERB & other) const { return (*this).value == other.value; }
bool operator != (const VERB & other) const { return ! ( (*this) == other); }
// ---
static const VERB GET;
static const VERB POST;
static const VERB PUT;
static const VERB DELETE;
};
const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};
const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file
Voici une tentative pour obtenir << et >> les opérateurs de flux sur enum automatiquement avec une commande de macro d'une ligne uniquement.
Définitions:
#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str
#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)
#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \
attribute std::istream& operator>>(std::istream& is, name& e) { \
const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
std::string str; \
std::istream& r = is >> str; \
const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
const std::vector<std::string> enumStr(name##Str, name##Str + len); \
const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
if (it != enumStr.end())\
e = name(it - enumStr.begin()); \
else \
throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
return r; \
}; \
attribute std::ostream& operator<<(std::ostream& os, const name& e) { \
const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
return (os << name##Str[e]); \
}
Usage:
// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);
class Essai {
public:
// Declare enum inside class
enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);
};
int main() {
std::cout << Essai::Item1 << std::endl;
Essai::Test ddd = Essai::Item1;
std::cout << ddd << std::endl;
std::istringstream strm("Item2");
strm >> ddd;
std::cout << (int) ddd << std::endl;
std::cout << ddd << std::endl;
}
Pas sûr des limites de ce schéma cependant ... les commentaires sont les bienvenus!
Je veux poster ceci au cas où quelqu'un le trouverait utile.
Dans mon cas, je dois simplement générer les fonctions ToString()
et FromString()
pour un seul enum C++ 11 à partir d'un seul fichier .hpp
.
J'ai écrit un script python qui analyse le fichier d'en-tête contenant les éléments enum et génère les fonctions dans un nouveau fichier .cpp
.
Vous pouvez ajouter ce script dans CMakeLists.txt avec execute_process , ou sous forme d'événement de pré-génération dans Visual Studio. Le fichier .cpp
sera généré automatiquement, sans qu'il soit nécessaire de le mettre à jour manuellement chaque fois qu'un nouvel élément enum est ajouté.
generate_enum_strings.py
# This script is used to generate strings from C++ enums
import re
import sys
import os
fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])
with open(fileName, 'r') as f:
content = f.read().replace('\n', '')
searchResult = re.search('enum(.*)\{(.*?)\};', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)
textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '{\n'
textOut += ' std::string ToString(ErrorCode errorCode)\n'
textOut += ' {\n'
textOut += ' switch (errorCode)\n'
textOut += ' {\n'
for token in tokens:
textOut += ' case ' + enumName + '::' + token + ':\n'
textOut += ' return "' + token + '";\n'
textOut += ' default:\n'
textOut += ' return "Last";\n'
textOut += ' }\n'
textOut += ' }\n'
textOut += '\n'
textOut += ' ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += ' {\n'
textOut += ' if ("' + tokens[0] + '" == errorCode)\n'
textOut += ' {\n'
textOut += ' return ' + enumName + '::' + tokens[0] + ';\n'
textOut += ' }\n'
for token in tokens[1:]:
textOut += ' else if("' + token + '" == errorCode)\n'
textOut += ' {\n'
textOut += ' return ' + enumName + '::' + token + ';\n'
textOut += ' }\n'
textOut += '\n'
textOut += ' return ' + enumName + '::Last;\n'
textOut += ' }\n'
textOut += '}\n'
fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)
Exemple:
ErrorCode.hpp
#pragma once
#include <string>
#include <cstdint>
namespace myns
{
enum class ErrorCode : uint32_t
{
OK = 0,
OutOfSpace,
ConnectionFailure,
InvalidJson,
DatabaseFailure,
HttpError,
FileSystemError,
FailedToEncrypt,
FailedToDecrypt,
EndOfFile,
FailedToOpenFileForRead,
FailedToOpenFileForWrite,
FailedToLaunchProcess,
Last
};
std::string ToString(ErrorCode errorCode);
ErrorCode FromString(const std::string &errorCode);
}
Exécuter python generate_enum_strings.py ErrorCode.hpp
Résultat:
ErrorCode.cpp
#include "ErrorCode.hpp"
namespace myns
{
std::string ToString(ErrorCode errorCode)
{
switch (errorCode)
{
case ErrorCode::OK:
return "OK";
case ErrorCode::OutOfSpace:
return "OutOfSpace";
case ErrorCode::ConnectionFailure:
return "ConnectionFailure";
case ErrorCode::InvalidJson:
return "InvalidJson";
case ErrorCode::DatabaseFailure:
return "DatabaseFailure";
case ErrorCode::HttpError:
return "HttpError";
case ErrorCode::FileSystemError:
return "FileSystemError";
case ErrorCode::FailedToEncrypt:
return "FailedToEncrypt";
case ErrorCode::FailedToDecrypt:
return "FailedToDecrypt";
case ErrorCode::EndOfFile:
return "EndOfFile";
case ErrorCode::FailedToOpenFileForRead:
return "FailedToOpenFileForRead";
case ErrorCode::FailedToOpenFileForWrite:
return "FailedToOpenFileForWrite";
case ErrorCode::FailedToLaunchProcess:
return "FailedToLaunchProcess";
case ErrorCode::Last:
return "Last";
default:
return "Last";
}
}
ErrorCode FromString(const std::string &errorCode)
{
if ("OK" == errorCode)
{
return ErrorCode::OK;
}
else if("OutOfSpace" == errorCode)
{
return ErrorCode::OutOfSpace;
}
else if("ConnectionFailure" == errorCode)
{
return ErrorCode::ConnectionFailure;
}
else if("InvalidJson" == errorCode)
{
return ErrorCode::InvalidJson;
}
else if("DatabaseFailure" == errorCode)
{
return ErrorCode::DatabaseFailure;
}
else if("HttpError" == errorCode)
{
return ErrorCode::HttpError;
}
else if("FileSystemError" == errorCode)
{
return ErrorCode::FileSystemError;
}
else if("FailedToEncrypt" == errorCode)
{
return ErrorCode::FailedToEncrypt;
}
else if("FailedToDecrypt" == errorCode)
{
return ErrorCode::FailedToDecrypt;
}
else if("EndOfFile" == errorCode)
{
return ErrorCode::EndOfFile;
}
else if("FailedToOpenFileForRead" == errorCode)
{
return ErrorCode::FailedToOpenFileForRead;
}
else if("FailedToOpenFileForWrite" == errorCode)
{
return ErrorCode::FailedToOpenFileForWrite;
}
else if("FailedToLaunchProcess" == errorCode)
{
return ErrorCode::FailedToLaunchProcess;
}
else if("Last" == errorCode)
{
return ErrorCode::Last;
}
return ErrorCode::Last;
}
}
C'est à peu près la seule façon dont cela peut être fait (un tableau de chaînes pourrait également fonctionner).
Le problème est qu'une fois qu'un programme C est compilé, la valeur binaire de l'énum est tout ce qui est utilisé, et le nom est parti.
Cette question est une copie de,
Cependant, dans aucune des questions, j'ai pu trouver de bonnes réponses.
Après avoir approfondi le sujet, j'ai trouvé deux excellentes solutions open source:
Remarque: je répète la recommandation ici. Cette question a beaucoup de trafic/vues et nécessite vraiment d'énumérer les solutions ci-dessus.
#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)
std::map<int , std::string> enToStr;
class mapEnumtoString
{
public:
mapEnumtoString(){ }
mapEnumtoString& operator()(int i,std::string str)
{
enToStr[i] = str;
return *this;
}
public:
std::string operator [] (int i)
{
return enToStr[i];
}
};
mapEnumtoString k;
mapEnumtoString& init()
{
return k;
}
int main()
{
init()
IDMAP(1)
IDMAP(2)
IDMAP(3)
IDMAP(4)
IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
}
Vérifiez ce post:
Implémentation en classe de C++ Enums
il contient une implémentation de classe de c ++ enum.
Comme variante, utilisez simple lib> http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C
Dans le code
#include <EnumString.h>
enum FORM {
F_NONE = 0,
F_BOX,
F_CUBE,
F_SPHERE,
};
ajouter des lignes
Begin_Enum_String( FORM )
{
Enum_String( F_NONE );
Enum_String( F_BOX );
Enum_String( F_CUBE );
Enum_String( F_SPHERE );
}
End_Enum_String;
Fonctionne bien, si les valeurs enum ne sont pas dupliquées.
Exemple d'utilisation
enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );
et vice versa
assert( EnumString< FORM >::To( f, str ) );
L'utilisation de déclarations ternaires composées peut être assez élégante pour les énumérations comportant peu d'éléments (une ligne). L'expression ne grossit que de manière approximativement linéaire en longueur avec le nombre d'éléments.
Voici un bon cas d'utilisation:
enum log_level {INFO, WARNING, ERROR};
...
void logger::write(const std::string log, const log_level l) {
...
std::string s = (l == INFO) ? "INFO" :
(l == WARNING) ? "WARNING" :
(l == ERROR) ? "ERROR" : "UNKNOWN";
...
}
...
Bien sûr, il ne s'agit que d'un autre bloc d'instructions switch/if, mais il s'agit d'une instruction à une seule ligne. Et en ce qui concerne la concision et la simplicité, il se situe quelque part au milieu. En tant qu’expression constante, elle peut également être facilement transformée en une fonction en ligne.