énumération de chaîne en C ++ 11 / C ++ 14 / C ++ 17 moderne et C ++ 20 futur


354

Contrairement à toutes les autres questions similaires, cette question concerne l'utilisation des nouvelles fonctionnalités C ++.

Après avoir lu de nombreuses réponses, je n'en ai pas encore trouvé:

  • Façon élégante en utilisant C ++ 11 , C ++ 14 ou C ++ 17 nouvelles fonctionnalités
  • Ou quelque chose de prêt à l'emploi dans Boost
  • Sinon, quelque chose de prévu pour C ++ 20

Exemple

Un exemple vaut souvent mieux qu'une longue explication.
Vous pouvez compiler et exécuter cet extrait sur Coliru .
( Un autre ancien exemple est également disponible)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

Contraintes

  • S'il vous plaît pas de duplication sans valeur d' autres réponses ou lien de base .
  • Veuillez éviter les réponses basées sur des macros, ou essayez de réduire au #defineminimum les frais généraux.
  • S'il vous plaît pas de manuel enum-> stringcartographie.

Agréable d'avoir

  • Prend en charge les enumvaleurs à partir d'un nombre différent de zéro
  • Prend en charge les enumvaleurs négatives
  • Prise en charge de enumvaleurs fragmentées
  • Prise en charge class enum(C ++ 11)
  • Support class enum : <type>ayant tout autorisé <type>(C ++ 11)
  • Conversions au moment de la compilation (pas au moment de l'exécution) en chaîne,
    ou au moins exécution rapide au moment de l'exécution (par exemple, ce std::mapn'est pas une bonne idée ...)
  • constexpr (C ++ 11, puis détendu en C ++ 14/17/20)
  • noexcept (C ++ 11)
  • Extrait convivial C ++ 17 / C ++ 20

Une idée possible pourrait être d'utiliser les capacités du compilateur C ++ pour générer du code C ++ au moment de la compilation en utilisant des astuces de méta-programmation basées sur variadic template classet des constexprfonctions ...


4
(peut-être du sujet) regardez ce blog lié à Qt. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Décrit une possibilité de remplacer le moc de Qt (méta-objet-compilateur) en utilisant la réflexion C ++ (norme proposée).
ibre5041

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
ecatmur

1
J'ai personnellement résolu ce problème en implémentant une petite bibliothèque d'utilitaires de préprocesseur qui me permet de parcourir les arguments de macros variadiques et d'effectuer une fonction sur chacun d'eux. Je passe les valeurs d'énumération en tant qu'arguments de macro et je génère automatiquement l'énumération et le tableau de chaînes via le préprocesseur. Vous pouvez probablement le faire en utilisant également Boost Preprocessor.
Vittorio Romeo

2
faut-il tout résoudre avec C ++? Il est si facile de générer automatiquement du code pour la représentation sous forme de chaîne, juste quelques lignes de code.
Karoly Horvath

2
"Veuillez ne pas fournir de réponses basées sur les macros C si possible" et bien, à moins que vous ne souhaitiez attendre C ++ 17, il n'y a pratiquement rien utilisable, et ce n'est pas si mal de déclarer vos énumérations comme DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))si vous n'avez pas à maintenir le générer des macros ... et à mon humble avis mettre de tels cas dans le langage n'est qu'un autre type de piratage au lieu d'un hybride modèle / macro plus puissant. Nous ne devons pas ajouter toutes ces utilisations utiles de macros dans la langue juste pour pouvoir dire que les macros n'ont plus d'utilité.
PlasmaHH

Réponses:


43

La bibliothèque uniquement d'en-tête Magic Enum fournit une réflexion statique pour les énumérations (vers chaîne, à partir de chaîne, itération) pour C ++ 17.

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

Pour plus d'exemples, consultez le référentiel d'accueil https://github.com/Neargye/magic_enum .

Où est l'inconvénient?

Cette bibliothèque utilise un hack spécifique au compilateur (basé sur __PRETTY_FUNCTION__/ __FUNCSIG__), qui fonctionne sur Clang> = 5, MSVC> = 15.3 et GCC> = 9.

La valeur énumérée doit être dans la plage [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Par défaut MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Si vous avez besoin d'une autre plage pour tous les types d'énumérations par défaut, redéfinissez la macro MAGIC_ENUM_RANGE_MINet MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINdoit être inférieur ou égal à 0et doit être supérieur à INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXdoit être supérieur à 0et doit être inférieur à INT16_MAX.

  • Si vous avez besoin d'une autre plage pour un type d'énumération spécifique, ajoutez la spécialisation enum_range pour le type d'énumération nécessaire.

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }

Pourquoi les limites de portée? Est-ce pour limiter une sorte de profondeur de récursivité, ou à cause d'une sorte de recherche linéaire au moment de la compilation?
Emile Cormier

Ceci est incroyable. Je vous remercie! C'est probablement même efficace si le compilateur est suffisamment intelligent pour évaluer une seule fois le constexpr std :: array. Très très gentil.
Iestyn

87

(L'approche de la bibliothèque better_enums )

Il existe un moyen de faire une énumération de chaîne dans le C ++ actuel qui ressemble à ceci:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };

Usage:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

Toutes les opérations peuvent être effectuées constexpr. Vous pouvez également implémenter la proposition de réflexion C ++ 17 mentionnée dans la réponse de @ecatmur.

  • Il n'y a qu'une seule macro. Je pense que c'est le minimum possible, car le préprocesseur stringization ( #) est le seul moyen de convertir un jeton en chaîne en C ++ actuel.
  • La macro est assez discrète - les déclarations constantes, y compris les initialiseurs, sont collées dans une déclaration d'énumération intégrée. Cela signifie qu'ils ont la même syntaxe et la même signification que dans une énumération intégrée.
  • La répétition est éliminée.
  • L'implémentation est la plus naturelle et utile dans au moins C ++ 11, en raison de constexpr. Il peut également être conçu pour fonctionner avec C ++ 98 + __VA_ARGS__. C'est définitivement du C ++ moderne.

La définition de la macro est quelque peu impliquée, donc je réponds à cela de plusieurs façons.

  • La majeure partie de cette réponse est une implémentation qui, je pense, convient aux contraintes d'espace sur StackOverflow.
  • Il existe également un article CodeProject décrivant les bases de l'implémentation dans un didacticiel détaillé. [ Dois-je le déplacer ici? Je pense que c'est trop pour une réponse SO ].
  • Il existe une bibliothèque complète "Better Enums" qui implémente la macro dans un seul fichier d'en-tête. Il implémente également N4428 Type Property Queries , la révision actuelle de la proposition de réflexion C411 17 N4113. Ainsi, au moins pour les énumérations déclarées via cette macro, vous pouvez maintenant avoir la réflexion d'énumération C ++ 17 proposée, en C ++ 11 / C ++ 14.

Il est simple d'étendre cette réponse aux fonctionnalités de la bibliothèque - rien «important» n'est laissé ici. C'est cependant assez fastidieux et il y a des problèmes de portabilité du compilateur.

Avertissement : je suis l'auteur à la fois de l'article CodeProject et de la bibliothèque.

Vous pouvez essayer le code dans cette réponse , la bibliothèque et l'implémentation de N4428 en ligne en direct dans Wandbox. La documentation de la bibliothèque contient également un aperçu de la façon de l'utiliser comme N4428 , ce qui explique la partie énumération de cette proposition.


Explication

Le code ci-dessous implémente les conversions entre les énumérations et les chaînes. Cependant, il peut également être étendu pour faire d'autres choses, comme l'itération. Cette réponse encapsule une énumération dans un struct. Vous pouvez également générer des traitsstruct côté d'une énumération à la place.

La stratégie consiste à générer quelque chose comme ceci:

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

Les problèmes sont:

  1. Nous finirons avec quelque chose comme {Red = 1, Green, Blue}l'initialiseur du tableau de valeurs. Ce n'est pas un C ++ valide, car ce Redn'est pas une expression attribuable. Ceci est résolu par coulée chaque constante à un type Tqui a un opérateur d'affectation, mais baissera la mission: {(T)Red = 1, (T)Green, (T)Blue}.
  2. De même, nous nous retrouverons avec {"Red = 1", "Green", "Blue"}comme initialiseur pour le tableau des noms. Nous devrons couper le " = 1". Je ne suis pas au courant d'un excellent moyen de le faire au moment de la compilation, nous allons donc reporter cela à l'exécution. En conséquence, _to_stringce ne sera pas le cas constexpr, mais _from_stringpeut toujours êtreconstexpr , car nous pouvons traiter les espaces et les signes égaux comme des terminateurs lors de la comparaison avec des chaînes non coupées.
  3. Les deux ci-dessus ont besoin d'une macro de "mappage" qui peut appliquer une autre macro à chaque élément dans __VA_ARGS__. C'est assez standard. Cette réponse comprend une version simple qui peut gérer jusqu'à 8 éléments.
  4. Pour que la macro soit vraiment autonome, elle ne doit déclarer aucune donnée statique nécessitant une définition distincte. En pratique, cela signifie que les tableaux nécessitent un traitement spécial. Il existe deux solutions possibles: constexpr(ou simplement const) des tableaux au niveau de l'espace de noms, ou des tableaux réguliers dans constexprdes fonctions en ligne non statiques. Le code dans cette réponse est pour C ++ 11 et adopte la première approche. L'article CodeProject est pour C ++ 98 et prend ce dernier.

