Enum türü bir değişkeni dizeye nasıl dönüştürebilirim?


128

Enum türündeki değişkenlerin değerlerini göstermek için printf nasıl yapılır? Örneğin:

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

ve ihtiyacım olan şey

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

tamsayı değil, "Linux" dizesi göstermelidir.

Sanırım, önce değer indeksli bir dizi dizisi oluşturmalıyım. Ama bunu yapmanın en güzel yolu mu bilmiyorum. Bütün bunlar mümkün mü?


2
Seçiminizi yapın: burada , burada , burada . Bu bir karmaşa, hepsi yarı kopyalar.
rubenvb

Bir diğeri , bu daha eksiksiz.
bit2shift


Pek çok cevap henüz "enum sınıfı" yok
Vivick

Yanıtlar:


70

Bunu yapmanın gerçekten güzel bir yolu yok. Sadece enum tarafından indekslenmiş bir dizi ayarlayın.

Çok fazla çıktı yaparsanız, bir enum parametresi alan ve sizin için arama yapan bir << operatörü tanımlayabilirsiniz.


2
Ayrıca, dizinizin içinde beklenen sayıda dizgeye sahip olup olmadığını derleme sırasında kontrol edebilirsiniz.
markh44

2
Bu konuda büyük bir azınlıkta olduğumu biliyorum, ancak benim gibi büyük üçüncü taraf kitaplıklarına ve / veya sözlü dile özgü eksiklikleri gidermek için markalı kodlara güvenmek istemeyen programcılar için bunu şöyle buluyorum günümüz standardı için açık ara en basit ve en saf çözüm. +1
Syndog

13
@Syndog Daha sonra, üretim kodunuzdaki 56 numaralandırıcı uzun numaralandırıcı, bu programcı tarafından gecikmiş bir özelliği serbest bırakması için büyük bir baskı altında güncellenir ve o numaralandırılmış diziyi güncellemeyi unutur. Fark edilmeden gider, çünkü ilgili baskı tesisi yalnızca uygulama hata ayıklama kodu tarafından kullanılır. 2 ay sonra, bu hata ayıklama kodunu gerçekten çalıştıran ilk kişi sizsiniz: daha sonra size yanlış bilgi verir, bu nedenle hata ayıklama kodunda hata ayıklamanız gerektiğini fark etmeden önce, bu yanlış bilgiye dayalı olarak yarım günlük yapı varsayımlarını kaybedersiniz: tasarım, açık çoğaltmaya dayanır.
Reklam N

1
@AdN Bu tasarım yanlış. Numaralandırmadan insan tarafından okunabilir dizeye eşleme, enum değeriyle dizinlenmiş dizeler dizisi olarak uygulanmamalıdır. Deneyiminiz (muhtemelen) nedenini gösteriyor. Eşleme, (enum, string) çiftlerden oluşan bir dizi olmalıdır, bu nedenle yeni numaralandırma değeriniz için bir giriş eklemeyi unutursanız, "???" çıktı olarak, ancak en azından diğer numaralandırmalarınızın adlarını bozmaz.
brewbuck

8
@AdN, senaryonuz, bir dizi yerine bir anahtar (varsayılan cümleci olmayan) içeren bir işlevi tercih etmemin ve derleme dosyasındaki derleyici anahtarlarını, tümünü kapsamayan bir enum üzerinden bir anahtar için bir hata verecek şekilde ayarlamamın nedenidir. olası değerler. İlgili switch ifadelerini güncellemeden yeni bir enum girişi eklemek bir derleme hatasına neden olur.
divegeek

131

Elbette saf çözüm, dizeye dönüştürmeyi gerçekleştiren her numaralandırma için bir işlev yazmaktır:

enum OS_type { Linux, Apple, Windows };

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

