Bagaimana cara mengubah variabel tipe enum ke string?

128

Bagaimana membuat printf untuk menunjukkan nilai-nilai variabel yang merupakan tipe enum? Misalnya:

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

dan apa yang saya butuhkan adalah sesuatu seperti

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

yang harus menunjukkan string "Linux", bukan bilangan bulat.

Saya kira, pertama-tama saya harus membuat array string yang terindeks nilai. Tetapi saya tidak tahu apakah itu cara yang paling indah untuk melakukannya. Apakah mungkin?

psihodelia
sumber
2
Silakan pilih: di sini , di sini , di sini . Ini berantakan, mereka semua semi-duplikat.
rubenvb
Yang lain , yang ini lebih lengkap.
bit2shift
Kemungkinan duplikat stackoverflow.com/questions/207976/… untuk C ++ dan stackoverflow.com/questions/9907160/… untuk C
Ciro Santilli 郝海东 冠状 病 六四 事件 事件
Begitu banyak jawaban namun tidak ada "kelas enum"
Vivick

Jawaban:

70

Tidak ada cara yang indah untuk melakukan ini. Cukup atur array string yang diindeks oleh enum.

Jika Anda melakukan banyak output, Anda dapat menentukan operator << yang mengambil parameter enum dan melakukan pencarian untuk Anda.

Bo Persson
sumber
2
Anda juga dapat memeriksa pada waktu kompilasi bahwa array Anda memiliki jumlah string yang diharapkan di dalamnya.
markh44
2
Saya tahu saya dalam minoritas yang luas dengan ini, tetapi untuk programmer seperti saya yang tidak ingin bergantung pada perpustakaan pihak ketiga yang besar dan / atau kode teka-teki marco untuk menyelesaikan kekurangan asli bahasa tersebut, saya menemukan ini sebagai solusi paling sederhana dan paling murni untuk standar saat ini. +1
Syndog
13
@Syndog Kemudian enum enumerator yang panjang dalam kode produksi Anda diperbarui oleh programmer ini di bawah banyak tekanan untuk merilis fitur yang sudah lewat waktu, dan ia lupa untuk memperbarui array enum yang diindeks. Tidak diketahui, karena fasilitas pencetakan terkait hanya digunakan oleh kode debugging aplikasi. 2 bulan kemudian, Anda adalah orang pertama yang benar-benar mengeksekusi kode debug itu: itu kemudian memberi Anda informasi yang salah, sehingga Anda kehilangan setengah hari membangun asumsi berdasarkan informasi yang salah ini, sebelum menyadari Anda pertama kali harus men-debug kode debug: desain bergantung pada duplikasi eksplisit.
Iklan N
1
@ ADN Desain itu salah. Pemetaan dari enum ke string yang dapat dibaca manusia tidak harus diimplementasikan sebagai array string yang diindeks oleh nilai enum. Pengalaman Anda (mungkin) menunjukkan alasannya. Pemetaan harus berupa array eksplisit (enum, string) pasangan, jadi jika Anda lupa menambahkan entri untuk nilai enum baru Anda, Anda mendapatkan "???" sebagai output, tetapi setidaknya itu tidak akan mengacaukan nama semua enum lainnya.
brewbuck
8
@Tambahkan skenario Anda adalah mengapa saya lebih suka fungsi yang mengandung switch (tanpa klausa default) daripada array, dan untuk mengatur switch kompilator dalam file build untuk mengeluarkan kesalahan untuk switch di atas enum yang tidak mencakup semua nilai yang mungkin. Menambahkan entri enum baru tanpa memperbarui pernyataan switch yang relevan akan menyebabkan kesalahan kompilasi.
divegeek
131

Solusi naif, tentu saja, adalah menulis fungsi untuk setiap enumerasi yang melakukan konversi ke string:

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

Ini, bagaimanapun, adalah bencana pemeliharaan. Dengan bantuan perpustakaan Boost.Preprocessor, yang dapat digunakan dengan kode C dan C ++, Anda dapat dengan mudah mengambil keuntungan dari preprocessor dan membiarkannya menghasilkan fungsi ini untuk Anda. Makro generasi adalah sebagai berikut:

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