Code

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

et

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

ENUM(Channel, char, Red = 1, Green, Blue)

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

Le programme ci-dessus s'imprime Red, comme vous vous en doutez. Il existe un certain degré de sécurité de type, car vous ne pouvez pas créer une énumération sans l'initialiser, et la suppression de l'un des cas de la switchentraînera un avertissement du compilateur (selon votre compilateur et vos indicateurs). Notez également qu'il a "Red"été converti en une énumération lors de la compilation.


Heya @mrhthepie, désolé que votre modification ait été rejetée. Je viens de voir l'e-mail à ce sujet. Je vais l'intégrer dans la réponse - merci pour le correctif!
antron

c'est bien. Est-ce que cela fonctionnerait aussi si je veux une énumération de bits? Comme je veux une énumération de BitFlags, chacun est 1Udécalé d'un certain montant?
user3240688

1
il semble y avoir une fuite de mémoire _trimmed_names()dans le code que vous avez publié ici ( new char[length + 1]mais vous ne définissez pas initializedsur true). est-ce que je manque quelque chose? je ne vois pas le même problème dans votre code github.
user3240688

1
Il est défini sur true, mais en dehors de la ifbranche (fuite de mémoire initialement détectée par @mrhthepie). Devrait le déplacer à l'intérieur ... Édition. Merci pour l'examen attentif de ce code et du code GH.
antron

1
to_stringpourrait retourner un string_viewC ++ 17, qui ne nécessite pas de terminaison nulle, et devenir constexpr.
Yakk - Adam Nevraumont

74

Pour C ++ 17 C ++ 20, vous serez intéressé par les travaux du groupe d'étude sur la réflexion (SG7). Il existe une série parallèle d'articles couvrant le libellé ( P0194 ) et la justification, la conception et l'évolution ( P0385 ). (Les liens renvoient au dernier article de chaque série.)

À partir de P0194r2 (2016-10-15), la syntaxe utiliserait le reflexprmot clé proposé :

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

Par exemple (adapté de la branche reflexpr de Matus Choclik ):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

La réflexion statique n'a pas réussi à en faire C ++ 17 (plutôt, dans le projet probablement final présenté lors de la réunion de novembre 2016 sur les normes à Issaquah), mais il est certain qu'elle en fera C ++ 20; extrait du rapport de voyage de Herb Sutter :

En particulier, le groupe d'étude de réflexion a examiné la dernière proposition de réflexion statique fusionnée et l'a trouvé prêt à entrer dans les principaux groupes d'évolution lors de notre prochaine réunion pour commencer à examiner la proposition de réflexion statique unifiée pour un TS ou pour la prochaine norme.


2
@antron désolé, votre modification a été rejetée; Je l'aurais approuvé si je l'avais vu à temps. Je n'avais pas vu N4428 donc merci d'avoir donné la tête haute.
ecatmur

3
Pas de problème, merci de l'intégrer. Je me demande en quelque sorte pourquoi il a été rejeté. Je vois que le motif passe-partout "ne le rend pas plus précis", mais il est clairement plus précis pour aujourd'hui.
antron

1
Merci :-) J'ai divisé le dernier exemple pour éviter la barre de défilement horizontale. Quel dommage que la valeur MyEnum::AAAne puisse pas être passée comme deuxième argument de std::meta::get_enumerators_m: - /
olibre

1
Le fait qu'une telle tâche conceptuellement simple nécessite 3 niveaux d'arguments de modèle imbriqués est très complexe. Je suis sûr qu'il y a des raisons techniques spécifiques. Mais cela ne signifie pas que le résultat final est convivial. J'adore le C ++ et le code a du sens pour moi. Mais 90% des autres programmeurs avec lesquels je travaille quotidiennement évitent le C ++ à cause de ce code. Je suis déçu de ne pas avoir vu de solutions plus simples et intégrées.
void.pointer

2
Il semble que l'estimation actuelle pour l'inclusion du prochain Reflection TS dans la norme soit C ++ 23 : herbsutter.com/2018/04/02/…
Tim Rae

25

Ceci est similaire à Yuri Finkelstein; mais ne nécessite pas de boost. J'utilise une carte pour que vous puissiez attribuer n'importe quelle valeur aux énumérations, à n'importe quel ordre.

Déclaration de la classe d'énumération en tant que:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

Le code suivant créera automatiquement la classe enum et la surcharge:

  • '+' '+ =' pour std :: string
  • '<<' pour les flux
  • '~' juste pour convertir en chaîne (tout opérateur unaire fera l'affaire, mais personnellement je n'aime pas ça pour plus de clarté)
  • '*' pour obtenir le nombre d'énumérations

Aucun boost requis, toutes les fonctions requises sont fournies.

Code:

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

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

Exemple:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here


1
Pouvons-nous avoir des sauts de ligne à l'intérieur de cette définition de macro?
einpoklum

1
J'ai ajouté la surcharge pour *obtenir le nombre d'énumérations ... J'espère que cela ne vous dérange pas :-)
Peter VARGA

1
Y a-t-il une raison pour laquelle cette implémentation utilise l' std::mapindexation O (log (n)) plutôt que l'indexation std::unordered_mapO (1)?
River Tam

1
aussi, je pense que les méthodes doivent être marquées inlineafin que vous puissiez déclarer des énumérations dans les fichiers d'en-tête comme d'habitude sans obtenir les erreurs de "définition multiple" de l'éditeur de liens. (je ne sais pas si c'est la solution la plus propre / la meilleure, cependant)
River Tam

1
(désolé de spammer mais je n'arrive pas à éditer les commentaires aujourd'hui). La map ( E##MapName) doit être déplacée vers une unité de compilation qui a également accès à l'énumération. J'ai créé une solution, mais elle n'est pas très propre et je dois obtenir la permission de la partager. Pour l'instant, je commente simplement pour dire qu'il est inutile de marquer les méthodes en ligne sans les fonctionnalités supplémentaires nécessaires pour prendre en charge l'utilisation dans un fichier d'en-tête.
River Tam

20

En 2011, j'ai passé un week-end à peaufiner une solution basée sur des macros et j'ai fini par ne jamais l'utiliser.

Ma procédure actuelle consiste à démarrer Vim, à copier les énumérateurs dans un corps de commutateur vide, à démarrer une nouvelle macro, à transformer le premier énumérateur en une instruction de cas, à déplacer le curseur au début de la ligne suivante, à arrêter la macro et à générer le cas restant en exécutant la macro sur les autres énumérateurs.

Les macros Vim sont plus amusantes que les macros C ++.

Exemple concret:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};

Je vais créer ceci:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

Et c'est comme ça que je me débrouille.

La prise en charge native de la chaîne d'énumération serait cependant bien meilleure. Je suis très intéressé de voir les résultats du groupe de travail de réflexion en C ++ 17.

@Sehe a publié un autre moyen de le faire dans les commentaires .


1
Je fais exactement ça. Bien que je l'habitude d' utiliser Surround sélections vim et bloc le long du chemin
sehe

@sehe Intéressant. Je devrais jeter un oeil à "surround" car j'ai besoin de beaucoup de touches actuellement.
StackedCrooked

Ici , il est en plein gore, aucune macro (sauf .chiffres): i.imgur.com/gY4ZhBE.gif
sehe

1
Le gif animé est mignon, mais il est difficile de dire quand il commence et se termine, et jusqu'où nous en sommes. ... en fait, grattez ça, ce n'est pas mignon, c'est distrayant. Je dis le tuer.
einpoklum

Cette approche de sélection de bloc dans vim est agréable et tout, mais pourquoi ne pas simplement utiliser quelque chose comme :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan

14

Je ne sais pas si vous allez l'aimer ou non, je ne suis pas assez satisfait de cette solution mais c'est une approche conviviale C ++ 14 car elle utilise des variables de modèle et abuse de la spécialisation de modèle:

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

Le pire de cette approche est que c'est une douleur à maintenir, mais c'est aussi une douleur à maintenir d'autres approches similaires, n'est-ce pas?

Bons points sur cette approche:

  • Utilisation de températures variables (fonctionnalité C ++ 14)
  • Avec la spécialisation des modèles, nous pouvons «détecter» lorsqu'une valeur non valide est utilisée (mais je ne suis pas sûr que cela puisse être utile du tout).
  • Ça a l'air bien.
  • La recherche de nom se fait au moment de la compilation.

Live example

Éditer

Misterious user673679 vous avez raison; l'approche du modèle de variable C ++ 14 ne gère pas le cas d'exécution, c'était ma faute de l'oublier :(

Mais nous pouvons toujours utiliser certaines fonctionnalités C ++ modernes et un modèle de variable plus une supercherie de modèle variadique pour obtenir une traduction d'exécution de la valeur enum en chaîne ... c'est aussi gênant que les autres, mais mérite tout de même d'être mentionné.

Commençons par utiliser un alias de modèle pour raccourcir l'accès à une carte enum-to-string:

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

Ensuite, la supercherie du modèle variadic:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

La " meilleure astuce " ici est l'utilisation d'un modèle de variable pour la carte qui contient les valeurs et les noms de chaque entrée d'énumération; cette carte sera la même dans chaque unité de traduction et aura le même nom partout, donc elle est assez simple et nette, si nous appelons la initializefonction comme ceci:

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);

