enum în string în C++11 / C++14 / C++17 și viitorul C++20 modern (Programare, C++, String, Enums, C++17, C++20)

oHo a intrebat.

Spre deosebire de toate celelalte întrebări similare, această întrebare se referă la utilizarea noilor caracteristici C++.

După ce am citit multe răspunsuri, nu am găsit încă nici unul:

  • Mod elegant folosind C++11, , C++14 sau C++17 caracteristici noi
  • Sau ceva gata de utilizare în Boost
  • Sau ceva planificat pentru C++20

Exemplu

Un exemplu este adesea mai bun decât o explicație lungă.
Puteți compila și rula acest fragment pe Coliru.
(Un alt exemplu anterior este, de asemenea, disponibil)

#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) <<'
';
   std::cout << magic(MyClass::MyEnum::BBB) <<'
';
   std::cout << magic(MyClass::MyEnum::CCC) <<'
';
}

Constrângeri

  • Vă rugăm să nu se dubleze fără valoare alte răspunsuri sau legătură de bază.
  • Vă rugăm să evitați răspunsul bazat pe macrobloat, sau încercați să reduceți #define cât mai puțin posibil.
  • Vă rugăm să nu folosiți manuale enum -> string cartografiere.

Este bine să aveți

  • Suport enum valori care încep de la un număr diferit de zero
  • Suportă valori negative enum valorile negative
  • Suportă valori fragmentate enum valori fragmentate
  • Suportă class enum (C++11)
  • Suport class enum : <type> având orice permis <type> (C++11)
  • Conversii la compilare (nu la execuție) într-un șir de caractere,
    sau cel puțin execuție rapidă în timpul execuției (de ex. std::map nu este o idee grozavă…)
  • constexpr (C++11, apoi relaxat în C++14/17/20)
  • noexcept (C++11)
  • C++17/C++20 Fragment prietenos

O posibilă idee ar putea fi utilizarea capacităților compilatorului C++ pentru a genera cod C++ în momentul compilării, folosind trucuri de metaprogramare bazate pe variadic template class și constexpr funcții…

Comentarii

  • (poate că este un subiect de discuție), consultați acest blog legat de Qt. woboq.com/blog/reflection-in-cpp-and-qt-moc.html. Descrie o posibilitate de înlocuire a moc(meta-object-compiler) de la Qt prin utilizarea reflecției C++(standard propus). –  > Por ibre5041.
  • N4113: std::enumerator::identifier_v<MyEnum, MyEnum::AAA> –  > Por ecatmur.
  • Eu personal am rezolvat această problemă implementând o mică bibliotecă utilitară de preprocesor care îmi permite să fac o buclă peste macroargumente variadice și să execut o funcție peste toate. Trec valorile enum ca argumente ale macroului și generez automat enum-ul și array-ul de șiruri de caractere prin intermediul preprocesorului. Probabil că puteți face acest lucru și cu ajutorul preprocesorului Boost. –  > Por Vittorio Romeo.
  • totul trebuie să fie rezolvat cu C++? Este atât de ușor să generezi automat codul pentru reprezentarea șirurilor de caractere, doar câteva linii de cod. –  > Por Karoly Horvath.
  • „Vă rugăm să nu oferiți răspunsuri bazate pe macro-uri C, dacă este posibil ” ei bine, dacă nu sunteți dispuși să așteptați C++17 nu prea există nimic utilizabil, și nu este rău să vă declarați enumerațiile ca DEC_ENUM(enumname, (a,b,c,(d,b),(e,42))) dacă nu trebuie să mențineți macroul generator… și, imho, introducerea unor astfel de cazuri în limbaj este doar un alt fel de hack în locul unui hibrid șablon/macro mai puternic. Nu ar trebui să adăugăm toate aceste cazuri utile de utilizare a macrourilor în limbaj doar pentru a putea spune că macrourile nu mai au nicio utilitate. –  > Por PlasmaHH.
29 răspunsuri
Neargye

Magic Enum biblioteca numai pentru antet oferă reflecție statică pentru enumerații (la șir, de la șir, iterație) pentru 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
};

Pentru mai multe exemple, verificați depozitul de acasă https://github.com/Neargye/magic_enum.

Unde este dezavantajul?

Această bibliotecă utilizează un hack specific compilatorului (bazat pe __PRETTY_FUNCTION__ / __FUNCSIG__), care funcționează pe Clang >= 5, MSVC >= 15.3 și GCC >= 9.

Valoarea enum trebuie să fie în intervalul [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • În mod implicit MAGIC_ENUM_RANGE_MIN = -128, , MAGIC_ENUM_RANGE_MAX = 128.

  • Dacă aveți nevoie de un alt interval pentru toate tipurile enum în mod implicit, redefiniți macroul MAGIC_ENUM_RANGE_MIN și MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MIN trebuie să fie mai mică sau egală cu 0 și trebuie să fie mai mare decât INT16_MIN.

  • MAGIC_ENUM_RANGE_MAX trebuie să fie mai mare decât 0 și trebuie să fie mai mic decât INT16_MAX.

  • În cazul în care este nevoie de un alt interval pentru un anumit tip de enumerație, adăugați specializarea enum_range pentru tipul de enumerație necesar.

    #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;
    };
    }
    

Comentarii

  • De ce limitele intervalului? Este pentru a limita un fel de adâncime de recursivitate sau din cauza unui fel de căutare liniară la compilare? –  > Por Emile Cormier.
  • Acest lucru este uimitor. Vă mulțumesc! Probabil că este chiar eficient dacă compilatorul este suficient de inteligent pentru a evalua constexpr std::array o singură dată. Foarte, foarte frumos. –  > Por iestyn.
  • @EmileCormier Limitele intervalului sunt necesare deoarece biblioteca trebuie să sondeze fiecare valoare posibilă din interval pentru a vedea dacă corespunde unui enumerator. Ea instanțiază un is_valid șablon de funcție pentru fiecare valoare din intervalul [-128, 127]. Acest lucru poate duce la timpi de compilare mari, astfel încât intervalul este destul de conservator în mod implicit. Iată o versiune simplificată a tehnicii: godbolt.org/z/GTxfva –  > Por Alastair Harrison.
  • pentru mine, cel mai important dezavantaj este faptul că eșuează silențios: godbolt.org/z/TTMx1v Există o restricție privind dimensiunea valorilor, dar atunci când constrângerea nu este respectată, nu există nicio eroare de compilare, nicio excepție, ci doar un șir gol returnat. –  > Por MateuszL.
antron

(Abordarea better_enums library)

Există o modalitate de a face enum la string în C++ curent care arată astfel:

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

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

Utilizare:

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...