Makro pertama (dimulai dengan X_) digunakan secara internal oleh yang kedua. Makro kedua pertama menghasilkan enumerasi, lalu menghasilkan aToString fungsi yang mengambil objek jenis itu dan mengembalikan nama enumerator sebagai string (implementasi ini, untuk alasan yang jelas, mengharuskan enumerator memetakan ke nilai unik).

Dalam C ++ Anda dapat mengimplementasikan ToStringfungsi sebagai operator<<kelebihan beban, tapi saya pikir ini agak lebih bersih untuk membutuhkan eksplisit "ToString " untuk mengonversi nilai ke bentuk string.

Sebagai contoh penggunaan, OS_typeenumerasi Anda akan didefinisikan sebagai berikut:

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

Sementara makro terlihat pada awalnya seperti itu banyak pekerjaan, dan definisi OS_typeterlihat agak asing, ingat bahwa Anda harus menulis makro sekali, maka Anda dapat menggunakannya untuk setiap enumerasi. Anda dapat menambahkan fungsionalitas tambahan untuk itu (misalnya, bentuk string untuk enum konversi) tanpa terlalu banyak kesulitan, dan itu sepenuhnya memecahkan masalah pemeliharaan, karena Anda hanya perlu memberikan nama satu kali, ketika Anda menjalankan makro.

Pencacahan kemudian dapat digunakan seolah-olah didefinisikan secara normal:

#include <iostream>

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

Cuplikan kode dalam pos ini, dimulai dengan #include <boost/preprocessor.hpp>baris, dapat dikompilasi sebagai diposting untuk menunjukkan solusinya.

Solusi khusus ini adalah untuk C ++ karena menggunakan sintaksis spesifik C ++ (mis., Tidak typedef enum) dan fungsi overloading, tetapi akan mudah untuk membuat ini bekerja dengan C juga.

James McNellis
sumber
7
+1, Mesin implementasinya menakutkan tetapi antarmuka akhirnya sulit dikalahkan untuk keanggunan.
deft_code
4
Apakah ada pula untuk mendapatkan ini juga memungkinkan Anda untuk memberikan nilai integer enum. Misalnya, Windows akan menjadi 3, Linux 5, dan Apple 7?
Tandai
4
Ya, Anda bisa mengganti (Windows)menjadi (Windows, 3)lalu mengganti BOOST_PP_SEQ_ENUMdengan yang ditulis sesuai BOOST_PP_SEQ_FOR_EACH. Saya tidak punya contoh yang berguna, tetapi saya bisa menulis satu jika Anda mau.
James McNellis
2
@JamesMcNellis Saya pasti ingin contoh kode yang memenuhi apa yang diminta Mark, apakah Anda akan berbaik hati menunjukkan caranya? :)
Omer Raviv
2
CATATAN: boost preprocessor memiliki batas keras 256 elemen. Untuk enum yang lebih besar, solusi berbeda diperlukan.
dshin
32

Ini adalah blok pra prosesor

#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

Definisi Enum

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

Panggilan menggunakan

GetStringOs_type(winblows);

Diambil dari sini . Betapa kerennya itu ? :)

Reno
sumber
1
Ini adalah satu-satunya solusi yang berfungsi ketika enum Anda memiliki lebih dari 256 elemen.
dshin
8

Gunakan std::map<OS_type, std::string>dan isi dengan enum sebagai kunci, dan representasi string sebagai nilai, maka Anda dapat melakukan ini:

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

Masalah dengan C enum adalah bahwa itu bukan jenis itu sendiri, seperti di C ++. Enum dalam C adalah cara untuk memetakan pengidentifikasi ke nilai integral. Hanya itu saja. Itu sebabnya nilai enum dapat dipertukarkan dengan nilai integer.

Seperti yang Anda tebak dengan benar, cara yang baik adalah membuat pemetaan antara nilai enum dan string. Sebagai contoh:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};
Andrew
sumber
Saya berasumsi - tampaknya salah - bahasa pemrograman dibatasi untuk C.
Andrew
1
Anda sedikit kurang baik, enum adalah tipe C. Konstanta tipe enumerasi integral adalah tipe intdan bukan enumtipe yang mendefinisikannya, mungkin itulah yang ingin Anda katakan. Tetapi saya sama sekali tidak mengerti apa hubungannya dengan pertanyaan ini.
Jens Gustedt
7

Saya telah menggabungkan solusi James ' , Howard dan Éder dan menciptakan implementasi yang lebih umum:

  • nilai int dan representasi string khusus dapat ditentukan secara opsional untuk setiap elemen enum
  • "enum class" digunakan