Nous attribuons des noms à chaque MyEnumentrée et pouvons être utilisés lors de l'exécution:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';

Mais peut être amélioré avec SFINAE et l' <<opérateur de surcharge :

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

Avec le correct operator <<maintenant, nous pouvons utiliser l'énumération de cette façon:

std::cout << MyEnum::AAA << '\n';

C'est aussi gênant à entretenir et peut être amélioré, mais j'espère que vous aurez l'idée.

Live example


Cela semble assez soigné (est-il possible de ne pas simplement définir la variable non spécialisée?). Peut-être que quelque chose me manque, car je ne vois pas du tout comment cela gère le cas d'exécution.
user673679

@Paula_plus_plus: Ne devriez-vous pas simplement utiliser un std::arrayau lieu de la carte lourde? Cela ne deviendra préférable que pour les énumérations à partir de ... quoi, 2 ^ 10 valeurs? Peut-être même plus.
einpoklum

@einpoklum ce serait incroyable si nous pouvons garantir au moment de l'exécution combien d'éléments enumont. Malheureusement, nous ne pouvons pas. Et le but de la carte est simplement d'associer des noms à des valeurs, ce qui std::mapest bon.
PaperBirdMaster

@Paula_plus_plus: Vous appelez déjà une initialize()fonction dont le nombre d'arguments est le nombre de valeurs d'énumération, vous connaissez donc le nombre de valeurs au moment de la compilation. Seule la valeur spécifique que vous êtes invité à imprimer est connue au moment de l'exécution uniquement. De plus, même si vous ne connaissiez pas ce nombre, un vecteur std :: serait plus rapide qu'un std :: map, encore une fois, dans presque tous les cas réalistes.
einpoklum

@einpoklum c'est vraiment un très bon point, j'y pense, merci! La seule chose qui me préoccupe, c'est qu'il std::arrayne s'agit pas d'un conteneur de valeurs-clés et qu'il manque donc des méthodes de recherche; de toute façon je vais y réfléchir.
PaperBirdMaster

7

Si vous enumressemble

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

Vous pouvez déplacer le contenu de la enumvers un nouveau fichier:

AAA = -8,
BBB = '8',
CCC = AAA + BBB

Et puis les valeurs peuvent être entourées d'une macro:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

L'étape suivante peut être d'inclure à enumnouveau les éléments dans :

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

Et enfin, vous pouvez générer des fonctions utilitaires à ce sujet enum:

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

La solution peut être appliquée aux anciennes normes C ++ et elle n'utilise pas d'éléments C ++ modernes mais elle peut être utilisée pour générer beaucoup de code sans trop d'effort et de maintenance.


3
Il n'est pas nécessaire d'avoir un fichier séparé. Il s'agit essentiellement d'une macro x .
HolyBlackCat

@HolyBlackCat si vous divisez la solution dans certains fichiers, vous pouvez réutiliser les valeurs d'énumération à des fins différentes
eferion

J'essaie de vous dire que vous pouvez faire la même chose si vous mettez la liste de valeurs dans une seule macro à côté de la définition d'énumération dans un en-tête.
HolyBlackCat

@HolyBlackCat oui je vous comprends mais je préfère cette solution. d'autre part, cette solution peut être trouvée dans le code source de clang, donc je pense que c'est un bon moyen de résoudre le problème
eferion

C'est suffisant. Je ne devrais pas avoir rétrogradé cela, je suppose, car cela peut en effet avoir des utilisations. (Pardonnez l'édition fictive, le système verrouille mon vote autrement.)
HolyBlackCat

6

J'ai eu le même problème il y a quelques jours. Je ne pouvais pas trouver de solution C ++ sans une magie macro étrange, alors j'ai décidé d'écrire un générateur de code CMake pour générer des instructions de cas de commutation simples.

Usage:

enum2str_generate(
  PATH          <path to place the files in>
  CLASS_NAME    <name of the class (also prefix for the files)>
  FUNC_NAME     <name of the (static) member function>
  NAMESPACE     <the class will be inside this namespace>
  INCLUDES      <LIST of files where the enums are defined>
  ENUMS         <LIST of enums to process>
  BLACKLIST     <LIST of constants to ignore>
  USE_CONSTEXPR <whether to use constexpr or not (default: off)>
  USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)

La fonction recherche les fichiers include dans le système de fichiers (utilise les répertoires include fournis avec la commande include_directories), les lit et effectue des regex pour générer la classe et la ou les fonctions.

REMARQUE: constexpr implique inline en C ++, donc l'utilisation de l'option USE_CONSTEXPR générera une classe d'en-tête uniquement!

Exemple:

./inclut/ah:

enum AAA : char { A1, A2 };

typedef enum {
   VAL1          = 0,
   VAL2          = 1,
   VAL3          = 2,
   VAL_FIRST     = VAL1,    // Ignored
   VAL_LAST      = VAL3,    // Ignored
   VAL_DUPLICATE = 1,       // Ignored
   VAL_STRANGE   = VAL2 + 1 // Must be blacklisted
} BBB;

./CMakeLists.txt:

include_directories( ${PROJECT_SOURCE_DIR}/includes ...)

enum2str_generate(
   PATH       "${PROJECT_SOURCE_DIR}"
   CLASS_NAME "enum2Str"
   NAMESPACE  "abc"
   FUNC_NAME  "toStr"
   INCLUDES   "a.h" # WITHOUT directory
   ENUMS      "AAA" "BBB"
   BLACKLIST  "VAL_STRANGE")

Génère:

./enum2Str.hpp:

/*!
  * \file enum2Str.hpp
  * \warning This is an automatically generated file!
  */

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

#include <string>
#include <a.h>