Toate operațiile pot fi făcute constexpr. De asemenea, puteți implementa propunerea de reflecție C++17 menționată în răspunsul lui @ecatmur.

  • Există doar un singur macro. Cred că acesta este minimul posibil, deoarece stringerea preprocesorului (#) este singura modalitate de a converti un token într-un șir de caractere în C++ actual.
  • Macroul este destul de discret – declarațiile constantelor, inclusiv inițializatoarele, sunt lipite într-o declarație enum încorporată. Acest lucru înseamnă că au aceeași sintaxă și semnificație ca într-o declarație enum încorporată.
  • Repetiția este eliminată.
  • Implementarea este cea mai naturală și mai utilă cel puțin în C++11, datorită constexpr. De asemenea, poate fi făcută să funcționeze și în C++98 +. __VA_ARGS__. Este cu siguranță un C++ modern.

Definiția macroului este oarecum complicată, așa că răspund în mai multe moduri.

  • Cea mai mare parte a acestui răspuns este o implementare care cred că este potrivită pentru constrângerile de spațiu de pe StackOverflow.
  • Există, de asemenea, o articol CodeProject care descrie elementele de bază ale implementării într-un tutorial de lungă durată. [Ar trebui să îl mut aici? Cred că este prea mult pentru un răspuns SO].
  • Există un bibliotecă completă „Better Enums” care implementează macroul într-un singur fișier header. De asemenea, implementează N4428 Interogări ale proprietăților de tip, , revizuirea actuală a propunerii de reflecție N4113 din C++17. Astfel, cel puțin pentru enumerațiile declarate prin această macro, puteți avea reflectarea enumurilor propusă în C++17 acum, în C++11/C++14.

Este simplu să extindeți acest răspuns la caracteristicile bibliotecii – nimic „important” nu este omis aici. Cu toate acestea, este destul de anevoios și există probleme de portabilitate a compilatorului.

Disclaimer: Eu sunt autorul atât al articolului CodeProject, cât și al bibliotecii.

Puteți încerca biblioteca codul din acest răspuns, , biblioteca, , și implementarea lui N4428 trăiesc online în Wandbox. Documentația bibliotecii conține, de asemenea, o prezentare generală a modului de utilizare a bibliotecii ca N4428, , care explică partea de enumerație a acestei propuneri.


Explicație

Codul de mai jos implementează conversiile între enums și șiruri de caractere. Cu toate acestea, el poate fi extins pentru a face și alte lucruri, cum ar fi iterația. Acest răspuns înfășoară un enum într-un struct. Puteți genera, de asemenea, un traits struct alături de un enum.

Strategia este de a genera ceva de genul acesta:

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 */ }
};

Problemele sunt:

  1. Vom sfârși cu ceva de genul {Red = 1, Green, Blue} ca inițializator pentru matricea de valori. Acest lucru nu este valid în C++, deoarece Red nu este o expresie atribuibilă. Acest lucru se rezolvă prin turnarea fiecărei constante la un tip T care are un operator de atribuire, dar care va renunța la atribuire: {(T)Red = 1, (T)Green, (T)Blue}.
  2. În mod similar, vom ajunge la {"Red = 1", "Green", "Blue"} ca inițializator pentru tabloul de nume. Va trebui să tăiem " = 1". Nu cunosc o modalitate foarte bună de a face acest lucru la compilare, așa că vom amâna acest lucru pentru execuție. Ca rezultat, _to_string nu va fi constexpr, , ci _from_string poate fi în continuare constexpr, , deoarece putem trata spațiile albe și semnele de egalitate ca și terminatori atunci când comparăm cu șiruri de caractere nereglementate.
  3. Ambele variante de mai sus au nevoie de o macro de „mapare” care poate aplica o altă macro la fiecare element din __VA_ARGS__. Acest lucru este destul de standard. Acest răspuns include o versiune simplă care poate gestiona până la 8 elemente.
  4. Dacă macroul trebuie să fie cu adevărat autonom, acesta nu trebuie să declare date statice care să necesite o definiție separată. În practică, acest lucru înseamnă că matricele au nevoie de un tratament special. Există două soluții posibile: constexpr (sau doar const) în cadrul spațiului de nume, sau array-uri obișnuite în cadrul unui spațiu de nume care nu este de tipconstexpr statice în funcții inline. Codul din acest răspuns este pentru C++11 și adoptă prima abordare. Articolul din CodeProject este pentru C++98 și adoptă cea de-a doua abordare.

Cod

#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[] = " =tr
";

// The size of terminators includes the implicit ''.
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] == '' :
        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] = '';                      
            }                                                         
                                                                      
            initialized = true;                                       
        }                                                             
                                                                      
        return the_names;                                             
    }                                                                 
};

și

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

Programul de mai sus tipărește Red, , așa cum v-ați aștepta. Există un anumit grad de siguranță a tipului, deoarece nu puteți crea un enum fără să-l inițializați și ștergerea unuia dintre cazurile de la switch va avea ca rezultat un avertisment din partea compilatorului (în funcție de compilator și de indicatori). De asemenea, rețineți că "Red" a fost convertit într-un enum în timpul compilării.

Comentarii

  • Heya @mrhthepie, îmi pare rău că editarea ta a fost respinsă. Tocmai am văzut e-mailul despre asta. Am de gând să o încorporez în răspuns – mulțumesc pentru remedierea bug-ului! –  > Por antron.
  • asta e grozav. Ar funcționa și dacă aș vrea un enum de biți? Ca și cum aș vrea un enum de BitFlags, fiecare dintre ele fiind 1U decalată cu o anumită valoare? –  > Por user3240688.
  • se pare că există o scurgere de memorie în _trimmed_names() în codul pe care l-ați postat aici (new char[length + 1] dar nu setați initialized la true). îmi scapă ceva? Nu văd aceeași problemă în codul tău de pe github. –  > Por user3240688.
  • Este setat la true, , dar în afara domeniului if ramură (scurgere de memorie prinsă inițial de @mrhthepie). Ar trebui mutat înăuntru… Editare. Mulțumesc pentru că te-ai uitat atent atât la acest cod, cât și la codul GH. –  > Por antron.
  • to_string ar putea returna un string_view din C++17, care nu necesită terminarea nulă, și să devină constexpr. –  > Por Yakk – Adam Nevraumont.
ecatmur

Pentru C++17 C++20, veți fi interesați de activitatea Grupului de studiu pentru reflectare (SG7). Există o serie paralelă de documente care acoperă formularea (P0194) și justificare, proiectare și evoluție (P0385). (Legăturile duc la cel mai recent articol din fiecare serie.)

Începând cu P0194r2 (2016-10-15), sintaxa ar urma să utilizeze formula propusă reflexpr cuvânt-cheie:

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

De exemplu (adaptat din ramura reflexpr a lui Matus Choclik din clang):

#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;
}

Reflecția statică nu a reușit să intre în C++17 (mai degrabă, în proiectul probabil-final prezentat la reuniunea privind standardele din noiembrie 2016 de la Issaquah), dar există încredere că va intra în C++20; din raportul de călătorie al lui Herb Sutter:

În special Reflection study group a revizuit cea mai recentă propunere de reflecție statică fuzionată și a considerat-o pregătită pentru a intra în grupurile principale de evoluție la următoarea noastră întâlnire pentru a începe să ia în considerare propunerea unificată de reflecție statică pentru un TS sau pentru următorul standard.

Comentarii

  • @antron îmi pare rău că editarea ta a fost respinsă; aș fi aprobat-o dacă aș fi văzut-o la timp. Nu văzusem N4428, așa că vă mulțumesc că m-ați informat. –  > Por ecatmur.
  • Nicio problemă, mulțumesc pentru încorporare. Mă cam întreb de ce a fost respinsă. Înțeleg motivul boilerplate „nu o face mai precisă”, dar e clar că e mai precisă pentru ziua de azi. –  > Por antron.
  • Mulțumesc 🙂 Am împărțit exemplul final pentru a evita bara de defilare orizontală. Ce păcat că valoarea MyEnum::AAA nu poate fi trecută ca al doilea argument al std::meta::get_enumerators_m :-/ –  > Por oHo.
  • Faptul că o sarcină atât de simplă din punct de vedere conceptual necesită 3 niveluri de argumente de șabloane imbricate este foarte supradimensionat. Sunt sigur că există motive specifice, tehnice pentru asta. Dar asta nu înseamnă că rezultatul final este ușor de utilizat. Îmi place C++ și codul are sens pentru mine. Dar 90% dintre ceilalți programatori cu care lucrez zilnic se feresc de C++ din cauza unui cod ca acesta. Sunt dezamăgit că nu am văzut soluții mai simple și mai integrate. –  > Por void.pointer.
  • Se pare că estimarea actuală pentru includerea în standard a viitoarei TS Reflection este următoarea C++23: herbsutter.com/2018/04/02/… –  > Por Tim Rae.