Ancak bu bir bakım felaketi. Hem C hem de C ++ kodu ile kullanılabilen Boost.Preprocessor kütüphanesi sayesinde önişlemciden rahatlıkla faydalanabilir ve sizin için bu fonksiyonu oluşturmasına izin verebilirsiniz. Üretim makrosu aşağıdaki gibidir:

#include <boost/preprocessor.hpp>

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

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

İlk makro (ile başlayan X_), ikinci makro tarafından dahili olarak kullanılır. İkinci makro ilk olarak numaralandırmayı üretir, ardından birToString bu türden bir nesneyi alan ve numaralandırıcı adını bir dizge olarak döndüren işlev (bu uygulama, açık nedenlerden ötürü, numaralandırıcıların benzersiz değerlerle eşleşmesini gerektirir).

C ++ 'da ToStringişlevi operator<<bunun yerine bir aşırı yük olarak uygulayabilirsiniz , ancak bence açıkça belirtilmesi biraz daha temiz "ToString değeri dizgi biçimine dönüştürmek için " .

Kullanım örneği olarak OS_typenumaralandırmanız aşağıdaki gibi tanımlanacaktır:

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

Makro ilk bakışta çok fazla işmiş gibi görünürken ve tanımı OS_typeoldukça yabancı görünse de, makroyu bir kez yazmanız gerektiğini unutmayın, sonra her numaralandırma için kullanabilirsiniz. Çok fazla sorun yaşamadan ona ek işlevler ekleyebilirsiniz (örneğin, enum dönüştürmeye bir dizge formu) ve makroyu çalıştırdığınızda adları yalnızca bir kez sağlamanız gerektiğinden bakım sorununu tamamen çözer.

Numaralandırma daha sonra normal olarak tanımlanmış gibi kullanılabilir:

#include <iostream>

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

Bu gönderideki kod parçacıkları, #include <boost/preprocessor.hpp>satırdan başlayarak , çözümü göstermek için postalanmış olarak derlenebilir.

Bu özel çözüm, C ++ 'ya özgü sözdizimi (örneğin, hayır typedef enum) ve işlev aşırı yüklemesini kullandığı için C ++ içindir , ancak bunun C ile de çalışmasını sağlamak kolay olacaktır.


7
+1, Uygulama makineleri korkutucu ama son arayüz zarafet için yenmek zor.
deft_code

4
Bunu, enum tamsayı değerlerini vermenize de izin verecek bir şekilde elde etmek var mı? Örneğin, Windows 3, Linux 5 ve Apple 7 olabilir mi?
Mark

4
Evet, değiştirebilir (Windows)içine (Windows, 3)daha sonra değiştirmek BOOST_PP_SEQ_ENUMuygun bir şekilde yazılı olan BOOST_PP_SEQ_FOR_EACH. Bunun kullanışlı bir örneğim yok, ama isterseniz bir tane yazabilirim.
James McNellis

2
@JamesMcNellis Kesinlikle Mark'ın istediğini gerçekleştiren bir kod örneği istiyorum, bize yolu gösterecek kadar kibar olur muydunuz? :)
Omer Raviv

2
NOT: boost önişlemcisinin 256 öğelik sabit bir sınırı vardır. Daha büyük numaralandırmalar için farklı bir çözüme ihtiyaç vardır.
dshin

32

Bu ön işlemci bloğudur

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

Enum tanımı

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

Kullanarak ara

GetStringOs_type(winblows);

Alındığı burada . Ne kadar serin ? :)


1
Numaranız 256'dan fazla öğe içerdiğinde çalışan tek çözüm budur.
dshin

8

std::map<OS_type, std::string>Bunu anahtar olarak enum ve değerler olarak dize gösterimi ile kullanın ve doldurun, ardından şunları yapabilirsiniz:

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

7

C enums ile ilgili sorun, C ++ 'da olduğu gibi kendine ait bir tür olmamasıdır. C'deki bir enum, tanımlayıcıları integral değerlerle eşlemenin bir yoludur. Sadece bu. Bu nedenle bir enum değeri tamsayı değerleriyle değiştirilebilir.