Kode lengkapnya ditulis di bawah (gunakan "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" untuk mendefinisikan enum) ( demo online ).

#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;
}
Beruang kutub
sumber
Ini adalah jawaban terbaik sejauh ini
Arnout
6

Contoh sederhana ini berhasil bagi saya. Semoga ini membantu.

#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";
}
dgmz
sumber
13
Tidak akan berfungsi jika Anda memiliki ARAH a = UTARA; dan kemudian menulis ENUM_TO_STR (a)
mathreadler
5

Apakah Anda mencoba ini:

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

Itu stringify() makro dapat digunakan untuk mengubah teks dalam kode Anda ke dalam string, tetapi hanya teks yang tepat antara tanda kurung. Tidak ada variabel dereferencing atau substitusi makro atau hal-hal lain yang dilakukan.

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

M.Ali
sumber
Yang ini benar-benar akan menjadi yang teratas, meskipun yang pertama saja sudah cukup :)
pholat
Berfungsi dengan baik, tetapi Anda harus menambahkan #ifndef di bagian atas untuk menghindari kesalahan kompilasi. Saya juga mengubah tipe enum sebagai std :: string seperti yang disarankan dgmz.
astarakastara
5

Ada banyak jawaban bagus di sini, tetapi saya pikir beberapa orang mungkin menganggap jawaban saya bermanfaat. Saya menyukainya karena antarmuka yang Anda gunakan untuk mendefinisikan makro sesederhana yang bisa didapat. Ini juga berguna karena Anda tidak harus menyertakan pustaka tambahan - semuanya dilengkapi dengan C ++ dan bahkan tidak memerlukan versi yang sangat terlambat. Saya menarik potongan-potongan dari berbagai tempat secara online sehingga saya tidak dapat mengambil kredit untuk semua itu, tetapi saya pikir itu cukup unik untuk menjamin jawaban baru.

Pertama buat file header ... sebut saja EnumMacros.h atau semacamnya, dan letakkan ini di dalamnya:

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

Kemudian, dalam program utama Anda, Anda dapat melakukan ini ...

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

Di mana output akan >> Nilai 'Apple' adalah: 2 dari 4

Nikmati!

Ph0t0n
sumber
Hal utama yang saya sukai dari pendekatan khusus ini adalah bahwa ia bekerja dengan sintaks yang dipisahkan koma normal dari enum (selama itu tidak termasuk tugas pengaturan nilai dalam enum). Dalam kasus saya, saya harus bekerja dengan enum yang ada dengan sejumlah besar anggota, jadi ini jauh lebih mudah untuk dijatuhkan daripada pendekatan peningkatan.
CuriousKea
4

Dengan asumsi bahwa enum Anda sudah ditentukan, Anda dapat membuat array pasangan:

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

Sekarang, Anda dapat membuat peta:

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

Sekarang, Anda bisa menggunakan peta. Jika enum Anda diubah, Anda harus menambah / menghapus pasangan dari pasangan array []. Saya pikir itu adalah cara paling elegan untuk mendapatkan string dari enum di C ++.

Vladimir
sumber
2
Terlepas dari komentar wajar bahwa tidak perlu untuk Qt di sini, poin lain adalah orang mungkin ingin menggunakan Boost bimapjika seseorang ingin mengurai nama dan mengubahnya menjadi enum (misalnya, dari file XML).
Dmitri Nesteruk
4
Seharusnya tidak menggunakan tipe Qt dalam pertanyaan C ++ umum.
Vektor
3

Untuk C99 ada P99_DECLARE_ENUMdi P99 yang memungkinkan Anda mendeklarasikan enumseperti ini:

P99_DECLARE_ENUM(color, red, green, blue);

dan kemudian gunakan color_getname(A)untuk mendapatkan string dengan nama warna.

Jens Gustedt
sumber
2

Ini kode C ++ saya:

/* 
 * 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)
y2k1234
sumber
2

Sedikit terlambat ke pesta, tapi inilah solusi C ++ 11 saya:

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
    .................
}
OneOfOne
sumber
1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra
2

Preferensi saya sendiri adalah untuk meminimalkan pengetikan berulang dan sulit untuk memahami makro dan untuk menghindari memasukkan definisi makro ke dalam ruang kompilator umum.

Jadi, di file header:

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

static Level readLevel(const char *);

dan implementasi cpp adalah:

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

Perhatikan #undef dari makro segera setelah kita selesai dengan itu.

gerardw
sumber
2

Solusi saya, tidak menggunakan boost:

#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_ */