Danilo Ramos

Acest lucru este similar cu Yuri Finkelstein; dar nu necesită stimulare. Folosesc o hartă, astfel încât să puteți atribui orice valoare la enums, în orice ordine.

Declararea clasei enum ca:

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

Următorul cod va crea automat clasa enum și supraîncărcarea:

  • ‘+’ ‘+=’ pentru std::string
  • ‘<<‘ pentru fluxuri
  • ‘~’ doar pentru a converti în șir de caractere (orice operator unar este suficient, dar personal nu-mi place pentru claritate).
  • ‘*’ pentru a obține numărul de enumerații

Nu este necesar niciun boost, toate funcțiile necesare sunt furnizate.

Cod:

#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;
}

Exemplu:

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;
}

Puteți rula codul aici

Comentarii

  • Putem avea pauze de linie în interiorul acestei definiții de macro? –  > Por einpoklum.
  • Am adăugat supraîncărcarea pentru * pentru a obține numărul de enumerații… Sper că nu vă deranjează 🙂 –  > Por Peter VARGA.
  • Există vreun motiv pentru care această implementare folosește std::map (O(log(n(n))) indexare) în loc de std::unordered_map (O(1) indexare)? –  > Por River Tam.
  • de asemenea, cred că metodele ar trebui să fie marcate inline astfel încât să puteți declara enums în fișierele de antet în mod normal, fără a primi erori de „definiție multiplă a” de la linker. (nu sunt sigur dacă aceasta este de fapt cea mai curată/cea mai bună soluție, totuși) – -.  > Por River Tam.
  • (îmi pare rău că fac spam, dar se pare că nu pot să editez comentariile astăzi) există și alte probleme legate de faptul că acest lucru se află într-un fișier antet. Harta (E##MapName) trebuie să fie mutată într-o unitate de compilare care are acces și la enum. Am creat o soluție, dar nu este foarte curată și ar trebui să obțin permisiunea de a o împărtăși. Deocamdată, comentez doar pentru a spune că nu are rost să marcăm metodele inline fără caracteristicile suplimentare necesare pentru a susține utilizarea într-un fișier antet. –  > Por River Tam.
StackedCrooked

În 2011 am petrecut un weekend pentru a pune la punct o soluție bazată pe macro-uri și am sfârșit prin a nu o folosi niciodată.

Procedura mea actuală este să pornesc Vim, să copiez enumeratorii într-un corp de comutație gol, să pornesc un nou macro, să transform primul enumerator într-o declarație de caz, să mut cursorul la începutul liniei următoare, să opresc macroul și să generez restul instrucțiunilor case prin rularea macroului pe ceilalți enumeratori.

Macrogramele Vim sunt mai distractive decât macrogramele C++.

Exemplu din viața reală:

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

Voi crea acest lucru:

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);
}

Și așa mă descurc.

Suportul nativ pentru stringificarea enum ar fi totuși mult mai bun. Sunt foarte interesat să văd rezultatele grupului de lucru pentru reflecție în C++17.

O modalitate alternativă de a face acest lucru a fost postată de @sehe în secțiunea comentarii.

Comentarii

  • Eu fac exact acest lucru. Deși, de obicei, folosesc Surround vim și selecții de blocuri de-a lungul drumului…  > Por sehe.
  • @sehe Interesant. Ar trebui să arunc o privire la „surround”, deoarece am nevoie de mult prea multe apăsări de taste în prezent. –  > Por StackedCrooked.
  • Aici este în întregime în gory, fără macro-uri (cu excepția cazului în care . contează): i.imgur.com/gY4ZhBE.gif –  > Por sehe.
  • Gif-ul animat e drăguț, dar e greu de spus când începe și când se termină, și cât de departe suntem. … de fapt, șterge asta, nu e drăguț, e deranjant. Eu zic să îl omorâm. –  > Por einpoklum.
  • Această abordare de selectare a blocurilor în vim este drăguță și toate cele, dar de ce nu folosiți pur și simplu ceva de genul :'<,'>s/ *(.*)=.*/case EtherType::1: return os << "1";/? –  > Por Ruslan.
PaperBirdMaster

Nu știu dacă o să vă placă sau nu, eu nu sunt prea mulțumit de această soluție, dar este o abordare prietenoasă cu C++14, deoarece folosește variabile de șablon și abuzează de specializarea șablonului:

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> << '
';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '
';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

Cel mai rău la această abordare este că este o durere de întreținut, dar este, de asemenea, o durere de întreținut unele dintre alte abordări similare, nu-i așa?

Puncte bune despre această abordare:

  • Utilizarea variabilelor temporizate (caracteristică C++14)
  • Cu ajutorul specializării șabloanelor putem „detecta” când este utilizată o valoare invalidă (dar nu sunt sigur că acest lucru ar putea fi util).
  • Arată bine.
  • Căutarea numelui se face în momentul compilării.

Exemplu real

Editați

Misterious user673679 ai dreptate; abordarea șablonului de variabile C++14 nu se ocupă de cazul de execuție, a fost vina mea că am uitat 🙁

Dar putem totuși să folosim unele caracteristici moderne ale C++ și șablonul variabil plus șablonul variadic pentru a realiza o traducere în timp de execuție din valoare enum în șir de caractere… este la fel de deranjant ca și celelalte, dar merită totuși menționat.

Să începem să folosim un alias de șablon pentru a scurta accesul la o hartă enum-șir:

// 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{};

Apoi, șablonul șablon 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 ...);
}

cel mai bun truc” aici este utilizarea șablonului variabil pentru harta care conține valorile și numele fiecărei intrări enum; această hartă va fi aceeași în fiecare unitate de traducere și va avea același nume peste tot, deci este destul de simplu și îngrijit, dacă apelăm la initialize în felul următor:

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

Atribuim nume pentru fiecare MyEnum intrare și pot fi utilizate în timpul execuției:

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

Dar poate fi îmbunătățită cu SFINAE și supraîncărcare. << operator:

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);
}

Cu ajutorul unui operator << acum putem utiliza enum-ul în acest mod:

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

Acest lucru este, de asemenea, deranjant pentru întreținere și poate fi îmbunătățit, dar sper că ați înțeles ideea.

Exemplu real

Comentarii

  • Acest lucru pare destul de îngrijit (este posibil să nu se definească pur și simplu variabila nespecializată?). Poate că îmi scapă ceva, totuși, deoarece nu văd cum se gestionează cazul de execuție. –  > Por user673679.
  • @Paula_plus_plus: Nu ar trebui să folosiți doar un std::array în loc de harta greoaie? Va deveni preferabil doar pentru enumerațiile care încep de la… cât, 2^10 valori? Poate chiar mai mult. –  > Por einpoklum.
  • @einpoklum ar fi uimitor dacă am putea asigura în momentul execuției câte elemente are o enum au. Din păcate, nu putem. Și tot scopul hărții este doar de a asocia nume cu valori, ceea ce este ceea ce std::map este bun pentru asta. –  > Por PaperBirdMaster.
  • @Paula_plus_plus: Deja chemi un initialize() al cărei număr de argumente este numărul de valori enum, deci știți numărul de valori la compilare. Doar valoarea specifică pe care ți se cere să o tipărești este cunoscută doar la momentul execuției. De asemenea, chiar dacă nu ați cunoaște acest număr, un std::vector ar fi mai rapid decât un std::map, din nou, în aproape toate cazurile realiste. –  > Por einpoklum.
  • @einpoklum aceasta este o observație foarte bună, într-adevăr, mă voi gândi la asta, mulțumesc! Singurul lucru care mă îngrijorează este că std::array nu este un container cheie-valoare și, prin urmare, îi lipsesc metodele de căutare; oricum, mă voi gândi la asta. –  > Por PaperBirdMaster.