Doğru tahmin ettiğiniz gibi, enum değeri ile bir dizge arasında bir eşleme oluşturmak iyi bir yöntemdir. Örneğin:

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

Sanırım - görünüşe göre yanlış - programlama dili C ile sınırlı.
Andrew

1
Eğer biraz kapalı, enum olan C. İntegral numaralandırma türü sabitleri içinde türleri türdedir intve değil enumtanımlandıkları yoluyla türü, sen demek istediğini belki de. Ama bunun soruyla ne ilgisi olduğunu hiç anlamıyorum.
Jens Gustedt

7

Ben birleştirdik James' , Howard'ın ve Eder en çözümleri ve daha genel bir uygulama oluşturuldu:

  • int değeri ve özel dize temsili, her enum öğesi için isteğe bağlı olarak tanımlanabilir
  • "enum sınıfı" kullanıldı

Kodun tamamı aşağıda yazılmıştır (bir numaralandırmayı tanımlamak için "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" kullanın) ( çevrimiçi demo ).

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

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


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

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

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

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


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

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

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

    return 0;
}

Şimdiye kadarki en iyi cevap bu
Arnout

6

Bu basit örnek benim için çalıştı. Bu yardımcı olur umarım.

#include <iostream>
#include <string>

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

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

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

13
DIRECTION a = KUZEY varsa çalışmayacaktır; ve sonra ENUM_TO_STR (a) yazın
mathreadler

5

Bunu denedin mi:

#define stringify( name ) # name

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

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

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

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

stringify()Makro bir dizeye kodunuzda herhangi bir metni çevirmek için kullanılır, ancak yalnızca tam metin parantez arasında yapılabilir. Değişken referans alma veya makro ikameler veya yapılan başka herhangi bir şey yoktur.

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


Birincisi yeterli olsa da bu aslında en üstte olacak :)
pholat

İyi çalışıyor, ancak derleme hatasını önlemek için en üste #ifndef stringify eklemelisiniz. Ayrıca dgmz'nin önerdiği gibi enum türünü std :: string olarak değiştirdim.
astarakastara

5

Burada pek çok iyi cevap var, ama bazılarının benimkini faydalı bulabileceğini düşündüm. Bunu beğendim çünkü makroyu tanımlamak için kullandığınız arabirim alabildiği kadar basit. Ayrıca, herhangi bir ekstra kitaplık eklemeniz gerekmediği için kullanışlıdır - hepsi C ++ ile birlikte gelir ve gerçekten geç bir sürüm bile gerektirmez. Çevrimiçi olarak çeşitli yerlerden parçalar aldım, bu yüzden hepsinden övgü alamam, ancak yeni bir cevabı garanti edecek kadar eşsiz olduğunu düşünüyorum.

Önce bir başlık dosyası oluşturun ... buna EnumMacros.h veya buna benzer bir ad verin ve bunu içine koyun:

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

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

Daha sonra ana programınızda bunu yapabilirsiniz ...

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

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

Çıktının nerede olacağı >> 'Apple'ın değeri: 2/4

Zevk almak!


Bu özel yaklaşımla ilgili sevdiğim en önemli şey, numaralandırmanın virgülle ayrılmış normal sözdizimi ile çalışmasıdır (enum içinde herhangi bir değer ayarı ataması içermediği sürece). Benim durumumda, çok sayıda üyeye sahip mevcut bir numaralandırma ile çalışmak zorunda kaldım, bu yüzden bu, boost yaklaşımından çok daha kolaydı.
CuriousKea

4

Numaranızın zaten tanımlı olduğunu varsayarsak, bir çift dizisi oluşturabilirsiniz:

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

Şimdi bir harita oluşturabilirsiniz:

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

Şimdi haritayı kullanabilirsiniz. Numaranız değiştirilirse, dizi çiftlerinden [] çift eklemeniz / kaldırmanız gerekir. C ++ 'da enum'dan bir string elde etmenin en şık yolu olduğunu düşünüyorum.