Dan inilah cara menggunakannya

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

Terlambat lain ke pesta, menggunakan preprosesor:

 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

(Saya hanya memasukkan nomor baris sehingga lebih mudah untuk dibicarakan.) Baris 1-4 adalah apa yang Anda edit untuk menentukan elemen enum. (Saya menyebutnya "daftar makro", karena itu makro yang membuat daftar hal-hal. @Lundin memberi tahu saya ini adalah teknik terkenal yang disebut X-makro.)

Baris 7 mendefinisikan makro bagian dalam untuk mengisi deklarasi enum aktual dalam baris 8-11. Baris 12 mendefinisikan makro bagian dalam (hanya untuk membungkam peringatan kompiler).

Baris 14 mendefinisikan makro bagian dalam untuk membuat versi string dari nama elemen enum. Kemudian baris 15-18 menghasilkan array yang dapat mengonversi nilai enum ke string yang sesuai.

Baris 21-27 menghasilkan fungsi yang mengubah string ke nilai enum, atau mengembalikan NULL jika string tidak cocok dengan apa pun.

Ini sedikit rumit dalam cara menangani elemen ke-0. Saya sebenarnya telah mengatasinya di masa lalu.

Saya akui teknik ini mengganggu orang-orang yang tidak ingin berpikir preprocessor itu sendiri dapat diprogram untuk menulis kode untuk Anda. Saya pikir itu sangat menggambarkan perbedaan antara keterbacaan dan pemeliharaan . Kode ini sulit dibaca, tetapi jika enum memiliki beberapa ratus elemen, Anda dapat menambah, menghapus, atau mengatur ulang elemen dan masih memastikan kode yang dihasilkan tidak memiliki kesalahan.

Mike Dunlavey
sumber
"X macro" jarang merupakan solusi elegan untuk masalah apa pun. Dalam hal ini, akan jauh lebih mudah dibaca untuk mendefinisikan item makro saat #define TEST_1 hello #define TEST_2 worlditu typedef enum { TEST_1, TEST_2 } test_t;dan kemudian membuat tabel look-up string yang menggunakan makro strify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; Sudah ada beberapa jawaban yang mengisyaratkan solusi yang sama. Jauh lebih mudah dibaca.
Lundin
@Lundin: Saya hanya mengklaim 1) ini berfungsi bahkan dengan kompiler C yang paling primitif, dan 2) menambah atau menghapus elemen adalah edit 1-baris.
Mike Dunlavey
Saya telah memposting jawaban saya sendiri: stackoverflow.com/a/39877228/584518 . Semoga ini akan menyelamatkan beberapa jiwa miskin dari solusi x macro.
Lundin
1
Saya menggunakan solusi Anda. Saya pikir itu yang terbaik. Sintaks C masih di sana sehingga Anda memahami apa yang terjadi dan daftar hanya didefinisikan sekali. Anda dapat menghapus elemen 0, dengan menempatkan koma setelah entri di DEFINE_ENUM_ELEMENT Anda.
pergi
1

Inilah metode Old Skool (dulu digunakan secara luas dalam gcc) hanya menggunakan C-prosesor. Berguna jika Anda membuat struktur data diskrit tetapi perlu menjaga urutan konsisten di antara mereka. Entri di mylist.tbl tentu saja dapat diperluas ke sesuatu yang jauh lebih kompleks.

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

Dan kemudian 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 )
TheDuke
sumber
1
Teknik ini disebut x makro!
Watusimoto
0

Dalam c ++ seperti ini:

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";
}
BЈовић
sumber
0
#include <EnumString.h>

dari http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C dan setelah

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

memasukkan

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

Berfungsi dengan baik jika nilai dalam enum tidak duplikat.

Kode sampel untuk mengonversi nilai enum ke string:

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

Kode contoh untuk sebaliknya:

assert( EnumString< FORM >::To( f, str ) );
Andrey Syrokomskiy
sumber
0

Terima kasih James atas saran Anda. Itu sangat berguna jadi saya menerapkan sebaliknya untuk berkontribusi dalam beberapa cara.