eferion

În cazul în care enum arată ca

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

Puteți muta conținutul din enum într-un nou fișier:

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

Și apoi valorile pot fi înconjurate de un 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

Următorul pas poate fi includerea elementelor în enum din nou:

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

Și în cele din urmă puteți genera funcții de utilitate despre asta 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");
}

Soluția poate fi aplicată la standarde C++ mai vechi și nu folosește elemente moderne C++, dar poate fi folosită pentru a genera mult cod fără prea mult efort și întreținere.

Comentarii

  • Nu este nevoie de un fișier separat. Acesta este în esență un x-macro. –  > Por HolyBlackCat.
  • @HolyBlackCat dacă împărțiți soluția în câteva fișiere, puteți reutiliza valorile enum pentru diferite scopuri –  > Por eferion.
  • Încerc să vă spun că puteți face același lucru dacă puneți lista de valori într-un singur macro alături de definiția enum într-un antet. –  > Por HolyBlackCat.
  • @HolyBlackCat da, vă înțeleg, dar prefer această soluție. pe de altă parte, această soluție poate fi găsită în codul sursă clang, așa că eu cred că este o modalitate bună de a rezolva problema –  > Por eferion.
  • Destul de corect. Nu ar fi trebuit să nu fi votat în jos acest lucru cred, deoarece poate avea într-adevăr unele utilizări. (Scuzați editarea fictivă, altfel sistemul îmi blochează votul) – -.  > Por HolyBlackCat.
Mense

Am avut aceeași problemă acum câteva zile. Nu am putut găsi nicio soluție C++ fără o magie ciudată de macro, așa că am decis să scriu un generator de cod CMake pentru a genera declarații simple de tip switch case.

Utilizare:

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)>
)

Funcția caută fișierele de includere din sistemul de fișiere (utilizează directoarele de includere furnizate cu comanda include_directories), le citește și face o regexare pentru a genera clasa și funcția (funcțiile).

NOTĂ: constexpr implică inline în C++, astfel încât utilizarea opțiunii USE_CONSTEXPR va genera o clasă doar pentru antet!

Exemplu:

./includes/a.h:

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")

Generează:

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

Actualizare:

Scriptul suportă acum și enumerările cu domeniu de cuprindere (enum class|struct) și l-am mutat într-un repo separat cu alte câteva scripturi pe care le folosesc des: https://github.com/mensinda/cmakeBuildTools

Comentarii

  • wow! O idee foarte originală și inovatoare 🙂 Sper că aveți curajul să vă actualizați generatorul pentru a oferi un constexpr și noexcept versiune 😉 De asemenea, tocmai am privit proiectul tău GitHub 😉 Noroc –  > Por oHo.
  • Am actualizat generatorul. Funcțiile vor fi acum întotdeauna constexpr și enum : <type> este acum acceptat. Mulțumesc pentru stea 🙂 –  > Por Mense.
  • Legătura este stricată… -.- –  > Por yeoman.
  • Linkul este acum fixat. –  > Por Mense.
PlasmaHH

Conform solicitării lui OP, iată o variantă desprinsă din soluția macro urâtă bazată pe Boost Preprosessor și Variadic Macros.

Aceasta permite o sintaxă simplă de tip listă a elementelor enumeratorului, împreună cu stabilirea valorilor pentru elemente specifice, astfel încât

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

se extinde la

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

Împreună cu funcțiile necesare pentru a ieși și a face conversia înapoi. Acest macro a fost aici de mult timp și nu sunt sigur că este cel mai eficient mod sau că este un mod conform, dar de atunci a funcționat.

Codul complet poate fi văzut în acțiune la ambele adrese Ideone și Coliru.

Urâțenia sa gargantuescă este mai sus; l-aș fi pus în spatele spoilerelor pentru a vă proteja ochii, dacă aș fi știut cum, dar Markdown nu mă place.

Biblioteca (reunită într-un singur fișier header)

#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)

Utilizare

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

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

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

Compilare (copiați și lipiți antetul în 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)));
                               ^

Ieșire

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

Comentarii

  • Acest bloc de cod este o călătorie nebună prin peisajele uimitoare ale magiei negre a metaprogramării. De fapt, m-am simțit ușurat când am ajuns la main – Casă, dulce casă! –  > Por Quentin.
  • Tocmai am adăugat un link către coliru pentru a verifica ieșirea (există unele avertismente, faceți clic pe linkul din cadrul răspunsului). De asemenea, am împărțit în Lib/Usage. Lucrurile namespace xxx pot fi mutate în locul antetului? Puteți spune în introducere că utilizați boost/preprocessor.hpp și, prin urmare, răspunsul este compatibil cu C++ modern. Vă rugăm să remediați avertismentele și să curățați puțin codul sursă pentru o calitate mai bună. –  > Por oHo.
  • @olibre: Este copypastad din cred că 5 anteturi diferite din biblioteca noastră. Enum_cast este dintr-o altă parte mai generală, dar m-am gândit să o adaug și eu pentru a vedea la ce folosește do_enum_cast din macro… Avertizările sunt doar de la main<tab> de vim care include arginți pe care nu îi folosesc. Nu cred că acest cod poate fi curățat cu adevărat, este doar pentru a arăta ce se poate face și ce nu ar trebui să se facă 😉 și dacă îl schimb aici nu mai este codul pe care îl folosesc în producție… este unul dintre acele lucruri fragile pe care odată ce funcționează ar fi bine să nu le atingi niciodată deoarece s-ar putea prăbuși în moduri pe care nimeni nu le-ar putea prezice. –  > Por PlasmaHH.
  • În regulă Plasma, văd că acest lucru poate fi văzut ca un Dovada conceptului. Dar este prea mult macro overhead pentru a fi votat. Cu toate acestea, mulțumesc pentru partajare. Noroc –  > Por oHo.
  • Bună Plasma. Am efectuat o curățare profundă a codului sursă + completat de compilare și de ieșire de execuție. Vă rugăm să verificați editarea mea. Sper că acest lucru este OK pentru tine. Este răspunsul mai valoros? Cu toate acestea, macro overhead-ul este încă oribil! Vă doresc o zi bună 🙂 Noroc –  > Por oHo.
yeoman

Generează-ți doar enumerațiile. Scrierea unui generator în acest scop este o muncă de aproximativ cinci minute.

Codul generatorului în java și python, super ușor de portat în orice limbaj pe care îl doriți, inclusiv în C++.

De asemenea, super ușor de extins cu orice funcționalitate doriți.

exemplu de intrare:

First = 5
Second
Third = 7
Fourth
Fifth=11

antet generat:

#include <iosfwd>

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

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

fișier cpp generat

#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;
}

Și generatorul, într-o formă foarte laconică, ca șablon pentru portare și extindere. Acest cod de exemplu încearcă cu adevărat să evite suprascrierea oricărui fișier, dar folosiți-l totuși pe propriul risc.

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);
        }
    }
}