2
Burada Qt'ye gerek olmadığı şeklindeki adil yorumdan ayrı olarak, başka bir nokta da bimapBoost'un isimleri ayrıştırıp numaralandırmak istemesi durumunda kullanmak isteyebilir (örneğin bir XML dosyasından).
Dmitri Nesteruk

4
Meli değil genel bir C ++ Söz konusu Qt türlerini kullanıyor olması.
vektör

3

C99 P99_DECLARE_ENUMiçin P99'da şu şekilde beyan etmenizi sağlayan vardır enum:

P99_DECLARE_ENUM(color, red, green, blue);

ve ardından color_getname(A)renk adı ile bir dizi elde etmek için kullanın .


2

İşte benim C ++ kodum:

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

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

using namespace std;


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

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

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

    int totalMessages = sizeof(ErrorMessagesName)/4;

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

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)

2

Partiye biraz geç, ama işte benim C ++ 11 çözümüm:

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

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

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

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

2

Kendi tercihim, hem tekrarlayan yazımı hem de anlaşılması zor makroları en aza indirgemek ve makro tanımlarını genel derleyici alanına sokmaktan kaçınmaktır.

Yani, başlık dosyasında:

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

static Level readLevel(const char *);

ve cpp uygulaması:

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

İşimiz biter bitmez makronun #undef'ine dikkat edin.


2

Çözümüm, boost kullanmadan:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

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

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


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

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

#endif /* EN2STR_HXX_ */

Ve işte nasıl kullanılacağı

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

2

Ön işlemciyi kullanarak partiye bir başka geç:

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

(Hakkında daha kolay konuşmak için satır numaralarını ekledim.) 1-4. Satırlar, numaralamanın öğelerini tanımlamak için düzenlediğiniz şeylerdir. (Ben buna "makro listesi" adını verdim, çünkü nesnelerin bir listesini yapan bir makro. @Lundin bana bunların X-makroları adı verilen iyi bilinen bir teknik olduğunu söylüyor.)

7. Satır, 8-11. Satırlardaki gerçek enum bildirimini dolduracak şekilde iç makroyu tanımlar. Satır 12, iç makroyu tanımlamaz (sadece derleyici uyarısını susturmak için).

Satır 14, enum eleman adının bir dize versiyonunu yaratmak için iç makroyu tanımlar. Sonra 15-18 satırları, bir enum değerini karşılık gelen dizeye dönüştürebilen bir dizi oluşturur.

21-27. Satırlar, bir dizeyi enum değerine dönüştüren veya dizge hiçbiriyle eşleşmezse NULL döndüren bir işlev oluşturur.

Bu, 0. elementi ele alma biçiminde biraz külfetli. Aslında geçmişte bunun etrafında çalıştım.

Bu tekniğin, ön işlemcinin kendisinin sizin için kod yazmaya programlanabileceğini düşünmek istemeyen insanları rahatsız ettiğini kabul ediyorum. Okunabilirlik ve sürdürülebilirlik arasındaki farkı güçlü bir şekilde gösterdiğini düşünüyorum . Kodun okunması zordur, ancak numaralandırmada birkaç yüz öğe varsa, öğeleri ekleyebilir, kaldırabilir veya yeniden düzenleyebilir ve yine de üretilen kodda hata olmadığından emin olabilirsiniz.


"X makroları" nadiren herhangi bir soruna zarif bir çözümdür. Bu durumda, makro öğelerini #define TEST_1 hello #define TEST_2 worldo typedef enum { TEST_1, TEST_2 } test_t;zamanki gibi basitçe tanımlamak ve ardından bir dizgeleştirme makrosu kullanan bir dizgi arama tablosu oluşturmak çok daha okunaklı olacaktır : const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; Benzer çözümlere ipucu veren çok sayıda yanıt zaten var. Çok daha okunaklı.
Lundin