#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;
}
Éder
sumber
0

Untuk memperluas jawaban James, seseorang ingin beberapa kode contoh untuk mendukung enum define dengan nilai int, saya juga memiliki persyaratan ini, jadi inilah cara saya:

Pertama adalah makro penggunaan internal, yang digunakan oleh FOR_EACH:

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

Dan, inilah makro definisinya:

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

Jadi saat menggunakannya, Anda mungkin ingin menulis seperti ini:

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

yang akan diperluas ke:

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

Ide dasarnya adalah untuk mendefinisikan SEQ, yang setiap elemen adalah TUPLE, sehingga kita dapat memberikan nilai tambahan untuk anggota enum. Dalam loop FOR_EACH, periksa ukuran item TUPLE, jika ukurannya 2, perluas kode ke KEY = VALUE, kalau tidak simpan saja elemen pertama dari TUPLE.

Karena SEQ input sebenarnya adalah TUPLE, jadi jika Anda ingin mendefinisikan fungsi STRINGIZE, Anda mungkin perlu melakukan pra-proses input enumerator terlebih dahulu, inilah makro untuk melakukan pekerjaan:

#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 hanya akan mempertahankan elemen pertama di setiap TUPLE, dan kemudian dikonversi ke SEQ, sekarang modifikasi kode James, Anda akan memiliki kekuatan penuh.

Implementasi saya mungkin bukan yang paling sederhana, jadi jika Anda tidak menemukan kode bersih, milik saya untuk referensi Anda.

Howard Gong
sumber
0

Solusi bersih, aman dalam standar C murni:

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

Keluaran

0 hello
1 world
1 world

Alasan

Ketika memecahkan masalah inti "memiliki konstanta enum dengan string yang sesuai", seorang programmer yang masuk akal akan datang dengan persyaratan berikut:

  • Hindari pengulangan kode (prinsip "KERING").
  • Kode harus scalable, dapat dikelola, dan aman bahkan jika item ditambahkan atau dihapus di dalam enum.
  • Semua kode harus berkualitas tinggi: mudah dibaca, mudah dipelihara.

Persyaratan pertama, dan mungkin juga yang kedua, dapat dipenuhi dengan berbagai solusi makro yang berantakan seperti trik "x makro" yang terkenal, atau bentuk lain dari sihir makro. Masalah dengan solusi seperti itu adalah mereka meninggalkan Anda dengan kekacauan makro misterius yang sama sekali tidak terbaca - mereka tidak memenuhi persyaratan ketiga di atas.

Satu-satunya hal yang diperlukan di sini adalah memiliki tabel string look-up, yang dapat kita akses dengan menggunakan variabel enum sebagai indeks. Tabel seperti itu secara alami harus berhubungan langsung dengan enum dan sebaliknya. Ketika salah satu dari mereka diperbarui, yang lain harus diperbarui juga, atau itu tidak akan berfungsi.


Penjelasan kode

Misalkan kita memiliki enum like

typedef enum
{
  hello,
  world
} test_t;

Ini bisa diubah menjadi

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Dengan keuntungan bahwa konstanta makro ini sekarang dapat digunakan di tempat lain, misalnya menghasilkan tabel pencarian string. Mengubah konstanta pra-prosesor ke string dapat dilakukan dengan makro "stringify":

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

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

Dan itu saja. Dengan menggunakan hello, kita mendapatkan konstanta enum dengan nilai 0. Dengan menggunakan test_str[hello]kita mendapatkan string "halo".

Untuk membuat enum dan tabel pencarian berhubungan secara langsung, kita harus memastikan bahwa mereka mengandung jumlah item yang sama. Jika seseorang akan mempertahankan kode dan hanya mengubah enum, dan bukan tabel pencarian, atau sebaliknya, metode ini tidak akan berfungsi.

Solusinya adalah memiliki enum untuk memberi tahu Anda berapa banyak item yang dikandungnya. Ada trik C yang umum digunakan untuk ini, cukup tambahkan item di bagian akhir, yang hanya memenuhi tujuan untuk memberitahukan berapa banyak item yang dimiliki oleh enum:

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

Sekarang kita dapat memeriksa pada waktu kompilasi bahwa jumlah item dalam enum adalah sebanyak jumlah item dalam tabel pencarian, lebih disukai dengan pernyataan statis C11:

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