Și o portare la Python 3.5, deoarece este suficient de diferită pentru a fi potențial utilă

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("
")
    headerWriter.write("#include <iosfwd>
")
    headerWriter.write("
")
    headerWriter.write("enum class " + enumName + "
")
    headerWriter.write("{
")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",
")

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

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

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

Comentarii

  • Vă mulțumesc foarte mult pentru că ați împărtășit generatorul dvs. în două limbi 🙂 Dar aveți vreo idee cum să generați la compilare? De exemplu, ne putem imagina traducerea generatorului dvs. folosind declarații CMake pentru a reîmprospăta codul generat în C++ atunci când datele de intrare sunt modificate? Visul meu este să forțez compilatorul C++ să genereze enumerații la compilare folosind metaprogramarea (variadic template class și constexpr funcții). –  > Por oHo.
  • De asemenea, în cazul în care este prea complicat să adăugați o comandă cmake personalizată, puteți să vă automatizați IDE-ul sau să apelați manual gererator și să aveți rezultatul în controlul sursei. Este uneori o idee bună să ai oricum codul generat în controlul sursei, atâta timp cât nu este prea mult, iar oamenii înțeleg că nu trebuie să facă modificări manuale, pentru că uneori este interesant să te uiți la istoricul fișierelor generate atunci când depanezi ceva ciudat și ai suspiciunea că o modificare recentă a generatorului ar fi putut strica ceva 🙂 –  > Por yeoman.
  • În legătură cu generarea de lucruri la compilare, asta este atât de ușor în LISP, deoarece sintaxa este extrem de curată și ușoară. Acest lucru este ajutat de faptul că este tipizat dinamic, ceea ce îi permite să fie laconic și ușor de citit fără prea multă sintaxă. Echivalentul macrourilor LISP în C++ ar necesita o modalitate foarte complicată de a descrie AST-ul a ceea ce încercați să generați. Iar un AST pentru C++ nu este niciodată frumos 🙁 –  > Por yeoman.
  • Direct în Make în loc de cmake, este super ușor btw. Trebuie doar să generezi ținte .h și .cpp pentru fiecare fișier .enum prin find și să faci ca aceste ținte să depindă de respectivele definiții enum, astfel încât să fie generate din nou automat odată ce se schimbă fișierele .enum def. Probabil că este mult mai ușor în cmake, deoarece este plin de magie pentru acest tip de lucruri, dar eu folosesc în mod regulat Make, ant și gradle, dar am doar cunoștințe limitate despre Maven, cmake și grunt 🙂 –  > Por yeoman.
  • Mulțumesc pentru răspuns 🙂 Cred că majoritatea dezvoltatorilor C++ vor aprecia dacă generatorul tău ar putea detecta enumerațiile direct în codul C++, cum ar fi enum class Hallo{ First=5, Second=6, Third=7, Fourth=8}; sau în câteva linii 😀 Credeți că puteți adapta generatorul dvs. pentru a detecta un enum în cadrul unui fișier C++? Cel mai bine ar putea fi să generezi cod doar la detectarea unui tag de genul /*<Generate enum to string here>*/. Apoi, generatorul tău scrie pe loc codul C++ generat corespunzător (înlocuind codul generat anterior). ^_^ Ce generator minunat, nu-i așa? Noroc 🙂 –  > Por oHo.
Ignace

Și eu am fost frustrat de mult timp de această problemă, împreună cu problema de a obține un tip convertit în string într-un mod corespunzător. Totuși, pentru ultima problemă, am fost surprins de soluția explicată în Is it possible to print a variable’s type in standard C++?, folosind ideea din Can I obtain C++ type names in a constexpr way? Folosind această tehnică, se poate construi o funcție analogă pentru obținerea unei valori enum ca șir de caractere:

#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;
}

Codul de mai sus a fost testat doar pe Clang (a se vedea https://ideone.com/je5Quv) și VS2015, dar ar trebui să poată fi adaptat și la alte compilatoare, modificând puțin constantele de numere întregi. Bineînțeles, se folosesc în continuare macro-uri sub capotă, dar cel puțin nu este nevoie de acces la implementarea enum.

Comentarii

  • Aceasta nu funcționează cu g++ 6.3.0 și C++14. –  > Por einpoklum.
  • Interesant, deoarece enum-ul poate fi declarat în mod normal, fără a fi nevoie să îl înfășurați într-o macro. Deși nu-mi plac dependențele de compilator și constantele magice. –  > Por zett42.
Yuri Finkelstein

Am preluat ideea de la @antron și am implementat-o în mod diferit: generând un true enum class.

Această implementare îndeplinește toate cerințele enumerate în întrebarea originală, dar în prezent are doar o singură limitare reală: presupune că valorile enum fie nu sunt furnizate, fie, dacă sunt furnizate, trebuie să înceapă cu 0 și să urce secvențial, fără goluri.

Aceasta nu este o limitare intrinsecă – pur și simplu, nu folosesc valori enum ad-hoc. Dacă este necesar, se poate înlocui căutarea vectorială cu o implementare tradițională de tip switch/case.

Soluția folosește ceva c++17 pentru variabilele inline, dar acest lucru poate fi ușor de evitat dacă este necesar. De asemenea, soluția utilizează boost:trim din motive de simplitate.

Cel mai important, este nevoie de doar 30 de linii de cod și nu necesită macro-uri de magie neagră.Codul este mai jos. Este menit să fie pus în antet și inclus în mai multe module de compilare.

Poate fi folosit în același mod în care a fost sugerat mai devreme în acest fir:

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

Vă rog să-mi spuneți dacă este util și cum poate fi îmbunătățit în continuare.


#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); 
    } 

Jason Lim

Atâta timp cât sunteți de acord cu scrierea unui fișier separat .h/.cpp pereche separată pentru fiecare enum interogabil, această soluție funcționează cu aproape aceeași sintaxă și capacități ca un enum obișnuit în c++:

// 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
};

Site-ul .cpp este format din 3 linii de boilerplate:

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

Exemplu de utilizare:

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

Cod

Această soluție necesită 2 fișiere sursă:

// 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; }

…și

// 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

Explicație

Această implementare exploatează faptul că lista de elemente între paranteze a unei definiții enum poate fi utilizată și ca o listă de inițializatori între paranteze pentru inițializarea membrilor clasei.

Atunci când ETRAITS este evaluată în contextul EnumTraits.inl, , se extinde la o definiție statică a unui membru pentru elementul EnumTraits<> clasă.

Adresa EDECL transformă fiecare membru enum în valori de listă de inițializare care sunt ulterior trecute în constructorul membrului pentru a completa informațiile enum.

Adresa EnumInitGuard este concepută pentru a consuma valorile inițializatoare ale enumului și apoi să se prăbușească – lăsând o listă pură de date enum.

Beneficii

  • c++-sintaxă asemănătoare
  • Funcționează în mod identic atât pentru enum și enum class (*aproape)
  • Funcționează pentru enum tipuri cu orice tip numeric subiacent
  • Funcționează pentru enum tipuri cu valori de inițializare automate, explicite și fragmentate
  • Funcționează pentru redenumirea în masă (se păstrează legătura intellisense)
  • Numai 5 simboluri de preprocesor (3 globale)

* În contrast cu enums, , inițializatorii din enum class care fac referire la alte valori din același enum trebuie să aibă aceste valori complet calificate.

Dezavantaje

  • Necesită un fișier separat .h/.cpp pentru fiecare pereche enum
  • Depinde de un sistem complicat macro și include magie
  • Erori minore de sintaxă explodează în erori mult mai mari
  • Definirea class sau namespace enumerații cu domeniu de cuprindere nu este trivială
  • Nu există inițializare la compilare

Comentarii

Intellisense se va plânge puțin de accesul la membrii privați atunci când deschideți EnumTraits.inl, dar, deoarece macrourile extinse definesc de fapt membrii clasei, nu este o problemă.

#ifndef ENUM_INCLUDE_MULTI din partea de sus a fișierului de antet este o neplăcere minoră care ar putea fi probabil redusă la o macro sau ceva de genul acesta, dar este suficient de mică pentru a trăi cu ea în dimensiunea sa actuală.

Declararea unui enum din spațiul de nume necesită ca enum-ul să fie mai întâi declarat în cadrul spațiului său de nume, apoi definit în spațiul de nume global. În plus, orice inițializator de enum care utilizează valori ale aceluiași enum trebuie să aibă aceste valori complet calificate.

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

largest_prime_is_463035818

Nu sunt sigur dacă această abordare este deja acoperită într-unul dintre celelalte răspunsuri (de fapt, este, a se vedea mai jos). Am întâlnit problema de mai multe ori și nu am găsit o soluție care să nu folosească macro-uri ofuscate sau biblioteci terțe. Prin urmare, am decis să scriu propria mea versiune de macro-uri ofuscate.

Ceea ce vreau să activez este echivalentul

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

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

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

care ar trebui să tipărească

ONE
TWO
13

Nu sunt un fan al macrourilor. Cu toate acestea, cu excepția cazului în care c++ suportă în mod nativ conversia enumerațiilor în șiruri de caractere, trebuie să se utilizeze un fel de generare de cod și/sau macro-uri (și mă îndoiesc că acest lucru se va întâmpla prea curând). Folosesc un X-macro:

// 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

Cea mai mare parte a acestuia este definirea și nedefinirea simbolurilor pe care utilizatorul le va trece ca parametru către X-marco prin intermediul unui include. Utilizarea este de genul următor

#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"

Demonstrație live

Rețineți că nu am inclus încă alegerea tipului de bază. Nu am avut nevoie de ea până acum, dar ar trebui să fie simplu de modificat codul pentru a permite acest lucru.

Abia după ce am scris acest text mi-am dat seama că este destul de asemănător cu răspunsul lui Eferions. Poate că l-am citit înainte și poate că a fost principala sursă de inspirație. Întotdeauna am eșuat în înțelegerea macrocroselor X până când mi-am scris propriile mele macrocomenzi ;).

FKaria

Următoarea soluție se bazează pe o std::array<std::string,N> pentru un enum dat.

Pentru enum la std::string putem pur și simplu transforma enum-ul în size_t și să căutăm șirul din matrice. Operațiunea este O(1) și nu necesită alocare de heap.

#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)]; 
    } 
}