@Lundin: Sadece 1) bunun en ilkel C derleyicisiyle bile çalıştığını ve 2) bir öğeyi eklemek veya silmek 1 satırlık bir düzenleme olduğunu iddia ediyorum.
Mike Dunlavey

Kendi yanıtımı gönderdim: stackoverflow.com/a/39877228/584518 . Umarım bazı zavallıları x makro çözümlerinden kurtarır.
Lundin

1
Çözümünüzü kullandım. Bence en iyisi. C-sözdizimi hala oradadır, böylece ne olduğunu anlarsınız ve liste yalnızca bir kez tanımlanır. DEFINE_ENUM_ELEMENT içindeki girişten sonra virgül koyarak 0. öğeyi kaldırabilirsiniz.
isgoed

1

İşte sadece C ön işlemcisini kullanan Old Skool yöntemi (gcc'de yaygın olarak kullanılıyordu). Ayrık veri yapıları oluşturuyorsanız, ancak sırayı aralarında tutarlı tutmanız gerekiyorsa kullanışlıdır. Mylist.tbl'deki girişler elbette çok daha karmaşık bir şeye genişletilebilir.

test.cpp:

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

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

Ve sonra mylist.tbl:

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

1
Bu tekniğe x makro denir!
Watusimoto

0

C ++ 'da şu şekilde:

enum OS_type{Linux, Apple, Windows};

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

0
#include <EnumString.h>

dan http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C ve sonrası

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

insert

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

Numaralandırmadaki değerler yinelenmemişse iyi çalışır.

Bir enum değerini dizeye dönüştürmek için örnek kod:

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

Tam tersi için örnek kod:

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

0

Öneriniz için teşekkürler James. Çok faydalıydı, bu yüzden bir şekilde katkıda bulunmak için başka bir yol uyguladım.

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

using namespace std;

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

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

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


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

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

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

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

    return 0;
}

0

James'in cevabını genişletmek için, birisi int değeriyle enum tanımını desteklemek için bazı örnek kodlar istiyor, benim de bu gereksinime sahibim, işte benim yolum:

İlk olarak, FOR_EACH tarafından kullanılan dahili kullanım makrosu:

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

Ve işte tanımlama makrosu:

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

Bu yüzden onu kullanırken şöyle yazmak isteyebilirsiniz:

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

hangisine genişleyecek:

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

Temel fikir, her elemanın bir TUPLE olduğu bir SEQ tanımlamaktır, böylece enum üyesi için ek değer koyabiliriz. FOR_EACH döngüsünde, TUPLE boyutunu kontrol edin, eğer boyut 2 ise, kodu KEY = VALUE olarak genişletin, aksi takdirde TUPLE'ın ilk elemanını koruyun.

SEQ girişi gerçekte TUPLE olduğundan, bu nedenle STRINGIZE işlevlerini tanımlamak istiyorsanız, önce girdi numaralandırıcıları önceden işlemeniz gerekebilir, işte işi yapacak makro:

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

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

Makro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ , her TUPLE'da yalnızca ilk öğeyi tutacak ve daha sonra SEQ'e dönüşecek, şimdi James'in kodunu değiştirecek, tam güce sahip olacaksınız.

Benim uygulamam belki en basit olanı değil, bu yüzden temiz bir kod bulamazsanız, referansınız için benimki.


0

Saf standart C'de temiz, güvenli çözüm:

#include <stdio.h>

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

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

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

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

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

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

  return 0;
}

Çıktı

0 hello
1 world
1 world

gerekçe

"Karşılık gelen dizelerle enum sabitlerine sahip olun" temel problemi çözerken, mantıklı bir programcı aşağıdaki gereksinimleri karşılayacaktır:

  • Kod tekrarından kaçının ("KURU" prensibi).
  • Numaralandırmaya öğeler eklense veya çıkarılsa bile kod ölçeklenebilir, bakımı yapılabilir ve güvenli olmalıdır.
  • Tüm kodlar yüksek kalitede olmalıdır: okunması kolay, bakımı kolay.