(Ada cara-cara yang jelek tapi berfungsi penuh untuk membuat pernyataan statis dalam versi lama dari standar C juga, jika seseorang bersikeras menggunakan kompiler dinosaurus. Sedangkan untuk C ++, itu mendukung penegasan statis juga.)


Sebagai catatan tambahan, di C11 kita juga dapat mencapai keamanan tipe yang lebih tinggi dengan mengubah makro stringify:

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

( intkarena konstanta enumerasi sebenarnya bertipe int, bukan test_t)

Ini akan mencegah kode seperti STRINGIFY(random_stuff)dari kompilasi.

Lundin
sumber
Saya mengerti apa yang Anda katakan, tetapi intinya tetap. Perubahan tipikal yang dapat diperkirakan seharusnya membutuhkan pengeditan minimal (seperti 1 baris). (Saya pikir itulah alasan di balik KERING.) Jadi di sini, jika ukuran enum seperti 500, dan Anda ingin memasukkan elemen baru di tengah (atau menghapus / mengganti nama / swap), berapa banyak baris kode yang harus Anda berubah, dan berapa banyak pengecekan yang harus Anda lakukan untuk memastikan Anda tidak melakukan kesalahan? Mungkin juga ada potongan kode lain yang melakukan sesuatu yang seragam untuk setiap elemen daftar.
Mike Dunlavey
Terima kasih telah memberi tahu saya ini disebut X-macro . Saya tidak tahu itu. Yang tidak saya lihat adalah orang-orang merendahkan mereka secara umum.
Mike Dunlavey
@MikeDunlavey Tidak peduli ukuran enum, Anda harus mengubah tepat 3 baris: tambahkan a #define, tambahkan referensi ke yang didefinisikan dalam deklarasi enum dan tabel pencarian. Jika Anda salah saat menambahkan baris-baris itu, program tidak akan dikompilasi. Angka-angka yang saya tambahkan ke pengidentifikasi sama sekali tidak wajib, Anda juga bisa menulis #define APPLES hellodan #define ORANGES worlddiikuti oleh typedef enum { APPES, ORANGES, TEST_N } test_t;dan seterusnya.
Lundin
@MikeDunlavey Mengenai makro X, argumen terhadap mereka adalah sama dengan argumen terhadap makro fungsi-seperti. Anda tidak perlu melihat jauh untuk menemukan banyak kritik yang sangat valid terhadap makro seperti fungsi.
Lundin
0

Apa yang saya buat adalah kombinasi dari apa yang saya lihat di sini dan pertanyaan serupa di situs ini. Saya membuat ini adalah Visual Studio 2013. Saya belum mengujinya dengan kompiler lain.

Pertama-tama saya mendefinisikan satu set makro yang akan melakukan trik.

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

Selanjutnya, tentukan makro tunggal yang akan membuat kelas enum dan fungsi untuk mendapatkan string.

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

Sekarang mendefinisikan jenis enum dan memiliki string untuk itu menjadi sangat mudah. Yang perlu Anda lakukan adalah:

ENUM(MyEnumType, A, B, C);

Baris berikut dapat digunakan untuk mengujinya.

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

Ini akan menampilkan:

3
A
B
C
A
B
C
A
B
C

Saya percaya ini sangat bersih dan mudah digunakan. Ada beberapa batasan:

  • Anda tidak dapat memberikan nilai kepada anggota enum.
  • Nilai-nilai anggota enum digunakan sebagai indeks, tetapi itu harus baik-baik saja, karena semuanya didefinisikan dalam satu makro.
  • Anda tidak dapat menggunakannya untuk mendefinisikan tipe enum di dalam kelas.

Jika Anda bisa mengatasi ini. Saya pikir, terutama cara menggunakannya, ini bagus dan ramping. Keuntungan:

  • Mudah digunakan.
  • Tidak diperlukan pemisahan string saat runtime.
  • String terpisah tersedia pada waktu kompilasi.
  • Mudah dibaca. Set makro pertama mungkin membutuhkan tambahan kedua, tetapi tidak terlalu rumit.
jokr
sumber
0

Solusi bersih untuk masalah ini adalah:

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

Hal yang baik tentang solusi ini adalah sederhana dan juga membangun fungsi dapat dilakukan dengan mudah melalui salin dan ganti. Perhatikan bahwa jika Anda akan melakukan banyak konversi dan enum Anda memiliki terlalu banyak nilai yang mungkin, solusi ini mungkin menjadi intensif CPU.