namespace abc {

class enum2Str {
 public:
   static std::string toStr( AAA _var ) noexcept;
   static std::string toStr( BBB _var ) noexcept;
};

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

/*!
  * \file enum2Str.cpp
  * \warning This is an automatically generated file!
  */

#include "enum2Str.hpp"

namespace abc {

/*!
 * \brief Converts the enum AAA to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( AAA _var ) noexcept {
   switch ( _var ) {
      case A1: return "A1";
      case A2: return "A2";
      default: return "<UNKNOWN>";
   }
}

/*!
 * \brief Converts the enum BBB to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( BBB _var ) noexcept {
   switch ( _var ) {
      case VAL1: return "VAL1";
      case VAL2: return "VAL2";
      case VAL3: return "VAL3";
      default: return "<UNKNOWN>";
   }
}
}

Mise à jour:

Le script prend désormais également en charge les énumérations étendues (enum class | struct) et je l'ai déplacé vers un référentiel séparé avec d'autres scripts que j'utilise souvent: https://github.com/mensinda/cmakeBuildTools


Hou la la! Idée très originale et innovante :-) J'espère que vous avez le courage de mettre à jour votre générateur afin de fournir une constexpret une noexceptversion ;-) Je viens aussi de lancer votre projet GitHub ;-) A
bientôt

1
Mise à jour du générateur. Les fonctions seront désormais toujours constexpr et enum: <type> est désormais pris en charge. Merci pour la star :)
Mense

Le lien est rompu ... -.-
yeoman

Le lien est maintenant corrigé.
Mense

4

Générez simplement vos énumérations. L'écriture d'un générateur à cet effet représente environ cinq minutes de travail.

Code générateur en java et python, super facile à porter dans n'importe quelle langue que vous aimez, y compris C ++.

Également très facile à étendre par la fonctionnalité que vous souhaitez.

exemple d'entrée:

First = 5
Second
Third = 7
Fourth
Fifth=11

en-tête généré:

#include <iosfwd>

enum class Hallo
{
    First = 5,
    Second = 6,
    Third = 7,
    Fourth = 8,
    Fifth = 11
};

std::ostream & operator << (std::ostream &, const Hallo&);

fichier cpp généré

#include <ostream>

#include "Hallo.h"

std::ostream & operator << (std::ostream &out, const Hallo&value)
{
    switch(value)
    {
    case Hallo::First:
        out << "First";
        break;
    case Hallo::Second:
        out << "Second";
        break;
    case Hallo::Third:
        out << "Third";
        break;
    case Hallo::Fourth:
        out << "Fourth";
        break;
    case Hallo::Fifth:
        out << "Fifth";
        break;
    default:
        out << "<unknown>";
    }

    return out;
}

Et le générateur, sous une forme très concise comme modèle pour le portage et l'extension. Cet exemple de code essaie vraiment d'éviter d'écraser des fichiers tout en l'utilisant à vos risques et périls.

package cppgen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EnumGenerator
{
    static void fail(String message)
    {
        System.err.println(message);
        System.exit(1);
    }

    static void run(String[] args)
    throws Exception
    {
        Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
        Charset charset = Charset.forName("UTF8");
        String tab = "    ";

        if (args.length != 3)
        {
            fail("Required arguments: <enum name> <input file> <output dir>");
        }

        String enumName = args[0];

        File inputFile = new File(args[1]);

        if (inputFile.isFile() == false)
        {
            fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
        }

        File outputDir = new File(args[2]);

        if (outputDir.isDirectory() == false)
        {
            fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
        }

        File headerFile = new File(outputDir, enumName + ".h");
        File codeFile = new File(outputDir, enumName + ".cpp");

        for (File file : new File[] { headerFile, codeFile })
        {
            if (file.exists())
            {
                fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
            }
        }

        int nextValue = 0;

        Map<String, Integer> fields = new LinkedHashMap<>();

        try
        (
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
        )
        {
            while (true)
            {
                String line = reader.readLine();

                if (line == null)
                {
                    break;
                }

                if (line.trim().length() == 0)
                {
                    continue;
                }

                Matcher matcher = pattern.matcher(line);

                if (matcher.matches() == false)
                {
                    fail("Syntax error: [" + line + "]");
                }

                String fieldName = matcher.group(1);

                if (fields.containsKey(fieldName))
                {
                    fail("Double fiend name: " + fieldName);
                }

                String valueString = matcher.group(2);

                if (valueString != null)
                {
                    int value = Integer.parseInt(valueString);

                    if (value < nextValue)
                    {
                        fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
                    }

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

        try
        (
            PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
            PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
        )
        {
            headerWriter.println();
            headerWriter.println("#include <iosfwd>");
            headerWriter.println();
            headerWriter.println("enum class " + enumName);
            headerWriter.println('{');
            boolean first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                if (first == false)
                {
                    headerWriter.println(",");
                }

                headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());

                first = false;
            }
            if (first == false)
            {
                headerWriter.println();
            }
            headerWriter.println("};");
            headerWriter.println();
            headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
            headerWriter.println();

            codeWriter.println();
            codeWriter.println("#include <ostream>");
            codeWriter.println();
            codeWriter.println("#include \"" + enumName + ".h\"");
            codeWriter.println();
            codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
            codeWriter.println('{');
            codeWriter.println(tab + "switch(value)");
            codeWriter.println(tab + '{');
            first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
                codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
                codeWriter.println(tab + tab + "break;");

                first = false;
            }
            codeWriter.println(tab + "default:");
            codeWriter.println(tab + tab + "out << \"<unknown>\";");
            codeWriter.println(tab + '}');
            codeWriter.println();
            codeWriter.println(tab + "return out;");
            codeWriter.println('}');
            codeWriter.println();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            run(args);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            System.exit(1);
        }
    }
}

Et un port pour Python 3.5 car suffisamment différent pour être potentiellement utile

import re
import collections
import sys
import io
import os

def fail(*args):
    print(*args)
    exit(1)

pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = "    "

if len(sys.argv) != 4:
    n=0
    for arg in sys.argv:
        print("arg", n, ":", arg, " / ", sys.argv[n])
        n += 1
    fail("Required arguments: <enum name> <input file> <output dir>")

enumName = sys.argv[1]

inputFile = sys.argv[2]

if not os.path.isfile(inputFile):
    fail("Not a file: [" + os.path.abspath(inputFile) + "]")

outputDir = sys.argv[3]

if not os.path.isdir(outputDir):
    fail("Not a directory: [" + os.path.abspath(outputDir) + "]")

headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")

for file in [ headerFile, codeFile ]:
    if os.path.exists(file):
        fail("Will not overwrite file [" + os.path.abspath(file) + "]")

nextValue = 0

fields = collections.OrderedDict()

for line in open(inputFile, 'r'):
    line = line.strip()

    if len(line) == 0:
        continue

    match = pattern.match(line)

    if match == None:
        fail("Syntax error: [" + line + "]")

    fieldName = match.group(1)

    if fieldName in fields:
        fail("Double field name: " + fieldName)

    valueString = match.group(2)

    if valueString != None:
        value = int(valueString)

        if value < nextValue:
            fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')

try:
    headerWriter.write("\n")
    headerWriter.write("#include <iosfwd>\n")
    headerWriter.write("\n")
    headerWriter.write("enum class " + enumName + "\n")
    headerWriter.write("{\n")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",\n")

        headerWriter.write(tab + fieldName + " = " + str(fieldValue))

        first = False
    if not first:
        headerWriter.write("\n")
    headerWriter.write("};\n")
    headerWriter.write("\n")
    headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
    headerWriter.write("\n")

    codeWriter.write("\n")
    codeWriter.write("#include <ostream>\n")
    codeWriter.write("\n")
    codeWriter.write("#include \"" + enumName + ".h\"\n")
    codeWriter.write("\n")
    codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
    codeWriter.write("{\n")
    codeWriter.write(tab + "switch(value)\n")
    codeWriter.write(tab + "{\n")
    for fieldName in fields.keys():
        codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
        codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
        codeWriter.write(tab + tab + "break;\n")
    codeWriter.write(tab + "default:\n")
    codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
    codeWriter.write(tab + "}\n")
    codeWriter.write("\n")
    codeWriter.write(tab + "return out;\n")
    codeWriter.write("}\n")
    codeWriter.write("\n")
finally:
    headerWriter.close()
    codeWriter.close()

1
Merci beaucoup de partager votre générateur en deux langues :-) Mais avez-vous une idée de comment générer au moment de la compilation? Par exemple, pouvons-nous imaginer traduire votre générateur à l'aide des instructions CMake afin de rafraîchir le code généré en C ++ lorsque les données d'entrée sont modifiées? Mon rêve est de forcer le compilateur C ++ à générer des énumérations lors de la compilation en utilisant la méta-programmation ( variadic template classet les constexprfonctions).
olibre

Otoh, au cas où cela serait trop lourd pour ajouter une commande cmake personnalisée, vous pouvez automatiser votre IDE ou appeler le gerateur manuellement et avoir la sortie en contrôle de source. C'est parfois une bonne idée d'avoir généré du code dans le contrôle de source de toute façon, tant que ce n'est pas trop, et les gens comprennent qu'ils ne sont pas censés apporter des modifications manuelles, car il est parfois intéressant de consulter l'historique des fichiers générés lorsque vous 'débogage quelque chose de bizarre et soupçonne qu'un changement récent du générateur peut avoir cassé quelque chose :)
yeoman

À propos de la génération de choses au moment de la compilation, c'est tellement facile dans LISP car la syntaxe est extrêmement propre et facile. Cela est aidé par le fait qu'il est typé dynamiquement, ce qui lui permet d'être concis et lisible sans trop de syntaxe. L'équivalent des macros LISP en C ++ aurait besoin d'une manière très compliquée pour décrire l'AST de ce que vous essayez de générer. Et un AST pour C ++ n'est jamais joli :(
yeoman

Directement dans Make au lieu de cmake, c'est super facile. Il suffit de générer des cibles .h et .cpp pour chaque fichier .enum via find, et de faire en sorte que ces cibles dépendent desdites définitions d'énumération, afin qu'elles soient automatiquement recréées une fois que les fichiers def .enum changent. C'est probablement beaucoup plus facile dans cmake car il est plein de magie pour ce genre de choses mais j'utilise régulièrement Make, ant et gradle, mais je n'ai qu'une connaissance limitée de Maven, cmake et grunt :)
yeoman

Merci pour votre réponse :-) Je pense que la plupart des développeurs C ++ apprécieront si votre générateur pourrait détecter des énumérations directement dans le code C ++ comme enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};ou en plusieurs lignes :-D Pensez-vous que vous pouvez adapter votre générateur afin de détecter un enumdans un C ++ fichier? Le mieux pourrait être de générer du code uniquement lors de la détection d'une balise comme /*<Generate enum to string here>*/. Ensuite, votre générateur écrit sur place le code généré C ++ correspondant (en remplaçant le code généré précédent). ^ _ ^ Qu'est-ce qu'un générateur génial n'est-ce pas?
Santé

3

Conformément à la demande de l'OP, voici une version simplifiée de la macreuse solution de macros basée sur Boost Preprosessor et Variadic Macros .

Il permet une liste simple comme la syntaxe des éléments énumérateurs ainsi que la définition de valeurs pour des éléments spécifiques afin que

XXX_ENUM(foo,(a,b,(c,42)));

s'étend à

enum foo {
    a,
    b,
    c=42
};

Parallèlement aux fonctions nécessaires pour produire et effectuer une certaine conversion. Cette macro existe depuis des lustres, et je ne suis pas totalement sûr que ce soit le moyen le plus efficace, ou que ce soit un moyen conforme, mais il fonctionne depuis

Le code complet peut être vu en action à la fois Ideone et à Coliru .

Sa laideur gargantuesque est au-dessus; Je l'aurais mis derrière des spoilers pour protéger tes yeux, si j'avais su, mais le démarque ne m'aime pas.

La bibliothèque (fusionnée dans un seul fichier d'en-tête)

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         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,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

Usage

#include "xxx_enum.h"  // the above lib
#include <iostream>

XXX_ENUM(foo,(a,b,(c,42)));

int main()
{
  std::cout << "foo::a = "            << foo::a            <<'\n';
  std::cout << "(int)foo::c = "       << (int)foo::c       <<'\n';
  std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
  std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}

Compilation (copier l'en-tête coller dans main.cpp )

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

Production

foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b

5
Ce bloc de code est un voyage fou à travers les paysages incroyables de la métaprogrammation de magie noire. En fait, je me suis sentie soulagée en arrivant à la mainmaison, douce maison!
Quentin

Vous venez d'ajouter un lien vers coliru pour vérifier la sortie (il y a quelques avertissements, cliquez sur le lien dans votre réponse). J'ai également divisé en Lib / Usage. Le contenu namespace xxxpeut-il être déplacé à la place de l'en-tête? Vous pouvez dire dans l'intro votre utilisation boost/preprocessor.hppet donc la réponse est conforme au C ++ moderne . Veuillez corriger les avertissements et nettoyer un peu le code source pour une meilleure qualité.
olibre

@olibre: Il s'agit du copypastad de, je pense, 5 en-têtes différents dans notre bibliothèque. L'énum_cast est d'une autre partie plus générale mais j'ai pensé à l'ajouter aussi pour voir à quoi sert le do_enum_cast dans la macro. Les avertissements sont juste de mon main<tab>de vim y compris les arguments que je n'utilise pas. Je ne pense pas que ce code puisse être vraiment nettoyé, c'est juste pour montrer ce qui peut être fait et ne devrait pas l'être;) et si je le change ici ce n'est plus le code que j'utilise en production ... c'est une de ces choses fragiles qu'une fois que cela fonctionne, il vaut mieux ne jamais toucher, car il pourrait s'effondrer d'une manière que personne ne pouvait prédire.
PlasmaHH

Très bien Plasma, je vois que cela peut être vu comme une preuve de concept . Mais il y a trop de frais généraux macro pour être voté à la hausse. Merci néanmoins pour le partage. Vive
olibre

Salut Plasma. J'ai effectué un nettoyage approfondi du code source + complété par la compilation et l'exécution de la sortie. Veuillez vérifier ma modification . J'espère que cela vous convient. La réponse est-elle plus valable? Cependant, la surcharge macro est toujours horrible! Bonne journée :-) A
bientôt

2

La solution suivante est basée sur un std::array<std::string,N>pour une énumération donnée.

Pour enumla std::stringconversion , nous pouvons simplement jeter l'enum size_tet rechercher la chaîne à partir du tableau. L'opération est O (1) et ne nécessite aucune allocation de segment de mémoire.

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

Pour std::stringla enumconversion , nous devrions effectuer une recherche linéaire sur le tableau et jeter l'index de tableau à enum.

Essayez-le ici avec des exemples d'utilisation: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Edit: retravaillé ma solution afin que l'énumération personnalisée puisse être utilisée dans une classe.


Merci pour votre réponse intéressante. Veuillez retravailler votre proposition afin d'utiliser votre macro dans une classe. Voir coliru.stacked-crooked.com/a/00d362eba836d04b De plus, essayez d'utiliser constexpret des noexeptmots - clés lorsque cela est possible.
Santé

La question ne précisait pas cette condition.
FKaria

Question mise à jour (voir exemple). Deux autres exigences: (1) le type de support enum et (2) les valeurs peuvent être différentes de la séquence 0, 1, 2 ...
olibre

J'ai retravaillé ma solution pour qu'elle puisse être utilisée dans une classe. Je n'ai pas encore compris comment rendre les valeurs différentes de 0,1,2, .. cependant.
FKaria

Salut FKaria. Merci beaucoup pour votre retouche. J'ai fait quelques changements afin de prendre en charge plusieurs énumérations au sein de la même classe, ainsi que pour prendre en charge le enum class X : Typeformat. Veuillez revoir ma contribution: coliru.stacked-crooked.com/a/b02db9190d3491a3 Que pensez-vous de mes changements? Avez-vous une idée de prendre en charge les valeurs définies dans enum? Example enum E{A=3, B=6, C=A-B};Cheers
olibre

2

Cet essentiel fournit un mappage simple basé sur des modèles variadiques C ++.

Il s'agit d'une version simplifiée C ++ 17 de la carte basée sur le type de l' essentiel :

#include <cstring> // http://stackoverflow.com/q/24520781

template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                  "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
            ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
            ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
            ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};

template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

Un exemple d'utilisation:

enum class fasion {
    fancy,
    classic,
    sporty,
    emo,
    __last__ = emo,
    __unknown__ = -1
};

#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
    NAME(fancy)
    NAME(classic)
    NAME(sporty)
    NAME(emo)
}

template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
    typedef decltype(K) key_t;
    typedef decltype(V) name_t;
    static constexpr key_t  key = K; // enum id value
    static constexpr name_t val = V; // enum id name
};