İlk gereksinim ve belki de ikincisi, kötü şöhretli "x makro" numarası veya diğer makro sihir biçimleri gibi çeşitli karmaşık makro çözümlerle yerine getirilebilir. Bu tür çözümlerle ilgili sorun, sizi tamamen okunamaz bir gizemli makro karmaşasıyla baş başa bırakmalarıdır - yukarıdaki üçüncü gereksinimi karşılamıyorlar.

Burada gerekli olan tek şey, dizin olarak enum değişkenini kullanarak erişebileceğimiz bir dizge arama tablosuna sahip olmaktır. Böyle bir tablo doğal olarak doğrudan enum'a karşılık gelmelidir ve bunun tersi de geçerlidir. Biri güncellendiğinde, diğerinin de güncellenmesi gerekir, aksi takdirde çalışmaz.


Kodun açıklaması

Şöyle bir numaramız olduğunu varsayalım

typedef enum
{
  hello,
  world
} test_t;

Bu değiştirilebilir

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Bu makro sabitlerinin artık başka bir yerde, örneğin bir dize arama tablosu oluşturmak için kullanılabilmesi avantajıyla. Ön işlemci sabitini bir dizeye dönüştürmek, bir "stringify" makrosu ile yapılabilir:

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

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

Ve bu kadar. Kullanarakhello , 0 değerine sahip enum sabitini test_str[hello]elde ederiz. Kullanarak "merhaba" dizesini elde ederiz.

Numaralandırma ve arama tablosunun doğrudan karşılık gelmesini sağlamak için, aynı miktarda öğe içerdiklerinden emin olmalıyız. Birisi kodu korursa ve yalnızca numaralandırmayı değiştirirse, arama tablosunu değiştirmezse veya tam tersi olursa, bu yöntem işe yaramaz.

Çözüm, size kaç öğe içerdiğini söyleyecek bir numaralandırmaya sahip olmaktır. Bunun için yaygın olarak kullanılan bir C numarası vardır, sonuna sadece numaralandırmanın kaç öğeye sahip olduğunu söyleme amacını karşılayan bir öğe ekleyin:

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

Şimdi, derleme zamanında, numaralandırmadaki öğe sayısının, arama tablosundaki öğe sayısı kadar olduğunu, tercihen bir C11 statik iddiasıyla kontrol edebiliriz:

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

(Birisi dinozor derleyicileri kullanmakta ısrar ederse, C standardının eski sürümlerinde de statik iddialar oluşturmanın çirkin ama tamamen işlevsel yolları vardır. C ++ için olduğu gibi, statik iddiaları da destekler.)


Bir yan not olarak, C11'de stringify makrosunu değiştirerek daha yüksek tip güvenliği elde edebiliriz:

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

( intçünkü numaralandırma sabitleri aslında türdendir int, değil test_t)

Bu, kodların STRINGIFY(random_stuff)derlenmesini engelleyecektir .