Pentru std::string la enum ar trebui să efectuăm o căutare liniară în tablou și să transformăm indicele tabloului în enum.

Încercați-o aici cu exemple de utilizare: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Editați: Mi-am modificat soluția pentru ca Enum-ul personalizat să poată fi utilizat în interiorul unei clase.

Comentarii

  • Vă mulțumesc pentru răspunsul interesant. Vă rugăm să reelaborați propunerea dvs. pentru a putea utiliza macroul dvs. în cadrul unei clase. A se vedea coliru.stacked-crooked.com/a/00d362eba836d04b În plus, încercați să utilizați constexprși noexept cuvinte cheie atunci când este posibil. Noroc 🙂 –  > Por oHo.
  • Întrebarea nu a specificat această cerință. –  > Por FKaria.
  • Întrebare actualizată (vezi exemplul). Alte două cerințe: (1) suportă tipul de enum și (2) valorile pot fi diferite de secvența 0, 1, 2… –  > Por oHo.
  • Mi-am refăcut soluția pentru a putea fi folosită în interiorul unei clase. Totuși, nu mi-am dat seama cum să fac ca valorile să fie diferite de 0, 1, 2,…. –  > Por FKaria.
  • Bună FKaria. Mulțumesc foarte mult pentru reelaborare. Am făcut câteva modificări pentru a suporta mai multe enumerații în cadrul aceleiași clase și, de asemenea, pentru a susține enum class X : Type format. Vă rog să revedeți contribuția mea: coliru.stacked-crooked.com/a/b02db919090d3491a3 Ce părere aveți despre modificările mele? Aveți vreo idee pentru a susține valorile stabilite în cadrul enum-ului? Exemplu enum E{A=3, B=6, C=A-B}; Noroc.  > Por oHo.
utilizator

Soluții care utilizează enum în cadrul clasei/structurii (struct defaults cu membri publici) și operatori supraîncărcați:

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";
        }
    }
};

Din exterior arată aproape exact ca o clasă enum:

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

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

Aceasta va produce „red 1 2”. Ați putea eventual supraîncărca << pentru a face ca albastru să scoată un șir de caractere (deși ar putea cauza ambiguitate, deci nu este posibil), dar nu ar funcționa cu Color::GREEN, deoarece nu se convertește automat în Color.

Scopul unei conversii implicite în Enum (care convertește implicit în int sau în tipul dat) este de a putea face acest lucru:

Color color;
switch (color) ...

Acest lucru funcționează, dar înseamnă, de asemenea, că și acest lucru funcționează:

int i = color;

Cu o clasă enum nu ar fi compilat.Ar trebui să aveți grijă dacă supraîncărcați două funcții care iau enum și un întreg, sau dacă eliminați conversia implicită…

O altă soluție ar implica utilizarea unei clase enum reale și a unor membri statici:

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

    //same as previous...
};

Este posibil să ocupe mai mult spațiu și să fie mai lungă de realizat, dar provoacă o eroare de compilare pentru conversiile implicite de int. Eu aș folosi-o pe aceasta din acest motiv!

Totuși, există cu siguranță o supraîncărcare cu acest lucru, dar cred că este mai simplu și arată mai bine decât alte coduri pe care le-am văzut. Există, de asemenea, un potențial de adăugare de funcționalități, care ar putea fi toate încadrate în clasa respectivă.

Editare: acest lucru funcționează și cele mai multe pot fi compilate înainte de execuție:

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;
};

Comentarii

  • Acest lucru este foarte interesant 🙂 Cu toate acestea, versiunea dvs. actuală implică faptul că trebuie să scrieți manual lucrurile case Enum::RED: return "red";. Întrebarea se referă la automatizarea acestor lucruri de către compilator (în momentul compilării). Ideea întrebării este de a modifica sau de a adăuga doar valorile enum fără a fi nevoie să actualizați lucrurile. toString(). Înțelegeți? Mulțumesc.  > Por oHo.
hutorny

Acest gist oferă o cartografiere simplă bazată pe șabloane variadice C++.

Aceasta este o versiune simplificată în C++17 a hărții bazate pe tipuri de la gist:

#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 exemplu de utilizare:

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;

map<KeyValues...> poate fi utilizată în ambele direcții:

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

Acest exemplu este disponibil pe godbolt.org

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

Rezultat din gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

Comentarii

  • Mod de metaprogramare foarte interesant. Am încercat să simplific puțin răspunsul pentru a fi autonom (fără a depinde de link-ul Gist). Pentru a fi concis și ușor de înțeles, am editat în final mult răspunsul dvs. Sunteți în continuare de acord cu modificările mele? Noroc 😉 –  > Por oHo.
malem

Soluție foarte simplă, cu o singură mare constrângere: nu puteți atribui valori personalizate la enum valori, dar cu regex-ul potrivit, ai putea. de asemenea, ai putea adăuga o hartă pentru a le traduce înapoi în enum valori fără mult mai mult efort:

#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__)

Exemplu de utilizare:

EnumToString(MyEnum, Red, Green, Blue);