typedef names<fasion,
    _<fasion::fancy, name::fancy>,
    _<fasion::classic, name::classic>,
    _<fasion::sporty, name::sporty>,
    _<fasion::emo, name::emo>,
    _<fasion::__unknown__, nullptr>
> fasion_names;

Le map<KeyValues...>peut être utilisé dans les deux sens:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

Cet exemple est disponible sur godbolt.org

int main ()
{
  constexpr auto str = fasion_names::get(fasion::emo);
  constexpr auto fsn = fasion_names::get(str);
  return (int) fsn;
}

Résultat de gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Méthode de méta-programmation très intéressante. J'ai essayé de simplifier un peu la réponse pour être autonome (sans dépendance sur le lien Gist). Afin d'être concis et compréhensible, j'ai finalement beaucoup édité votre réponse. Êtes-vous toujours d'accord avec mes modifications? Acclamations ;-)
olibre

2

J'ai également été frustré par ce problème pendant longtemps, ainsi que par le problème d'obtenir un type converti en chaîne de manière appropriée. Cependant, pour le dernier problème, j'ai été surpris par la solution expliquée dans Est-il possible d'imprimer le type d'une variable en C ++ standard? , en utilisant l'idée de Puis-je obtenir des noms de type C ++ de manière constexpr? . En utilisant cette technique, une fonction analogue peut être construite pour obtenir une valeur d'énumération sous forme de chaîne:

#include <iostream>
using namespace std;

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    constexpr static_string(const char(&a)[N]) noexcept
        : p_(a)
        , sz_(N - 1)
    {}

    constexpr static_string(const char* p, std::size_t N) noexcept
        : p_(p)
        , sz_(N)
    {}

    constexpr const char* data() const noexcept { return p_; }
    constexpr std::size_t size() const noexcept { return sz_; }

    constexpr const_iterator begin() const noexcept { return p_; }
    constexpr const_iterator end()   const noexcept { return p_ + sz_; }

    constexpr char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 37, p.size() - 37 - 7);
#endif

}

namespace details
{
    template <class Enum>
    struct EnumWrapper
    {
        template < Enum enu >
        static static_string name()
        {
#ifdef __clang__
            static_string p = __PRETTY_FUNCTION__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
            static_string p = __FUNCSIG__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
        }
    };
}

/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
    return details::EnumWrapper<Enum>::template name<enu>();
}

enum class Color
{
    Blue = 0,
    Yellow = 1
};


int main() 
{
    std::cout << "_" << typeName<Color>() << "_"  << std::endl;
    std::cout << "_" << enumName<Color, Color::Blue>() << "_"  << std::endl;
    return 0;
}

Le code ci-dessus n'a été testé que sur Clang (voir https://ideone.com/je5Quv ) et VS2015, mais devrait être adaptable à d'autres compilateurs en jouant un peu avec les constantes entières. Bien sûr, il utilise toujours des macros sous le capot, mais au moins un n'a pas besoin d'accéder à l'implémentation d'énumération.


Cela échoue avec g ++ 6.3.0 et C ++ 14.
einpoklum

Intéressant car l'énumération peut être déclarée normalement sans avoir à l'envelopper dans une macro. Bien que je n'aime pas les dépendances du compilateur et les constantes magiques.
zett42

2

J'ai pris l'idée de @antron et l'ai mise en œuvre différemment: générer une vraie classe enum .

Cette implémentation répond à toutes les exigences énumérées dans la question d'origine mais n'a actuellement qu'une seule limitation réelle : elle suppose que les valeurs d'énumération ne sont pas fournies ou, si elles sont fournies, doivent commencer par 0 et remonter séquentiellement sans lacunes.

Ce n'est pas une limitation intrinsèque - simplement que je n'utilise pas de valeurs d'énumération ad hoc. Si cela est nécessaire, on peut remplacer la recherche vectorielle par une implémentation traditionnelle de commutateur / boîtier.

La solution utilise du c ++ 17 pour les variables en ligne mais cela peut être facilement évité si nécessaire. Il utilise également boost: trim pour sa simplicité.

Plus important encore, il ne prend que 30 lignes de code et aucune macro de magie noire. Le code est ci-dessous. Il est destiné à être placé dans l'en-tête et inclus dans plusieurs modules de compilation.

Il peut être utilisé de la même manière que cela a été suggéré précédemment dans ce fil:

ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green

Veuillez me faire savoir si cela est utile et comment il peut être amélioré.