Ne dediğini anlıyorum ama asıl mesele kaldı Tipik öngörülebilir değişiklikler minimum düzenleme gerektirmelidir (1 satır gibi). (Bence DRY'nin arkasındaki neden budur.) Yani burada, numaralandırmanın boyutu 500 gibiyse ve ortaya yeni bir öğe eklemek (veya kaldır / yeniden adlandır / takas etmek) istiyorsanız, kaç satır kod gerekir bir hata yapmadığınızdan emin olmak için ne kadar kontrol yapmanız gerekir? Listenin her bir öğesi için tek tip bir şey yapan başka kod parçaları da olabilir.
Mike Dunlavey

Bana bunlara X-makroları dendiğini söylediğin için teşekkürler . Bunu bilmiyordum. Görmediğim şey, insanların genel olarak onları aşağılaması.
Mike Dunlavey

@MikeDunlavey Numaralandırmanın boyutu ne olursa olsun, tam olarak 3 satırı değiştirmeniz gerekecek: bir ekleyin #define, numaralandırma bildiriminde ve başvuru tablosunda tanımlamaya bir referans ekleyin. Bu satırları eklerken hata yaparsanız, program derlenmeyecektir. Tanımlayıcılara eklediğim numaralar hiçbir şekilde zorunlu değildir, siz de yazabilir #define APPLES hellove #define ORANGES worldardından typedef enum { APPES, ORANGES, TEST_N } test_t;bu şekilde devam edebilirsiniz.
Lundin

@MikeDunlavey X makroları ile ilgili olarak, bunlara karşı argümanlar, işlev benzeri makrolara yönelik argümanlarla aynıdır. İşlev benzeri makrolara karşı çok sayıda geçerli eleştiri bulmak için uzağa bakmanıza gerek kalmayacak.
Lundin

0

Yaptığım şey, burada gördüklerimin ve bu sitede benzer sorularda gördüklerimin bir kombinasyonudur. Bunu Visual Studio 2013 yaptım. Diğer derleyicilerle test etmedim.

Öncelikle hileleri yapacak bir dizi makro tanımlıyorum.

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

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

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

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

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

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

Daha sonra, enum sınıfını ve dizeleri almak için işlevleri yaratacak tek bir makro tanımlayın.

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

Şimdi bir enum türü tanımlamak ve bunun için dizelere sahip olmak gerçekten kolay hale geliyor. Yapmanız gereken tek şey:

ENUM(MyEnumType, A, B, C);

Aşağıdaki satırlar test etmek için kullanılabilir.

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

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

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

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

    return 0;
}

Bu çıktı:

3
A
B
C
A
B
C
A
B
C

Çok temiz ve kullanımı kolay olduğuna inanıyorum. Bazı sınırlamalar vardır:

  • Enum üyelerine değer atayamazsınız.
  • Enum üyesinin değerleri dizin olarak kullanılır, ancak bu iyi olmalıdır, çünkü her şey tek bir makroda tanımlanmıştır.
  • Bir sınıf içinde bir numaralandırma türünü tanımlamak için kullanamazsınız.

Bu konuda çalışabilirsen. Bence, özellikle nasıl kullanılacağı, bu güzel ve yalın. Avantajları:

  • Kullanımı kolay.
  • Çalışma zamanında dize bölünmesi gerekmez.
  • Derleme sırasında ayrı dizeler mevcuttur.
  • Okuması kolay. İlk makro seti fazladan bir saniyeye ihtiyaç duyabilir, ancak gerçekten o kadar karmaşık değildir.

0

Bu soruna temiz bir çözüm şu şekilde olacaktır:

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

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

    /* ... */

    return "<UNKNOWN>";
}

Bu çözümle ilgili iyi olan şey, basit olması ve aynı zamanda işlevi oluşturmanın kopyala ve değiştir yoluyla kolayca yapılabilmesidir. Çok fazla dönüştürme yapacaksanız ve numaranızın çok fazla olası değeri varsa, bu çözümün CPU yoğun hale gelebileceğini unutmayın.


0

Biraz geciktim ama işte benim g ++ ve sadece standart kitaplıkları kullanan çözümüm. Ad alanı kirliliğini en aza indirmeye ve enum adlarını yeniden yazma ihtiyacını ortadan kaldırmaya çalıştım.

"My_enum.hpp" başlık dosyası:

#include <cstring>

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

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

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

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


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

Kullanım örneği:

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

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

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

Bu çıktı:

MERCURY
EARTH