Comentarii

  • Mulțumesc Malem pentru ideea ta inovatoare. Am editat răspunsul dvs. pentru a îmbunătăți lizibilitatea. Sper că vă plac modificările mele. Vă rugăm să continuați să vă îmbunătățiți răspunsul: (1) extindeți secțiunea „Exemplu de utilizare” cu ceva de genul auto name = MyEnumStrings["Red"]; — (2) De ce folosiți enum class? — (3) Sprijiniți enum class MyEnum : char { Red, Green, Blue };? — (4) Explicați funcția split() — (5) Aveți nevoie de parametri const std::regex& delim? — (6) Cum rămâne cu generarea MyEnumStrings în momentul compilării? => Puteți folosi constexpr? … Noroc 🙂 –  > Por oHo.
  • Îmi place foarte mult această abordare. Foarte scurtă și ușor de înțeles. –  > Por Anton Holmberg.
bit2shift

EDIT: verificați mai jos pentru o versiune mai nouă

Așa cum am menționat mai sus, N4113 este soluția finală la această problemă, dar va trebui să așteptăm mai mult de un an pentru a o vedea apărând.

Între timp, dacă doriți o astfel de funcție, va trebui să recurgeți la șabloane „simple” și la ceva magie de preprocesor.

Enumerator

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__);

Utilizare

#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
}

Explicație simplă

Enum<T>::m_counter este setat la 0 în interiorul fiecărei declarații de spațiu de nume.
(Ar putea cineva să-mi indice unde este menționat ^^acest comportament^^ în standard?)
Magia preprocesorului automatizează declararea enumeratorilor.

Dezavantaje

  • Nu este un adevărat enum și, prin urmare, nu poate fi promovat la int
  • Nu poate fi utilizat în cazurile de switch

Soluție alternativă

Aceasta sacrifică numerotarea liniilor (nu chiar) dar poate fi utilizată în cazurile de tip switch.

#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 0 intră în conflict cu -pedantic pe GCC și clang.

Soluție de rezolvare

Fie începeți de la #line 1 și scădeți 1 din __LINE__.
Sau nu utilizați -pedantic.
Și dacă tot suntem aici, evitați VC++ cu orice preț, a fost întotdeauna un compilator de doi bani.

Utilizare

#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;
}

Implementare și utilizare în viața reală

r3dVoxel – Enum
r3dVoxel – ELoggingLevel

Referință rapidă

#line lineno — cppreference.com

Nick

Ați putea folosi o bibliotecă de reflecție, cum ar fi 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"

TarmoPikaro

(analog al https://stackoverflow.com/a/54967187/2338477, ușor modificat).

Iată propria mea soluție cu un minimum de magie de definire și suport pentru atribuiri individuale de enumerații.

Aici este fișierul header:

#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;
}

Și aici este un exemplu de aplicație 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);

Versiunea actualizată a aceluiași fișier header va fi păstrată aici:

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

Madwyn

Am scris o bibliotecă pentru rezolvarea acestei probleme, totul se întâmplă în timpul compilării, cu excepția primirii mesajului.

Utilizare:

Utilizați macroul DEF_MSG pentru a defini o pereche de macro și mesaj:

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

CODE_OK este macroul care trebuie utilizat, iar "OK!" este mesajul corespunzător.

Utilizare get_message() sau doar gm() pentru a obține mesajul:

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

Utilizați MSG_NUM pentru a afla câte macro-uri au fost definite. Acesta va crește automat, nu trebuie să faceți nimic.

Mesaje predefinite:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Project: libcodemsg


Biblioteca nu creează date suplimentare. Totul se întâmplă în timpul compilării. În message_def.h, generează un fișier enum numit MSG_CODE; în message_def.c, , se generează o variabilă care conține toate șirurile de caractere din static const char* _g_messages[].

În acest caz, biblioteca se limitează la a crea un singur enum singură. Acest lucru este ideal pentru valorile de returnare, de exemplu:

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

MSG_CODE ret = foo();

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

Un alt lucru care îmi place din acest design este că puteți gestiona definițiile mesajelor în fișiere diferite.


Soluția la această întrebare mi se pare mult mai bună.

Comentarii

  • Bună Madwyn. Mulțumesc pentru ideea ta. Dar cum funcționează? Care este sarcina? (zero cheltuieli generale sau creează date suplimentare?). Propunerea dvs. pare bună, dar, din păcate, o afirmație DEF_MSG trebuie să fie folosită/actualizată/menținută pentru fiecare enum valoare :-/ Și asta este ceea ce, în mod ideal, am dori să nu mai facem… Noroc –  > Por oHo.
  • Mulțumesc pentru răspuns, @olibre. Vă rugăm să verificați răspunsul actualizat. Nu văd costuri suplimentare aici, cu excepția faptului că este necesar un apel de funcție pentru accesarea șirurilor de caractere. DEF_MSG face ca enum strâns legată de mesaj, deși are unele limitări. –  > Por Madwyn.
  • Vă mulțumesc pentru explicația anexată în răspunsul dumneavoastră 🙂 Librăria dvs. este în regulă, dar nu poate fi utilizată pentru mai multe enumerații :-/ Cum rămâne cu suportul pentru enum class (C++11) ? Puteți utiliza constexpr pentru a limita _g_messages în timpul execuției. Suportă mai multe enum tipuri (evitând _g_messages) utilizând metaprogramarea (tip transport de tip {enum-tip, enum-valoare}) sau poate variabile șablon (C++14). Cred că librăria dvs. nu se încadrează (încă?) în cerințele C++11/14/17. Ce părere aveți? Salutări 😉 –  > Por oHo.
  • Mulțumesc pentru urmărire. Am învățat ceva nou astăzi! Clasa enum și variabilele șablon arată bine. Cred că răspunsul meu a fost un pic „off topic”, deoarece a fost în stil C. –  > Por Madwyn.
desperado_98
#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;
    }

exemplu

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);

macro ENUM_MAKE generează automat ‘enum class’ și clasa helper cu ‘enum reflection function’.

Pentru a reduce numărul de greșeli, se definește deodată totul cu un singur ENUM_MAKE.

Avantajul acestui cod este creat automat pentru reflecție și o privire atentă la codul macro ,cod ușor de înțeles. ‘enum to string’ , ‘string to enum’ performanța ambelor este algoritmul O(1).

Dezavantajele sunt că la prima utilizare , clasa de ajutor pentru reflecția enum ‘s string vector și harta este inițializată.dar dacă doriți va fi de asemenea preinitializată. –

Comentarii

  • Deși acest cod poate răspunde la întrebare, ar fi mai bine să se explice cum se rezolvă problema fără a introduce altele și de ce să se folosească. Răspunsurile bazate doar pe cod nu sunt utile pe termen lung. –  > Por JAL.
  • hei băieți , îmi pare rău că nu vorbesc engleza foarte bine. –  > Por desperado_98.
  • automat macro ENUM_MAKE generează ‘enum class’ și clasa helper cu ‘enum reflection function’. / Pentru a reduce greșelile, deodată Totul este definit cu un singur ENUM_MAKE. Avantajul acestui cod este creat automat pentru reflecție și o privire atentă la codul macro ,cod ușor de înțeles. ‘enum to string’ , ‘string to enum’ performanța ambelor este algoritmul O(1). Dezavantaje este atunci când se utilizează pentru prima dată , clasa de ajutor pentru relecție enum ‘s string vector și harta este inițializată. dar Dacă doriți, veți fi, de asemenea, preinitializat. –  > Por desperado_98.
  • Salut desperado_98. Vă mulțumim pentru contribuția dumneavoastră. Te rog să editezi răspunsul și să inserezi în el conținutul comentariului tău. Compilatorul poate calcula exemplul tău la compilare dacă folosești câteva trucuri de meta-programare și constexpr. Mă refer la funcțiile toName() și toType() pot fi evaluate în timpul compilării și nu în timpul execuției (run-time). Vă rugăm să adoptați stilul C++11/14/17 în răspunsul dumneavoastră. Noroc 😉 –  > Por oHo.
  • În plus: Este macroul dvs. compatibil cu enum class MyEnum : short { A, B, C }; ? –  > Por oHo.