#include <boost/algorithm/string.hpp>   
struct EnumSupportBase {
  static std::vector<std::string> split(const std::string s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector<std::string> tokens;
    while (std::getline(ss, item, delim)) {
        auto pos = item.find_first_of ('=');
        if (pos != std::string::npos)
            item.erase (pos);
        boost::trim (item);
        tokens.push_back(item);
    }
    return tokens;
  }
};
#define ENUM(EnumName, Underlying, ...) \
    enum class EnumName : Underlying { __VA_ARGS__, _count }; \
    struct EnumName ## Support : EnumSupportBase { \
        static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
        static constexpr const char* get_name(EnumName enum_value) { \
            int index = (int)enum_value; \
            if (index >= (int)EnumName::_count || index < 0) \
               return "???"; \
            else \
               return _token_names[index].c_str(); \
        } \
    }; \
    inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
        return os << EnumName##Support::get_name(es); \
    } 

2

Tant que vous êtes d'accord avec l'écriture d'une .h/.cpppaire distincte pour chaque énumération interrogeable, cette solution fonctionne avec presque la même syntaxe et les mêmes capacités qu'une énumération c ++ régulière:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

Le .cppfichier est constitué de 3 lignes de passe-partout:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

Exemple d'utilisation:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

Code

Cette solution nécessite 2 fichiers sources:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...et

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

Explication

Cette implémentation exploite le fait que la liste des éléments contreventés d'une définition d'énumération peut également être utilisée comme une liste d'initialisation renforcée pour l'initialisation des membres de classe.

Lorsqu'il ETRAITSest évalué dans le contexte de EnumTraits.inl, il se développe en une définition de membre statique pour leEnumTraits<> classe.

La EDECLmacro transforme chaque membre d'énumération en valeurs de liste d'initialisation qui sont ensuite transmises au constructeur de membre afin de remplir les informations d'énumération.

La EnumInitGuardclasse est conçue pour consommer les valeurs de l'initialiseur d'énumération, puis s'effondrer, laissant une pure liste de données d'énumération.

Avantages

  • c++syntaxe
  • Fonctionne à l'identique pour les deux enumet enum class(* presque)
  • Fonctionne pour les enumtypes avec n'importe quel type sous-jacent numérique
  • Fonctionne pour les enumtypes avec des valeurs d'initialisation automatiques, explicites et fragmentées
  • Fonctionne pour le renommage de masse (liaison intellisense préservée)
  • Seulement 5 symboles de préprocesseur (3 globaux)

* Contrairement aux enumsinitialiseurs deenum class types qui font référence à d'autres valeurs de la même énumération, ces valeurs doivent être entièrement qualifiées.

Avantages

  • Nécessite une .h/.cpppaire distincte pour chaque requêteenum
  • Dépend des alambics macroet de la includemagie
  • Des erreurs de syntaxe mineures explosent en erreurs beaucoup plus importantes
  • La définition d' énumérations classou de namespaceportée n'est pas triviale
  • Pas d'initialisation du temps de compilation

commentaires

Intellisense se plaindra un peu de l'accès des membres privés lors de l'ouverture EnumTraits.inl, mais comme les macros étendues définissent en fait des membres de classe, ce n'est pas vraiment un problème.

Le #ifndef ENUM_INCLUDE_MULTIbloc en haut du fichier d'en-tête est une gêne mineure qui pourrait probablement être réduit en une macro ou quelque chose, mais il est assez petit pour vivre avec sa taille actuelle.

La déclaration d'une énumération de portée d'espace de noms nécessite que l'énumération soit d'abord déclarée en avant dans sa portée d'espace de noms, puis définie dans l'espace de noms global. En outre, tous les initialiseurs d'énumération utilisant des valeurs de la même énumération doivent avoir ces valeurs entièrement qualifiées.

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

2

Je ne sais pas si cette approche est déjà couverte dans l'une des autres réponses (en fait, c'est le cas, voir ci-dessous). J'ai rencontré le problème plusieurs fois et je n'ai pas trouvé de solution qui n'utilisait pas de macros obscurcies ou de bibliothèques tierces. J'ai donc décidé d'écrire ma propre version de macro obscurcie.

Ce que je veux activer est l'équivalent de

enum class test1 { ONE, TWO = 13, SIX };

std::string toString(const test1& e) { ... }

int main() {
    test1 x;
    std::cout << toString(x) << "\n";
    std::cout << toString(test1::TWO) << "\n";
    std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
    //std::cout << toString(123);// invalid
}

qui devrait imprimer

ONE
TWO
13

Je ne suis pas fan des macros. Cependant, à moins que c ++ ne supporte nativement la conversion d'énumérations en chaînes, il faut utiliser une sorte de génération de code et / ou de macros (et je doute que cela se produise trop tôt). J'utilise une macro X :

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

La plupart d'entre eux définissent et définissent des symboles que l'utilisateur transmettra en tant que paramètre au X-marco via un include. L'utilisation est comme ça

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

Démo en direct

Notez que je n'ai pas encore inclus le choix du type sous-jacent. Je n'en avais pas besoin jusqu'à présent, mais il devrait être simple de modifier le code pour l'activer.

Ce n'est qu'après avoir écrit ceci que j'ai réalisé que c'était assez similaire à la réponse aux eferions . Peut-être que je l'ai lu avant et peut-être que c'était la principale source d'inspiration. Je n'arrivais toujours pas à comprendre les macros X jusqu'à ce que j'écrive les miennes;).


1

Solutions utilisant enum dans class / struct (struct par défaut avec les membres publics) et opérateurs surchargés:

struct Color
{
    enum Enum { RED, GREEN, BLUE };
    Enum e;

    Color() {}
    Color(Enum e) : e(e) {}

    Color operator=(Enum o) { e = o; return *this; }
    Color operator=(Color o) { e = o.e; return *this; }
    bool operator==(Enum o) { return e == o; }
    bool operator==(Color o) { return e == o.e; }
    operator Enum() const { return e; }

    std::string toString() const
    {
        switch (e)
        {
        case Color::RED:
            return "red";
        case Color::GREEN:
            return "green";
        case Color::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
};

De l'extérieur, il ressemble presque exactement à une énumération de classe:

Color red;
red = Color::RED;
Color blue = Color::BLUE;

cout << red.toString() << " " << Color::GREEN << " " << blue << endl;

Cela affichera "rouge 1 2". Vous pourriez éventuellement surcharger << pour faire de la sortie bleue une chaîne (bien que cela puisse entraîner une ambiguïté donc pas possible), mais cela ne fonctionnerait pas avec Color :: GREEN car il ne se convertit pas automatiquement en Color.

Le but d'avoir une conversion implicite en Enum (qui se convertit implicitement en int ou en type donné) est de pouvoir faire:

Color color;
switch (color) ...

Cela fonctionne, mais cela signifie également que cela fonctionne aussi:

int i = color;

Avec une classe enum, elle ne se compilerait pas. Vous devez être prudent si vous surchargez deux fonctions en prenant l'énumération et un entier, ou supprimez la conversion implicite ...

Une autre solution consisterait à utiliser une classe enum réelle et des membres statiques:

struct Color
{
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    //same as previous...
};

Cela prend peut-être plus d'espace et est plus long à faire, mais provoque une erreur de compilation pour les conversions int implicites. J'utiliserais celui-ci à cause de ça!

Il y a sûrement des frais généraux avec cela, mais je pense que c'est juste plus simple et semble meilleur que les autres codes que j'ai vus. Il existe également un potentiel pour ajouter des fonctionnalités, qui pourraient toutes être définies dans la classe.

Edit : cela fonctionne et la plupart peuvent être compilés avant l'exécution:

class Color
{
public:
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    constexpr Color() : e(Enum::RED) {}
    constexpr Color(Enum e) : e(e) {}

    constexpr bool operator==(Enum o) const { return e == o; }
    constexpr bool operator==(Color o) const { return e == o.e; }
    constexpr operator Enum() const { return e; }

    Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
    Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }

    std::string toString() const
    {
        switch (e)
        {
        case Enum::RED:
            return "red";
        case Enum::GREEN:
            return "green";
        case Enum::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
private:
    const Enum e;
};

C'est très intéressant :-) Cependant, votre version actuelle implique que vous devez écrire manuellement le contenu case Enum::RED: return "red";. La question est d'automatiser ce truc par le compilateur (au moment de la compilation). L'idée de la question est de seulement changer ou ajouter des valeurs d'énumération sans avoir à mettre à jour le truc toString(). Est-ce que tu vois? Merci
olibre

1

Solution très simple avec une grande contrainte: vous ne pouvez pas attribuer de valeurs personnalisées à des enumvaleurs, mais avec la bonne expression rationnelle, vous pourriez. vous pouvez également ajouter une carte pour les traduire en enumvaleurs sans beaucoup plus d'efforts:

#include <vector>
#include <string>
#include <regex>
#include <iterator>

std::vector<std::string> split(const std::string& s, 
                               const std::regex& delim = std::regex(",\\s*"))
{
    using namespace std;
    vector<string> cont;
    copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), 
         regex_token_iterator<string::const_iterator>(),
         back_inserter(cont));
    return cont;
}

#define EnumType(Type, ...)     enum class Type { __VA_ARGS__ }

#define EnumStrings(Type, ...)  static const std::vector<std::string> \
                                Type##Strings = split(#__VA_ARGS__);

#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
                                EnumStrings(Type, __VA_ARGS__)

Exemple d'utilisation:

EnumToString(MyEnum, Red, Green, Blue);

Merci Malem pour votre idée innovante. J'ai modifié votre réponse pour améliorer la lisibilité. J'espère que vous aimez mes changements. Veuillez continuer à améliorer votre réponse: (1) étendez la section "Exemple d'utilisation" avec quelque chose comme auto name = MyEnumStrings["Red"];- (2) Pourquoi utilisez-vous enum class? - (3) Soutenez-vous enum class MyEnum : char { Red, Green, Blue };? - (4) Expliquer la fonction split()- (5) Avez-vous besoin d'un paramètre const std::regex& delim? - (6) Qu'en est-il de la génération MyEnumStringsau moment de la compilation? => Pouvez-vous utiliser constexpr? ... Vive :-)
olibre