Her şeyi yalnızca bir kez tanımlamanız gerekir, ad alanınız kirletilmemelidir ve tüm hesaplama yalnızca bir kez yapılır (geri kalanı yalnızca aramalardan ibarettir). Bununla birlikte, enum sınıflarının tür güvenliğini elde edemezsiniz (bunlar hala sadece kısa tamsayılardır), numaralandırmalara değer atayamazsınız, isim alanlarını tanımlayabileceğiniz bir yerde numaralandırmaları tanımlamanız gerekir (örn. Global olarak).

Bu konudaki performansın ne kadar iyi olduğundan veya iyi bir fikir olup olmadığından emin değilim (C ++ 'dan önce C'yi öğrendim, böylece beynim hala bu şekilde çalışıyor). Bunun neden kötü bir fikir olduğunu bilen biri varsa, bunu belirtmekten çekinmeyin.


0

Yıl 2017 ama soru hala yaşıyor

Yine başka bir yol:

#include <iostream>

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

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

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

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

Çıktılar:

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

0
#pragma once

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

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

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

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

Bu, ayrıntılı bir sınıf genişletilmiş enum sürümüdür ... sağlananlar dışında başka bir enum değeri eklemez.

Kullanım: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)


0

Bunun her iki yönde de çalışmasına ihtiyacım vardı VE numaralarımı sık sık kapsayıcı bir sınıfa yerleştirdim ve bu yüzden bu yanıtların başında James McNellis tarafından çözüme başladım, ancak bu çözümü yaptım. Ayrıca, yanıtı biraz karmaşıklaştıran sadece enum yerine enum sınıfını tercih ettiğime dikkat edin.

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

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

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

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

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

Bir sınıf içinde kullanmak için şunun gibi bir şey yapabilirsiniz:

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

Ve nasıl kullanılacağını gösteren bir CppUnit testi yazdım:

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

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

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

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

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

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

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

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

DEFINE_ENUMERATION veya DEFINE_ENUMERATION_INSIDE_CLASS olmak üzere hangi makroyu kullanacağınızı seçmelisiniz. ComponentStatus :: Status'u tanımlarken ikincisini kullandığımı göreceksiniz ama sadece Status'u tanımlarken ilkini kullandım. Aradaki fark basit. Bir sınıfın içinde, to / from yöntemlerinin önüne "statik" olarak ön ekliyorum ve bir sınıfta değilse "satır içi" kullanıyorum. Önemsiz farklılıklar, ancak gerekli.

Maalesef, bunu yapmaktan kaçınmanın temiz bir yolu olduğunu sanmıyorum:

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

Sınıf tanımınızdan sonra sınıf yöntemine zincirleme yapan bir satır içi yöntemi el ile oluşturabilseniz de, şunun gibi bir şey:

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

0

Kendi cevabım, boost kullanmamak - kendi yaklaşımımı ağır tanımlı sihir olmadan kullanmak ve bu çözümün belirli bir enum değerini tanımlayamama sınırlaması vardır.

#pragma once
#include <string>

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

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

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

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

    From string to enumeration:

       enumName value;

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

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

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

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

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

    return std::string();
}

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

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

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

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

    return false;
}

En son sürüm github'da burada bulunabilir:

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


0

Bunun başka birçok cevabı var ama bence daha iyi bir yol C ++ 17 özelliklerini kullanmak ve çevirilerin derleme zamanında yapılması için constexpr kullanmak. Bu tür güvenlidir ve makrolarla uğraşmamıza gerek yoktur. Aşağıya bakınız:

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

namespace Enum
{

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

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

}

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

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

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

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

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

}

Bu daha sonra kolayca kullanılabilir, böylece dize anahtarı hataları derleme sırasında tespit edilir:

#include "test_enum.hpp"

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

Kod, diğer bazı çözümlerden daha ayrıntılıdır, ancak derleme zamanında Enum'dan String'e ve String'den Enum'a dönüştürmeyi kolayca yapabilir ve tür hatalarını tespit edebiliriz. Gelecekteki C ++ 20 özelliklerinden bazıları ile bu muhtemelen biraz daha basitleştirilebilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.