Mia Shani

soluția mea este fără utilizarea macroului.

avantaje:

  • vezi exact ce faci
  • accesul se face cu hărți hash, deci bun pentru enumerații cu multe valori
  • nu este nevoie să țineți cont de ordine sau de valori neconsecutive
  • atât traducerea din enum în șir de caractere, cât și din șir de caractere în enum, în timp ce valoarea enum adăugată trebuie adăugată într-un singur loc suplimentar

Dezavantaje:

  • trebuie să reproduceți toate valorile enum ca text.
  • accesul în harta hash trebuie să ia în considerare cazul șirurilor de caractere
  • întreținerea în cazul în care adăugarea de valori este dureroasă – trebuie să se adauge atât în harta enum cât și în cea de traducere directă

așa că… până în ziua în care C++ va implementa funcționalitatea Enum.Parse din C#, voi rămâne blocat cu acest lucru:

            #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];

cibercitizen1

Ei bine, încă o opțiune. Un caz tipic de utilizare este acela în care aveți nevoie de constante pentru verbele HTTP, precum și utilizarea valorilor versiunii sale de șiruri.

Exemplul:

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;
  }

}

Clasa VERB:

// -----------------------------------------------------------
// -----------------------------------------------------------
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 
";
  }

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

Comentarii

  • Pentru a reduce utilizarea memoriei, puteți înlocui membrul const std::string text cu doar theStrings[v]. Totuși, întrebarea se referă la caracteristicile din C++11/C++14/C++17/C++20 pentru a evita să fie nevoie să scrieți manual o astfel de clasă :-/ –  > Por oHo.
tensor5375

Răspunsul meu este aici.

Puteți obține numele valorilor enum și acești indici simultan ca deque de string.

Această metodă are nevoie doar de puțin copy și paste și editare.

Rezultatul obținut are nevoie de type-casting de la size_t la tipul clasei enum atunci când aveți nevoie de valoarea tipului clasei enum, dar cred că este un mod foarte portabil și puternic de a trata clasa 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;
}

user1095108

Cei 3 cenți ai mei, deși aceasta nu corespunde complet cu ceea ce dorește op-ul. Iată referința relevantă.

namespace enums
{

template <typename T, T I, char ...Chars>
struct enums : std::integral_constant<T, I>
{
  static constexpr char const chars[sizeof...(Chars)]{Chars...};
};

template <typename T, T X, typename S, std::size_t ...I>
constexpr auto make(std::index_sequence<I...>) noexcept
{
  return enums<T, X, S().chars[I]...>();
}

#define ENUM(s, n) []() noexcept{
  struct S { char const (&chars)[sizeof(s)]{s}; };
  return enums::make<decltype(n), n, S>(
    std::make_index_sequence<sizeof(s)>());}()

#define ENUM_T(s, n)
  static constexpr auto s ## _tmp{ENUM(#s, n)};
  using s ## _enum_t = decltype(s ## _tmp)

template <typename T, typename ...A, std::size_t N>
inline auto map(char const (&s)[N]) noexcept
{
  constexpr auto invalid(~T{});

  auto r{invalid};

  return
    (
      (
        invalid == r ?
          r = std::strncmp(A::chars, s, N) ? invalid : A{} :
          r
      ),
      ...
    );
}

}

int main()
{
  ENUM_T(echo, 0);
  ENUM_T(cat, 1);
  ENUM_T(ls, 2);

  std::cout << echo_enum_t{} << " " << echo_enum_t::chars << std::endl;

  std::cout << enums::map<int, echo_enum_t, cat_enum_t, ls_enum_t>("ls")) << std::endl;

  return 0;
}

Deci, generați un tip, pe care îl puteți converti într-un număr întreg și/sau un șir de caractere.

Joma

Soluția mea, folosind o definiție de preprocesor.

Puteți verifica acest cod pe https://repl.it/@JomaCorpFX/nameof#main.cpp

#include <iostream>
#include <stdexcept>
#include <regex>

typedef std::string String;
using namespace std::literals::string_literals;

class Strings
{
public:
    static String TrimStart(const std::string& data)
    {
        String s = data;
        s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
            return !std::isspace(ch);
        }));
        return s;
    }

    static String TrimEnd(const std::string& data)
    {
        String s = data;
        s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
            return !std::isspace(ch);
        }).base(),
            s.end());
        return s;
    }

    static String Trim(const std::string& data)
    {
        return TrimEnd(TrimStart(data));
    }

    static String Replace(const String& data, const String& toFind, const String& toReplace)
    {
        String result = data;
        size_t pos = 0;
        while ((pos = result.find(toFind, pos)) != String::npos)
        {
            result.replace(pos, toFind.length(), toReplace);
            pos += toReplace.length();
            pos = result.find(toFind, pos);
        }
        return result;
    }

};

static String Nameof(const String& name)
{
    std::smatch groups;
    String str = Strings::Trim(name);
    if (std::regex_match(str, groups, std::regex(u8R"(^&?([_a-zA-Z]w*(->|.|::))*([_a-zA-Z]w*)$)")))
    {
        if (groups.size() == 4)
        {
            return groups[3];
        }
    }
    throw std::invalid_argument(Strings::Replace(u8R"(nameof(#). Invalid identifier "#".)", u8"#", name));
}

#define nameof(name) Nameof(u8## #name ## s)
#define cnameof(name) Nameof(u8## #name ## s).c_str()

enum TokenType {
    COMMA,
    PERIOD,
    Q_MARK
};

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

int main() {
    String greetings = u8"Hello"s;
    std::cout << nameof(COMMA) << std::endl;
    std::cout << nameof(TokenType::PERIOD) << std::endl;
    std::cout << nameof(TokenType::Q_MARK) << std::endl;
    std::cout << nameof(int) << std::endl;
    std::cout << nameof(std::string) << std::endl;
    std::cout << nameof(Strings) << std::endl;
    std::cout << nameof(String) << std::endl;
    std::cout << nameof(greetings) << std::endl;
    std::cout << nameof(&greetings) << std::endl;
    std::cout << nameof(greetings.c_str) << std::endl;
    std::cout << nameof(std::string::npos) << std::endl;
    std::cout << nameof(MyClass::MyEnum::AAA) << std::endl;
    std::cout << nameof(MyClass::MyEnum::BBB) << std::endl;
    std::cout << nameof(MyClass::MyEnum::CCC) << std::endl;


    std::cin.get();
    return 0;
}

Ieșire

COMMA
PERIOD
Q_MARK
int
string
Strings
String
greetings
greetings
c_str
npos
AAA
BBB
CCC

Clang

Visual C++

dau_sama

Ce ziceți de o simplă supraîncărcare de streaming?Tot va trebui să mențineți maparea dacă nu doriți să faceți o magie de macro, dar mi se pare mai curată decât soluția dvs. originală.

#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 <<'
';
}

Comentarii

  • 1) creează și mai multe dublări 2) te obligă să folosești fluxuri –  > Por Karoly Horvath.
  • -1 Îmi pare rău @dau_sama, dar scopul tuturor acestor de la enum la string întrebări recurente este de a evita întreținerea corespondenței enum/șir. Dacă considerați că răspunsul dvs. nu se potrivește scopului, vă rugăm să luați în considerare ștergerea răspunsului. Mult noroc la următorul răspuns 😉 Noroc –  > Por oHo.