J'aime vraiment cette approche. Vraiment court et facile à comprendre.
Anton Holmberg

1

EDIT: vérifiez ci-dessous pour une version plus récente

Comme mentionné ci-dessus, le N4113 est la solution finale à ce problème, mais nous devrons attendre plus d'un an pour le voir sortir.

En attendant, si vous voulez une telle fonctionnalité, vous devrez recourir à des modèles "simples" et à de la magie de préprocesseur.

Énumérateur

template<typename T>
class Enum final
{
    const char* m_name;
    const T m_value;
    static T m_counter;

public:
    Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}

    const T value() const {return m_value;}
    const char* name() const {return m_name;}
};

template<typename T>
T Enum<T>::m_counter = 0;

#define ENUM_TYPE(x)      using Enum = Enum<x>;
#define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
#define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);

Usage

#include <iostream>

//the initialization order should be correct in all scenarios
namespace Level
{
    ENUM_TYPE(std::uint8)
    ENUM(OFF)
    ENUM(SEVERE)
    ENUM(WARNING)
    ENUM(INFO, 10)
    ENUM(DEBUG)
    ENUM(ALL)
}

namespace Example
{
    ENUM_TYPE(long)
    ENUM(A)
    ENUM(B)
    ENUM(C, 20)
    ENUM(D)
    ENUM(E)
    ENUM(F)
}

int main(int argc, char** argv)
{
    Level::Enum lvl = Level::WARNING;
    Example::Enum ex = Example::C;
    std::cout << lvl.value() << std::endl; //2
    std::cout << ex.value() << std::endl; //20
}

Explication simple

Enum<T>::m_counterest défini sur 0 dans chaque déclaration d'espace de noms.
( Quelqu'un pourrait-il me signaler où ^^ ce comportement ^^ est mentionné dans la norme? )
La magie du préprocesseur automatise la déclaration des énumérateurs.

Désavantages

  • Ce n'est pas un vrai enumtype, donc pas propice à l'int
  • Ne peut pas être utilisé dans des boîtiers de commutation

Solution alternative

Celui-ci sacrifie la numérotation des lignes (pas vraiment) mais peut être utilisé sur les boîtiers de commutation .

#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x)      constexpr type x{__LINE__,#x}

template<typename T>
struct Enum final
{
    const T value;
    const char* name;

    constexpr operator const T() const noexcept {return value;}
    constexpr const char* operator&() const noexcept {return name;}
};

Errata

#line 0conflits avec -pedanticGCC et clang.

solution de contournement

Commencez par #line 1et soustrayez 1 de __LINE__.
Ou, ne l'utilisez pas -pedantic.
Et pendant que nous y sommes, évitez VC ++ à tout prix, cela a toujours été une blague de compilateur.

Usage

#include <iostream>

namespace Level
{
    ENUM_TYPE(short);
    #line 0
    ENUM(OFF);
    ENUM(SEVERE);
    ENUM(WARNING);
    #line 10
    ENUM(INFO);
    ENUM(DEBUG);
    ENUM(ALL);
    #line <next line number> //restore the line numbering
};

int main(int argc, char** argv)
{
    std::cout << Level::OFF << std::endl;   // 0
    std::cout << &Level::OFF << std::endl;  // OFF

    std::cout << Level::INFO << std::endl;  // 10
    std::cout << &Level::INFO << std::endl; // INFO

    switch(/* any integer or integer-convertible type */)
    {
    case Level::OFF:
        //...
        break;

    case Level::SEVERE:
        //...
        break;

    //...
    }

    return 0;
}

Mise en œuvre et utilisation réelles

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Référence rapide

#line lineno - cppreference.com


0

J'ai écrit une bibliothèque pour résoudre ce problème, tout se passe au moment de la compilation, sauf pour recevoir le message.

Usage:

Utilisez une macro DEF_MSGpour définir une macro et une paire de messages:

DEF_MSG(CODE_OK,   "OK!")
DEF_MSG(CODE_FAIL, "Fail!")

CODE_OKest la macro à utiliser et "OK!"est le message correspondant.

Utilisez get_message()ou simplement gm()pour obtenir le message:

get_message(CODE_FAIL);  // will return "Fail!"
gm(CODE_FAIL);           // works exactly the same as above

Utilisez MSG_NUMpour savoir combien de macros ont été définies. Cela augmentera automatiquement, vous n'avez rien à faire.

Messages prédéfinis:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Projet: libcodemsg


La bibliothèque ne crée pas de données supplémentaires. Tout se passe au moment de la compilation. Dans message_def.h, il génère un enumappelé MSG_CODE; dans message_def.c, il génère une variable qui contient toutes les chaînes static const char* _g_messages[].

Dans ce cas, la bibliothèque est limitée à en créer une enumseule. C'est idéal pour les valeurs de retour, par exemple:

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

Une autre chose que j'aime dans cette conception est que vous pouvez gérer les définitions de messages dans différents fichiers.


J'ai trouvé que la solution à cette question était bien meilleure.


Salut Madwyn. Merci pour votre idée. Mais comment ça marche? Quel est le surcoût? (zéro surcharge ou cela crée-t-il des données supplémentaires?). Votre proposition semble bonne, mais malheureusement, une déclaration DEF_MSGdoit être utilisée / mise à jour / maintenue pour chaque enumvaleur: - / Et c'est ce que nous aimerions idéalement arrêter de faire ... A
bientôt

Merci pour la réponse, @olibre. Veuillez vérifier la réponse mise à jour. Je ne vois pas de surcharge ici, sauf qu'un appel de fonction est nécessaire pour accéder aux chaînes. DEF_MSGrend le enumétroitement associé au message, bien qu'il ait certaines limites.
Madwyn

Merci pour l'explication jointe dans votre réponse :-) Votre bibliothèque est correcte mais ne peut pas être utilisée pour plusieurs énumérations: - / Qu'en est-il du support de enum class(C ++ 11) ? Vous pouvez utiliser constexprpour limiter _g_messagesau moment de l'exécution. Prend en charge plusieurs enumtypes (en évitant _g_messages) en utilisant la méta-programmation (type véhiculant {enum-type, enum-value}) ou peut-être des variables de modèle (C ++ 14) . Je pense que votre lib ne correspond pas (encore?) Aux exigences C ++ 11/14/17. Qu'est-ce que tu penses?
Santé

1
Merci pour le suivi. J'ai appris quelque chose de nouveau aujourd'hui! Les variables de classe et de modèle enum semblent bonnes. Je pense que ma réponse était un peu "hors sujet" car elle était aromatisée C.
Madwyn

0
#define ENUM_MAKE(TYPE, ...) \
        enum class TYPE {__VA_ARGS__};\
        struct Helper_ ## TYPE { \
            static const String& toName(TYPE type) {\
                int index = static_cast<int>(type);\
                return splitStringVec()[index];}\
            static const TYPE toType(const String& name){\
                static std::unordered_map<String,TYPE> typeNameMap;\
                if( typeNameMap.empty() )\
                {\
                    const StringVector& ssVec = splitStringVec();\
                    for (size_t i = 0; i < ssVec.size(); ++i)\
                        typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
                }\
                return typeNameMap[name];}\
            static const StringVector& splitStringVec() {\
                static StringVector typeNameVector;\
                if(typeNameVector.empty()) \
                {\
                    typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
                    for (auto& name : typeNameVector)\
                    {\
                        name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
                        name = String(#TYPE) + "::" + name;\
                    }\
                }\
                return typeNameVector;\
            }\
        };


using String = std::string;
using StringVector = std::vector<String>;

   StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
    {
        StringVector ret;
        // Pre-allocate some space for performance
        ret.reserve(maxSplits ? maxSplits+1 : 10);    // 10 is guessed capacity for most case

        unsigned int numSplits = 0;

        // Use STL methods 
        size_t start, pos;
        start = 0;
        do 
        {
            pos = str.find_first_of(delims, start);
            if (pos == start)
            {
                // Do nothing
                start = pos + 1;
            }
            else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
            {
                // Copy the rest of the string
                ret.push_back( str.substr(start) );
                break;
            }
            else
            {
                // Copy up to delimiter
                ret.push_back( str.substr(start, pos - start) );

                if(preserveDelims)
                {
                    // Sometimes there could be more than one delimiter in a row.
                    // Loop until we don't find any more delims
                    size_t delimStart = pos, delimPos;
                    delimPos = str.find_first_not_of(delims, delimStart);
                    if (delimPos == String::npos)
                    {
                        // Copy the rest of the string
                        ret.push_back( str.substr(delimStart) );
                    }
                    else
                    {
                        ret.push_back( str.substr(delimStart, delimPos - delimStart) );
                    }
                }

                start = pos + 1;
            }
            // parse up to next real data
            start = str.find_first_not_of(delims, start);
            ++numSplits;

        } while (pos != String::npos);



        return ret;
    }

exemple

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


    MY_TEST s1 = MY_TEST::MY_1;
    MY_TEST s2 = MY_TEST::MY_2;
    MY_TEST s3 = MY_TEST::MY_3;

    String z1 = Helper_MY_TEST::toName(s1);
    String z2 = Helper_MY_TEST::toName(s2);
    String z3 = Helper_MY_TEST::toName(s3);

    MY_TEST q1 = Helper_MY_TEST::toType(z1);
    MY_TEST q2 = Helper_MY_TEST::toType(z2);
    MY_TEST q3 = Helper_MY_TEST::toType(z3);

la macro ENUM_MAKE génère automatiquement la «classe énum» et la classe auxiliaire avec la «fonction de réflexion énum».

Afin de réduire les erreurs, tout est à la fois défini avec un seul ENUM_MAKE.

L'avantage de ce code est automatiquement créé pour la réflexion et un examen attentif du code macro, du code facile à comprendre. 'enum to string', 'string to enum' performance both is algorithm O (1).

Les inconvénients sont lors de la première utilisation, la classe d'assistance pour le vecteur de chaîne et la carte de la reconnexion enum est initialisée. mais si vous le souhaitez, vous serez également pré-initialisé. -


Bien que ce code puisse répondre à la question, il serait préférable d'expliquer comment il résout le problème sans en introduire d'autres et pourquoi l'utiliser. Les réponses uniquement codées ne sont pas utiles à long terme.
JAL

hé les gars, je suis désolé, je ne parle pas très bien anglais.
desperado_98

la macro ENUM_MAKE génère automatiquement la «classe énum» et la classe auxiliaire avec la «fonction de réflexion énum». / Afin de réduire les erreurs, tout est à la fois défini avec un seul ENUM_MAKE. L'avantage de ce code est automatiquement créé pour la réflexion et un examen attentif du code macro, du code facile à comprendre. 'enum to string', 'string to enum' performance both is algorith O (1). Les inconvénients sont lors de la première utilisation, la classe d'assistance pour le vecteur de chaîne et la carte de la reconnexion enum est initialisée. mais si vous le souhaitez, vous serez également pré-initialisé.
desperado_98

Salut desperado_98. Nous vous remercions de votre contribution. Veuillez modifier votre réponse et y insérer le contenu de votre commentaire. Le compilateur peut calculer votre exemple au moment de la compilation si vous utilisez des astuces de méta-programmation et constexpr. Je veux dire les fonctions toName()et toType()peuvent être évaluées pendant la compilation et non pendant l'exécution (au moment de l'exécution). Veuillez adopter le style C ++ 11/14/17 dans votre réponse.
Santé