Ali Alidoust
sumber
0

Saya agak terlambat tapi inilah solusi saya menggunakan g ++ dan hanya perpustakaan standar. Saya sudah mencoba untuk meminimalkan polusi namespace dan menghapus kebutuhan untuk mengetik ulang nama enum.

File header "my_enum.hpp" adalah:

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

Contoh penggunaan:

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

Ini akan menampilkan:

MERCURY
EARTH

Anda hanya perlu mendefinisikan semuanya sekali saja, namespace Anda seharusnya tidak tercemar, dan semua perhitungan hanya dilakukan satu kali (sisanya hanya pencarian). Namun, Anda tidak mendapatkan jenis-keamanan kelas enum (mereka masih hanya bilangan bulat pendek), Anda tidak dapat menetapkan nilai ke enum, Anda harus mendefinisikan enum di suatu tempat Anda dapat menentukan ruang nama (misalnya global).

Saya tidak yakin seberapa bagus kinerjanya dalam hal ini, atau apakah itu ide yang bagus (saya belajar C sebelum C ++ sehingga otak saya tetap bekerja seperti itu). Jika ada yang tahu mengapa ini adalah ide yang buruk jangan ragu untuk menunjukkannya.

Alias ​​Fakename
sumber
0

Ini tahun 2017 tetapi pertanyaannya masih hidup

Namun cara lain:

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

Output:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage
eungenue
sumber
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");     \
    }

Ini adalah versi enum yang diperluas dengan kelas yang diuraikan ... itu tidak menambah nilai enum lain selain yang disediakan.

Penggunaan: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)

Michal Turlik
sumber
0

Saya membutuhkan ini untuk bekerja di kedua arah DAN saya sering memasukkan enum saya di dalam kelas yang berisi, dan jadi saya mulai dengan solusi dengan cara James McNellis, jauh di atas jawaban ini, tetapi saya membuat solusi ini. Perhatikan juga saya lebih suka kelas enum daripada hanya enum, yang agak mempersulit jawaban.

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

Untuk menggunakannya di dalam kelas, Anda bisa melakukan sesuatu seperti ini:

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

Dan saya menulis tes CppUnit, yang menunjukkan cara menggunakannya:

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

Anda harus memilih makro mana yang akan digunakan, DEFINE_ENUMERATION atau DEFINE_ENUMERATION_INSIDE_CLASS. Anda akan melihat saya menggunakan yang terakhir ketika mendefinisikan ComponentStatus :: Status tetapi saya menggunakan yang pertama ketika hanya mendefinisikan Status. Perbedaannya sederhana. Di dalam kelas, saya awali metode to / from sebagai "statis" dan jika tidak di dalam kelas, saya menggunakan "inline". Perbedaan sepele, tetapi perlu.

Sayangnya, saya tidak berpikir ada cara yang bersih untuk menghindari keharusan melakukan ini:

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

walaupun Anda bisa secara manual membuat metode inline setelah definisi kelas Anda yang hanya menghubungkan ke metode kelas, sesuatu seperti:

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

Jawaban saya sendiri, tidak menggunakan boost - menggunakan pendekatan saya sendiri tanpa sihir berat, dan solusi ini memiliki batasan tidak dapat mendefinisikan nilai enum tertentu.

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

Versi terbaru dapat ditemukan di github di sini:

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

TarmoPikaro
sumber
0

Ada banyak jawaban lain untuk ini tetapi saya pikir cara yang lebih baik adalah dengan menggunakan fitur C ++ 17 dan menggunakan constexpr sehingga terjemahan dilakukan pada waktu kompilasi. Ini jenis aman dan kita tidak perlu macam-macam dengan makro. Lihat di bawah:

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

}

Ini kemudian dapat dengan mudah digunakan sehingga kesalahan kunci string terdeteksi pada waktu kompilasi:

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

Kode lebih verbose daripada beberapa solusi lain, tetapi kita dapat dengan mudah melakukan konversi Enum ke String dan konversi String ke Enum pada waktu kompilasi dan mendeteksi kesalahan tipe. Dengan beberapa fitur C ++ 20 yang akan datang, ini mungkin dapat disederhanakan sedikit lagi.

Marius
sumber