De plus: votre macro est-elle compatible enum class MyEnum : short { A, B, C };?
olibre

0

ma solution est sans utilisation de macro.

avantages:

  • vous voyez exactement ce que vous faites
  • l'accès est avec des cartes de hachage, donc bon pour de nombreux énumérations valorisées
  • pas besoin de considérer l'ordre ou les valeurs non consécutives
  • à la fois la énumération en chaîne et la conversion chaîne en énumération, tandis que la valeur d'énumération ajoutée doit être ajoutée à un emplacement supplémentaire uniquement

désavantages:

  • vous devez répliquer toutes les valeurs d'énumérations sous forme de texte
  • l'accès dans la carte de hachage doit tenir compte de la casse de la chaîne
  • maintenance si l'ajout de valeurs est pénible - doit ajouter à la fois enum et direct translate map

donc ... jusqu'au jour où C ++ implémentera la fonctionnalité C # Enum.Parse, je serai coincé avec ceci:

            #include <unordered_map>

            enum class Language
            { unknown, 
                Chinese, 
                English, 
                French, 
                German
                // etc etc
            };

            class Enumerations
            {
            public:
                static void fnInit(void);

                static std::unordered_map <std::wstring, Language> m_Language;
                static std::unordered_map <Language, std::wstring> m_invLanguage;

            private:
                static void fnClear();
                static void fnSetValues(void);
                static void fnInvertValues(void);

                static bool m_init_done;
            };

            std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
            std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();

            void Enumerations::fnInit()
            {
                fnClear();
                fnSetValues();
                fnInvertValues();
            }

            void Enumerations::fnClear()
            {
                m_Language.clear();
                m_invLanguage.clear();
            }

            void Enumerations::fnSetValues(void)
            {   
                m_Language[L"unknown"] = Language::unknown;
                m_Language[L"Chinese"] = Language::Chinese;
                m_Language[L"English"] = Language::English;
                m_Language[L"French"] = Language::French;
                m_Language[L"German"] = Language::German;
                // and more etc etc
            }

            void Enumerations::fnInvertValues(void)
            {
                for (auto it = m_Language.begin(); it != m_Language.end(); it++)
                {
                    m_invLanguage[it->second] = it->first;
                }
            }

            // usage -
            //Language aLanguage = Language::English;
            //wstring sLanguage = Enumerations::m_invLanguage[aLanguage];

            //wstring sLanguage = L"French" ;
            //Language aLanguage = Enumerations::m_Language[sLanguage];

0

Eh bien, encore une autre option. Un cas d'utilisation typique est celui où vous avez besoin de constantes pour les verbes HTTP ainsi que l'utilisation de ses 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

1
Pour réduire l'utilisation de la mémoire, vous pouvez remplacer le membre const std::string textpar juste theStrings[v]. Cependant, la question porte sur les fonctionnalités de C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 pour éviter d'avoir à écrire une telle classe à la main: - /
olibre

0

Ma réponse est ici.

Vous pouvez obtenir simultanément les noms de valeurs enum et ces index sous forme de deque de chaîne.

Cette méthode n'a besoin que de peu de copier-coller et d'éditer.

Le résultat obtenu nécessite une conversion de type de size_t en type de classe enum lorsque vous avez besoin d'une valeur de type de classe enum, mais je pense que c'est un moyen très portable et puissant de traiter la classe enum.

enum class myenum
{
  one = 0,
  two,
  three,
};

deque<string> ssplit(const string &_src, boost::regex &_re)
{
  boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
  boost::sregex_token_iterator e;
  deque<string> tokens;
  while (it != e)
    tokens.push_back(*it++);
  return std::move(tokens);
}

int main()
{
  regex re(",");
  deque<string> tokens = ssplit("one,two,three", re);
  for (auto &t : tokens) cout << t << endl;
    getchar();
  return 0;
}

0

Vous pouvez utiliser une bibliothèque de réflexion, comme Ponder :

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"

0

(Analogue de https://stackoverflow.com/a/54967187/2338477 , légèrement modifié).

Voici ma propre solution avec un minimum de magie définie et le support des affectations d'énumération individuelles.

Voici le fichier d'en-tête:

#pragma once
#include <string>
#include <map>
#include <regex>

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

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


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




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // 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...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

Et voici un exemple d'application de test:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

La version mise à jour du même fichier d'en-tête sera conservée ici:

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


-5

Qu'en est-il d'une simple surcharge de streaming? Vous devez toujours maintenir le mappage si vous ne voulez pas faire de macro magique, mais je le trouve plus propre que votre solution d'origine.

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

5
1) il crée encore plus de duplication 2) il vous oblige à utiliser des flux
Karoly Horvath

6
-1 Désolé @dau_sama mais le but de toutes ces questions récurrentes d' énumération en chaîne est d'éviter la maintenance du mappage enum / chaîne. Si vous pensez que votre réponse ne correspond pas à l'objectif, veuillez envisager de supprimer la réponse. Bonne chance pour votre prochaine réponse;)
Santé

-9

La manière la plus simple?
Utilisez Ada: Enumeration'Image( Value )fait exactement ce que vous voulez. Si vous avez vraiment besoin de C ++, vous pouvez essayer d'exporter la fonction:

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

4
Comment cela répond-il à la question? La question indique clairement la conversion d'une énumération en chaîne dans le C ++ moderne.
Michael Choi

1
@MichaelChoi - Il fait, mais il y a aussi la question de l' utilisation de l'outil approprié pour le travail: juste parce que C ++ est Turing-complet et ne peut donc résoudre tous les problèmes résolubles ne pas dire que la solution est: rapide, maintenable ou efficace. Utiliser une langue qui a la fonctionnalité appropriée / souhaitée et l'exporter est une solution valide.
Shark8

3
Dans la première phrase de la question "cette question concerne l'utilisation des nouvelles fonctionnalités C ++". puis "[je n'ai pas encore trouvé] de façon élégante d'utiliser les nouvelles fonctionnalités de C ++ 11, C ++ 14 ou C ++ 17". L'auteur cherchait clairement une solution C ++; vous avez donné une solution à Ada, donc vous n'avez pas répondu à la question. Vous proposez d'incorporer une dépendance entièrement différente pour résoudre quelque chose qui n'était probablement pas dans la portée des questions.
Michael Choi
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.