Làm thế nào để chuyển đổi một biến loại enum thành một chuỗi?


128

Làm cách nào để tạo printf để hiển thị các giá trị của các biến thuộc loại enum? Ví dụ:

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

và những gì tôi cần là một cái gì đó như

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

phải hiển thị một chuỗi "Linux", không phải là số nguyên.

Tôi cho rằng, đầu tiên tôi phải tạo ra một chuỗi các chuỗi được lập chỉ mục giá trị. Nhưng tôi không biết đó có phải là cách hay nhất để làm việc đó không. Có thể ở tất cả?


2
Hãy lựa chọn của bạn: ở đây , ở đây , ở đây . Đó là một mớ hỗn độn, tất cả chúng đều là bán trùng lặp.
rubenvb

Một số khác , cái này là đầy đủ hơn.
bit2shift


Rất nhiều câu trả lời chưa có "lớp enum"
Vivick

Câu trả lời:


70

Thực sự không có cách nào đẹp để làm điều này. Chỉ cần thiết lập một chuỗi các chuỗi được lập chỉ mục bởi enum.

Nếu bạn thực hiện nhiều đầu ra, bạn có thể xác định toán tử << lấy tham số enum và thực hiện tra cứu cho bạn.


2
Ngoài ra, bạn có thể kiểm tra tại thời điểm biên dịch rằng mảng của bạn có số chuỗi dự kiến ​​trong đó.
markh44

2
Tôi biết tôi thuộc nhóm thiểu số với điều này, nhưng với những lập trình viên như tôi, những người không muốn dựa vào các thư viện lớn của bên thứ ba và / hoặc mã bị đánh cắp marco để giải quyết những thiếu sót có trong ngôn ngữ, tôi thấy điều này là giải pháp đơn giản và tinh khiết nhất cho tiêu chuẩn ngày nay cho đến nay. +1
Syndog

13
@Syndog Sau đó, liệt kê 56 liệt kê dài trong mã sản xuất của bạn được lập trình viên này cập nhật dưới nhiều áp lực để phát hành một tính năng quá hạn và anh ta quên cập nhật mảng chỉ mục enum đó. Nó không được chú ý, bởi vì các cơ sở in ấn liên quan chỉ được sử dụng bởi mã gỡ lỗi ứng dụng. 2 tháng sau, bạn là người đầu tiên thực sự thực thi mã gỡ lỗi đó: sau đó nó cung cấp cho bạn thông tin sai, do đó bạn mất nửa ngày xây dựng các giả định dựa trên thông tin sai này, trước khi nhận ra trước tiên bạn phải gỡ lỗi mã gỡ lỗi: thiết kế dựa trên sự trùng lặp rõ ràng.
Quảng cáo N

1
@AdN Thiết kế đó sai. Ánh xạ từ enum đến chuỗi có thể đọc được của con người không nên được triển khai dưới dạng một chuỗi các chuỗi được lập chỉ mục bởi giá trị enum. Kinh nghiệm của bạn (có lẽ) cho thấy tại sao. Ánh xạ phải là một mảng explitiy của các cặp (enum, chuỗi), vì vậy nếu bạn quên thêm một mục nhập cho giá trị enum mới của mình, bạn sẽ nhận được "???" là đầu ra, nhưng ít nhất nó sẽ không làm hỏng tên của tất cả các enum khác của bạn.
brewbuck

8
@AdN kịch bản của bạn là lý do tại sao tôi thích một hàm chứa một khóa chuyển (không có mệnh đề mặc định) hơn là một mảng và để đặt các trình chuyển đổi trình biên dịch trong tệp xây dựng gây ra lỗi cho một chuyển đổi trong một enum không bao gồm tất cả những giá trị khả thi. Thêm một mục enum mới mà không cập nhật các câu lệnh chuyển đổi có liên quan sẽ gây ra lỗi biên dịch.
divegeek

131

Tất nhiên, giải pháp ngây thơ là viết một hàm cho mỗi phép liệt kê thực hiện chuyển đổi thành chuỗi:

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

Điều này, tuy nhiên, là một thảm họa bảo trì. Với sự trợ giúp của thư viện Boost.Pre Processor, có thể được sử dụng với cả mã C và C ++, bạn có thể dễ dàng tận dụng lợi thế của bộ tiền xử lý và để nó tạo ra chức năng này cho bạn. Macro thế hệ như sau:

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

Macro đầu tiên (bắt đầu bằng X_) được sử dụng bên trong bởi thứ hai. Macro thứ hai trước tiên tạo ra phép liệt kê, sau đó tạo một ToStringhàm lấy một đối tượng thuộc loại đó và trả về tên của điều tra viên dưới dạng một chuỗi (vì lý do rõ ràng này, yêu cầu các điều tra viên ánh xạ tới các giá trị duy nhất).

Thay vào đó, trong C ++, bạn có thể thực hiện ToStringchức năng dưới dạng operator<<quá tải, nhưng tôi nghĩ rằng sẽ sạch hơn một chút để yêu cầu "" rõ ràng ToStringđể chuyển đổi giá trị thành dạng chuỗi.

Trong ví dụ sử dụng, OS_typebảng liệt kê của bạn sẽ được xác định như sau:

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

Mặc dù macro trông ban đầu giống như nó là rất nhiều công việc và định nghĩa về OS_typengoại hình khá xa lạ, hãy nhớ rằng bạn phải viết macro một lần, sau đó bạn có thể sử dụng nó cho mỗi phép liệt kê. Bạn có thể thêm chức năng bổ sung cho nó (ví dụ: dạng chuỗi để chuyển đổi enum) mà không gặp quá nhiều khó khăn và nó giải quyết hoàn toàn vấn đề bảo trì, vì bạn chỉ phải cung cấp tên một lần, khi bạn gọi macro.

Phép liệt kê sau đó có thể được sử dụng như thể nó được định nghĩa bình thường:

#include <iostream>

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

Các đoạn mã trong bài đăng này, bắt đầu bằng #include <boost/preprocessor.hpp>dòng, có thể được biên dịch như đã đăng để thể hiện giải pháp.

Giải pháp cụ thể này dành cho C ++ vì nó sử dụng cú pháp cụ thể của C ++ (ví dụ: không typedef enum) và quá tải chức năng, nhưng sẽ rất đơn giản để làm cho điều này hoạt động với C là tốt.


7
+1, Bộ máy thực hiện là đáng sợ nhưng giao diện cuối khó đánh bại cho sự thanh lịch.
deft_code

4
Có cách nào để có được điều này cũng cho phép bạn đưa ra các giá trị số nguyên enum. Ví dụ: Windows sẽ là 3, Linux 5 và Apple 7?
Đánh dấu

4
Có, bạn có thể thay đổi (Windows)vào (Windows, 3)sau đó thay thế BOOST_PP_SEQ_ENUMbằng một văn bản phù hợp BOOST_PP_SEQ_FOR_EACH. Tôi không có một ví dụ về sự tiện dụng đó, nhưng tôi có thể viết một cái nếu bạn muốn.
James McNellis

2
@JamesMcNellis Tôi chắc chắn muốn có một ví dụ về một mã hoàn thành những gì Mark yêu cầu, bạn có tử tế khi chỉ cho chúng tôi đường đi không? :)
Omer Raviv

2
LƯU Ý: bộ tiền xử lý boost có giới hạn cứng là 256 phần tử. Đối với enum lớn hơn, một giải pháp khác là cần thiết.
dshin

32

Đây là khối bộ xử lý trước

#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

Định nghĩa Enum

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

Gọi bằng

GetStringOs_type(winblows);

Lấy từ đây . Làm thế nào là mát mẻ? :)


1
Đây là giải pháp duy nhất hoạt động khi enum của bạn có hơn 256 phần tử.
dshin

8

Sử dụng std::map<OS_type, std::string>và điền nó với enum làm khóa và biểu diễn chuỗi dưới dạng các giá trị, sau đó bạn có thể thực hiện các thao tác sau:

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

7

Vấn đề với C enums là nó không phải là một loại của riêng nó, giống như trong C ++. Một enum trong C là một cách để ánh xạ các định danh thành các giá trị tích phân. Chỉ vậy thôi. Đó là lý do tại sao một giá trị enum có thể hoán đổi cho nhau với các giá trị nguyên.

Như bạn đoán chính xác, một cách tốt là tạo ánh xạ giữa giá trị enum và chuỗi. Ví dụ:

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

Tôi giả sử - dường như không chính xác - ngôn ngữ lập trình bị giới hạn ở C.
Andrew

1
bạn hơi lạc lõng, enum các loại trong C. Các hằng số kiểu liệt kê tích phân là loại intvà không phải là enumloại mà chúng được định nghĩa, có lẽ là những gì bạn muốn nói. Nhưng tôi không thấy điều này có liên quan gì đến câu hỏi.
Jens Gustyt

7

Tôi đã kết hợp các giải pháp của James , HowardÉder và tạo ra một triển khai chung chung hơn:

  • Giá trị int và biểu diễn chuỗi tùy chỉnh có thể được xác định tùy ý cho từng phần tử enum
  • "lớp enum" được sử dụng

Mã đầy đủ được viết dưới đây (sử dụng "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" để xác định enum) ( bản demo trực tuyến ).

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

Đây là câu trả lời tốt nhất cho đến nay
Arnout

6

Ví dụ đơn giản này đã làm việc cho tôi. Hi vọng điêu nay co ich.

#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
Sẽ không hoạt động nếu bạn có HƯỚNG a = BẮC; và sau đó viết ENUM_TO_STR (a)
mathreadler

5

Bạn đã thử điều này:

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

Các stringify()vĩ mô có thể được sử dụng để biến bất kỳ văn bản trong mã của bạn vào một chuỗi, nhưng chỉ có văn bản chính xác giữa các dấu ngoặc đơn. Không có hội nghị thay thế hoặc thay thế vĩ mô hoặc bất kỳ loại điều khác được thực hiện.

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


Cái này sẽ thực sự đứng đầu, mặc dù chỉ cần cái đầu tiên là đủ: :)
pholat

Hoạt động tốt, nhưng bạn nên thêm #ifndef stringify ở trên cùng để tránh lỗi biên dịch. Tôi cũng đã thay đổi loại enum như std :: string như dgmz đề xuất nó.
astarakastara

5

Có rất nhiều câu trả lời hay ở đây, nhưng tôi nghĩ một số người có thể thấy tôi hữu ích. Tôi thích nó bởi vì giao diện mà bạn sử dụng để xác định macro đơn giản như nó có thể nhận được. Nó cũng tiện dụng vì bạn không phải bao gồm bất kỳ thư viện bổ sung nào - tất cả đều đi kèm với C ++ và thậm chí nó không yêu cầu phiên bản thực sự muộn. Tôi đã rút các mảnh từ nhiều nơi khác nhau trên mạng để tôi không thể tin vào tất cả số đó, nhưng tôi nghĩ nó đủ độc đáo để đảm bảo một câu trả lời mới.

Đầu tiên tạo một tệp tiêu đề ... gọi nó là EnumMacros.h hoặc một cái gì đó tương tự, và đặt nó trong đó:

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

Sau đó, trong chương trình chính của bạn, bạn có thể làm điều này ...

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

Nơi đầu ra sẽ là >> Giá trị của 'Apple' là: 2 trên 4

Thưởng thức!


Điều quan trọng tôi thích về cách tiếp cận cụ thể này là nó hoạt động với cú pháp được phân tách bằng dấu phẩy thông thường của enum (miễn là nó không bao gồm bất kỳ phép gán cài đặt giá trị nào trong enum). Trong trường hợp của tôi, tôi đã phải làm việc với một enum hiện có với số lượng thành viên lớn, vì vậy điều này dễ dàng hơn nhiều so với phương pháp tăng cường.
CuriousKea

4

Giả sử rằng enum của bạn đã được xác định, bạn có thể tạo một mảng các cặp:

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

Bây giờ, bạn có thể tạo bản đồ:

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

Bây giờ, bạn có thể sử dụng bản đồ. Nếu enum của bạn bị thay đổi, bạn phải thêm / xóa cặp khỏi cặp mảng []. Tôi nghĩ rằng đó là cách thanh lịch nhất để có được một chuỗi từ enum trong C ++.


2
Ngoài nhận xét công bằng rằng không cần Qt ở đây, một điểm khác là người ta có thể muốn sử dụng Boost bimaptrong trường hợp người ta muốn phân tích tên và biến chúng thành enum (ví dụ: từ tệp XML).
Dmitri Nesteruk

4
Nên không được sử dụng các loại Qt thông qua câu hỏi chung chung C ++.
Vector

3

Đối với C99, có P99_DECLARE_ENUMtrong P99 cho phép bạn chỉ cần khai báo enumnhư sau:

P99_DECLARE_ENUM(color, red, green, blue);

và sau đó sử dụng color_getname(A)để có được một chuỗi với tên màu.


2

Đây là mã C ++ của tôi:

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

Đến bữa tiệc muộn một chút, nhưng đây là giải pháp C ++ 11 của tôi:

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

Sở thích riêng của tôi là giảm thiểu cả việc gõ lặp đi lặp lại và các macro khó hiểu và tránh đưa các định nghĩa macro vào không gian trình biên dịch chung.

Vì vậy, trong tệp tiêu đề:

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

static Level readLevel(const char *);

và việc thực hiện cpp là:

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

Lưu ý #undef của macro ngay khi chúng tôi hoàn thành nó.


2

Giải pháp của tôi, không sử dụng 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_ */

Và đây là cách sử dụng nó

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

2

Một bữa tiệc muộn khác, sử dụng bộ tiền xử lý:

 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

(Tôi chỉ nhập số dòng để dễ nói hơn.) Dòng 1-4 là những gì bạn chỉnh sửa để xác định các thành phần của enum. (Tôi đã gọi nó là "macro danh sách", vì đó là macro tạo danh sách các thứ. @Lundin thông báo cho tôi đây là một kỹ thuật nổi tiếng được gọi là X-macro.)

Dòng 7 định nghĩa macro bên trong để điền vào khai báo enum thực tế trong các dòng 8-11. Dòng 12 xác định macro bên trong (chỉ để tắt cảnh báo trình biên dịch).

Dòng 14 định nghĩa macro bên trong để tạo phiên bản chuỗi của tên phần tử enum. Sau đó, các dòng 15-18 tạo ra một mảng có thể chuyển đổi một giá trị enum thành chuỗi tương ứng.

Các dòng 21-27 tạo ra một hàm chuyển đổi một chuỗi thành giá trị enum hoặc trả về NULL nếu chuỗi không khớp với bất kỳ.

Đây là một chút rườm rà trong cách nó xử lý phần tử 0. Tôi đã thực sự làm việc xung quanh đó trong quá khứ.

Tôi thừa nhận kỹ thuật này làm phiền cả những người không muốn nghĩ rằng chính bộ tiền xử lý có thể được lập trình để viết mã cho bạn. Tôi nghĩ rằng nó minh họa mạnh mẽ sự khác biệt giữa khả năng đọckhả năng bảo trì . Mã này rất khó đọc, nhưng nếu enum có vài trăm phần tử, bạn có thể thêm, xóa hoặc sắp xếp lại các phần tử và vẫn đảm bảo mã được tạo không có lỗi.


"X macro" hiếm khi là một giải pháp tao nhã cho bất kỳ vấn đề nào. Trong trường hợp này, sẽ dễ đọc hơn nhiều khi chỉ cần xác định các mục macro #define TEST_1 hello #define TEST_2 worldsau đó typedef enum { TEST_1, TEST_2 } test_t;và sau đó tạo bảng tra cứu chuỗi sử dụng macro chuỗi hóa: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; Đã có nhiều câu trả lời gợi ý cho các giải pháp tương tự. Dễ đọc hơn
Lundin

@Lundin: Tôi chỉ yêu cầu 1) điều này hoạt động với cả trình biên dịch C nguyên thủy nhất và 2) thêm hoặc xóa một phần tử là chỉnh sửa 1 dòng.
Mike Dunlavey

Tôi đã đăng câu trả lời của riêng mình: stackoverflow.com/a/39877228/584518 . Hy vọng rằng nó sẽ cứu một số linh hồn nghèo từ các giải pháp x macro.
Lundin

1
Tôi đã sử dụng giải pháp của bạn. Tôi nghĩ rằng nó là tốt nhất. Cú pháp C vẫn còn đó để bạn hiểu điều gì xảy ra và danh sách chỉ được xác định một lần. Bạn có thể xóa phần tử thứ 0 bằng cách đặt dấu phẩy sau mục nhập trong DEFINE_ENUM_ELEMENT của bạn.
diễn ra vào

1

Đây là phương pháp Old Skool (được sử dụng rộng rãi trong gcc) chỉ sử dụng bộ xử lý trước C. Hữu ích nếu bạn đang tạo các cấu trúc dữ liệu riêng biệt nhưng cần giữ trật tự nhất quán giữa chúng. Các mục trong mylist.tbl tất nhiên có thể được mở rộng thành một cái gì đó phức tạp hơn nhiều.

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

Và sau đó 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
Kỹ thuật này được gọi là x macro!
Watusimoto

0

Trong c ++ như thế này:

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>

từ http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C và sau đó

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

chèn

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

Hoạt động tốt nếu các giá trị trong enum không trùng lặp.

Mã mẫu để chuyển đổi một giá trị enum thành chuỗi:

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

Mã mẫu cho ngược lại:

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

0

Cảm ơn James cho đề nghị của bạn. Nó rất hữu ích vì vậy tôi đã thực hiện theo cách khác để đóng góp theo một cách nào đó.

#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

Để mở rộng câu trả lời của James, ai đó muốn một số mã ví dụ hỗ trợ enum xác định với giá trị int, tôi cũng có yêu cầu này, vì vậy đây là cách của tôi:

Đầu tiên, macro sử dụng nội bộ, được FOR_EACH sử dụng:

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

Và, đây là macro xác định:

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

Vì vậy, khi sử dụng nó, bạn có thể muốn viết như thế này:

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

sẽ mở rộng thành:

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

Ý tưởng cơ bản là xác định SEQ, mà mọi phần tử là TUPLE, vì vậy chúng ta có thể đặt giá trị bổ sung cho thành viên enum. Trong vòng lặp FOR_EACH, hãy kiểm tra kích thước TUPLE của mục, nếu kích thước là 2, hãy mở rộng mã thành KEY = VALUE, nếu không thì chỉ giữ phần tử đầu tiên của TUPLE.

Bởi vì SEQ đầu vào thực sự là TUPLE, vì vậy nếu bạn muốn xác định các hàm CHUINGI, bạn có thể cần xử lý trước các điều tra viên đầu vào, đây là macro để thực hiện công việc:

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

Macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQsẽ chỉ giữ phần tử đầu tiên trong mỗi TUPLE và sau đó chuyển đổi thành SEQ, bây giờ sửa đổi mã của James, bạn sẽ có toàn bộ sức mạnh.

Việc triển khai của tôi có thể không phải là đơn giản nhất, vì vậy nếu bạn không tìm thấy bất kỳ mã sạch nào, tôi sẽ tham khảo.


0

Dung dịch sạch, an toàn theo tiêu chuẩn C nguyên chất:

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

Đầu ra

0 hello
1 world
1 world

Cơ sở lý luận

Khi giải quyết vấn đề cốt lõi "có hằng số enum với các chuỗi tương ứng", một lập trình viên hợp lý sẽ đưa ra các yêu cầu sau:

  • Tránh lặp lại mã (nguyên tắc "DRY").
  • Mã phải có khả năng mở rộng, duy trì và an toàn ngay cả khi các mục được thêm hoặc xóa bên trong enum.
  • Tất cả các mã phải có chất lượng cao: dễ đọc, dễ bảo trì.

Yêu cầu đầu tiên, và cũng có thể là yêu cầu thứ hai, có thể được đáp ứng bằng các giải pháp macro lộn xộn khác nhau như thủ thuật "x macro" khét tiếng hoặc các hình thức ma thuật vĩ mô khác. Vấn đề với các giải pháp như vậy là chúng để lại cho bạn một mớ hỗn độn các macro bí ẩn hoàn toàn không thể đọc được - chúng không đáp ứng yêu cầu thứ ba ở trên.

Điều duy nhất cần thiết ở đây là thực sự có một bảng tra cứu chuỗi, mà chúng ta có thể truy cập bằng cách sử dụng biến enum làm chỉ mục. Một bảng như vậy phải tự nhiên tương ứng trực tiếp với enum và ngược lại. Khi một trong số chúng được cập nhật, cái khác cũng phải được cập nhật, hoặc nó sẽ không hoạt động.


Giải thích về mã

Giả sử chúng ta có một enum như

typedef enum
{
  hello,
  world
} test_t;

Điều này có thể được thay đổi thành

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Với lợi thế là các hằng số macro này hiện có thể được sử dụng ở nơi khác, ví dụ như để tạo bảng tra cứu chuỗi. Chuyển đổi hằng số tiền xử lý thành chuỗi có thể được thực hiện với macro "chuỗi hóa":

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

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

Và đó là nó. Bằng cách sử dụng hello, chúng tôi nhận được hằng số enum với giá trị 0. Bằng cách sử dụng, test_str[hello]chúng tôi nhận được chuỗi "xin chào".

Để làm cho bảng liệt kê và bảng tra cứu tương ứng trực tiếp, chúng ta phải đảm bảo rằng chúng chứa cùng một lượng vật phẩm. Nếu ai đó sẽ duy trì mã và chỉ thay đổi enum, và không phải bảng tra cứu, hoặc ngược lại, phương pháp này sẽ không hoạt động.

Giải pháp là có enum để cho bạn biết nó chứa bao nhiêu vật phẩm. Có một thủ thuật C thường được sử dụng cho việc này, chỉ cần thêm một mục ở cuối, chỉ hoàn thành mục đích cho biết có bao nhiêu mục mà enum có:

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

Bây giờ chúng ta có thể kiểm tra tại thời điểm biên dịch rằng số lượng vật phẩm trong enum có nhiều như số lượng vật phẩm trong bảng tra cứu, tốt nhất là với xác nhận tĩnh C11:

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

(Có nhiều cách xấu nhưng đầy đủ chức năng để tạo các xác nhận tĩnh trong các phiên bản cũ hơn của tiêu chuẩn C, nếu ai đó khăng khăng sử dụng trình biên dịch khủng long. Đối với C ++, nó cũng hỗ trợ các xác nhận tĩnh.)


Là một lưu ý phụ, trong C11, chúng ta cũng có thể đạt được độ an toàn loại cao hơn bằng cách thay đổi macro chuỗi hóa:

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

( intbởi vì hằng số liệt kê thực sự thuộc loại int, không phải test_t)

Điều này sẽ ngăn chặn mã như STRINGIFY(random_stuff)biên dịch.


Tôi hiểu những gì bạn đang nói, nhưng vấn đề vẫn còn. Các thay đổi có thể thấy trước điển hình nên yêu cầu chỉnh sửa tối thiểu (như 1 dòng). (Tôi nghĩ đó là lý do đằng sau DRY.) Vì vậy, ở đây, nếu kích thước của enum là 500 và bạn muốn chèn một phần tử mới vào giữa (hoặc xóa / đổi tên / hoán đổi), bạn phải có bao nhiêu dòng mã thay đổi và bạn phải làm bao nhiêu kiểm tra để chắc chắn rằng bạn đã không phạm sai lầm? Cũng có thể có các đoạn mã khác làm một cái gì đó thống nhất cho từng thành phần của danh sách.
Mike Dunlavey

Cảm ơn đã cho tôi biết những thứ này được gọi là X-macro . Tôi không biết điều đó. Những gì tôi không thấy là mọi người chê bai họ nói chung.
Mike Dunlavey

@MikeDunlavey Bất kể kích thước của enum, bạn sẽ phải thay đổi chính xác 3 dòng: thêm a #define, thêm một tham chiếu đến định nghĩa đó trong khai báo enum và bảng tra cứu. Nếu bạn sẽ tăng tốc khi thêm các dòng đó, chương trình sẽ không biên dịch. Các số tôi đã thêm vào các định danh không có nghĩa là bắt buộc, bạn cũng có thể viết #define APPLES hellovà làm #define ORANGES worldtheo typedef enum { APPES, ORANGES, TEST_N } test_t;, v.v.
Lundin

@MikeDunlavey Về các macro X, các đối số chống lại chúng cũng giống như các đối số chống lại bất kỳ macro nào giống như hàm. Bạn sẽ không cần nhìn xa để tìm thấy nhiều lời chỉ trích rất hợp lệ đối với các macro giống như chức năng.
Lundin

0

Những gì tôi đã làm là sự kết hợp của những gì tôi đã thấy ở đây và trong các câu hỏi tương tự trên trang web này. Tôi đã thực hiện điều này là Visual Studio 2013. Tôi chưa thử nghiệm nó với các trình biên dịch khác.

Trước hết tôi định nghĩa một tập hợp các macro sẽ thực hiện các thủ thuật.

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

Tiếp theo xác định một macro sẽ tạo lớp enum và các hàm để lấy chuỗi.

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

Bây giờ xác định một loại enum và có chuỗi cho nó trở nên thực sự dễ dàng. Tất cả bạn cần làm là:

ENUM(MyEnumType, A, B, C);

Các dòng sau đây có thể được sử dụng để kiểm tra nó.

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

Điều này sẽ xuất ra:

3
A
B
C
A
B
C
A
B
C

Tôi tin rằng nó rất sạch sẽ và dễ sử dụng. Có một số hạn chế:

  • Bạn không thể gán giá trị cho các thành viên enum.
  • Các giá trị của thành viên enum được sử dụng làm chỉ mục, nhưng điều đó sẽ ổn, bởi vì mọi thứ được xác định trong một macro.
  • Bạn không thể sử dụng nó để định nghĩa một loại enum bên trong một lớp.

Nếu bạn có thể làm việc xung quanh này. Tôi nghĩ, đặc biệt là làm thế nào để sử dụng nó, điều này là tốt đẹp và nạc. Ưu điểm:

  • Dễ sử dụng.
  • Không có chuỗi tách khi chạy yêu cầu.
  • Các chuỗi riêng biệt có sẵn tại thời gian biên dịch.
  • Dễ đọc. Nhóm macro đầu tiên có thể cần thêm một giây, nhưng thực sự không phức tạp lắm.

0

Một giải pháp sạch cho vấn đề này sẽ là:

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

Điểm hay của giải pháp này là nó đơn giản và việc xây dựng chức năng có thể được thực hiện dễ dàng thông qua sao chép và thay thế. Lưu ý rằng nếu bạn sẽ thực hiện nhiều chuyển đổi và enum của bạn có quá nhiều giá trị có thể, giải pháp này có thể trở nên tốn nhiều CPU.


0

Tôi hơi muộn nhưng đây là giải pháp của tôi sử dụng g ++ và chỉ các thư viện chuẩn. Tôi đã cố gắng giảm thiểu ô nhiễm không gian tên và loại bỏ mọi nhu cầu nhập lại tên enum.

Tệp tiêu đề "my_enum.hpp" là:

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

Ví dụ sử dụng:

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

Điều này sẽ xuất ra:

MERCURY
EARTH

Bạn chỉ phải xác định mọi thứ một lần, không gian tên của bạn không bị ô nhiễm và tất cả các tính toán chỉ được thực hiện một lần (phần còn lại chỉ là tra cứu). Tuy nhiên, bạn không có được sự an toàn kiểu của các lớp enum (chúng vẫn chỉ là các số nguyên ngắn), bạn không thể gán giá trị cho enum, bạn phải xác định enum ở đâu đó bạn có thể xác định không gian tên (ví dụ trên toàn cầu).

Tôi không chắc hiệu suất của nó tốt như thế nào, hoặc nếu đó là một ý tưởng tốt (tôi đã học C trước C ++ để bộ não của tôi vẫn hoạt động theo cách đó). Nếu bất cứ ai biết tại sao đây là một ý tưởng tồi, hãy thoải mái chỉ ra nó.


0

Đó là năm 2017 nhưng câu hỏi vẫn còn sống

Một cách khác:

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

Đầu ra:

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

Đây là một phiên bản enum mở rộng được xây dựng công phu ... nó không thêm bất kỳ giá trị enum nào khác ngoài những giá trị được cung cấp.

Cách sử dụng: DECLARE_ENUM_SEQ (CameraMode, (3), Bay, FirstPerson, Phối cảnh chính xác)


0

Tôi cần điều này để hoạt động theo cả hai hướng VÀ tôi thường xuyên nhúng các enum của mình vào một lớp có chứa, và vì vậy tôi đã bắt đầu với giải pháp theo cách của James McNellis, ở đầu các câu trả lời này, nhưng tôi đã thực hiện giải pháp này. Cũng lưu ý rằng tôi thích lớp enum hơn là enum, điều này làm phức tạp câu trả lời.

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

Để sử dụng nó trong một lớp, bạn có thể làm một cái gì đó như thế này:

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

Và tôi đã viết một bài kiểm tra CppUnit, trong đó trình bày cách sử dụng nó:

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

Bạn phải chọn macro nào sẽ sử dụng, DEFINE_ENUMERATION hoặc DEFINE_ENUMERATION_INSIDE_CLASS. Bạn sẽ thấy tôi đã sử dụng cái sau khi xác định ElementStatus :: Status nhưng tôi đã sử dụng cái trước khi chỉ xác định Status. Sự khác biệt là đơn giản. Trong một lớp, tôi đặt tiền tố các phương thức đến / từ là "tĩnh" và nếu không có trong một lớp, tôi sử dụng "nội tuyến". Sự khác biệt tầm thường, nhưng cần thiết.

Thật không may, tôi không nghĩ có một cách sạch sẽ để tránh phải làm điều này:

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

mặc dù bạn có thể tự tạo một phương thức nội tuyến sau khi định nghĩa lớp của bạn chỉ đơn giản là chuỗi thành phương thức lớp, đại loại như:

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

0

Câu trả lời của riêng tôi, không sử dụng boost - sử dụng phương pháp của riêng tôi mà không có phép xác định nặng, và giải pháp này có một hạn chế là không thể xác định giá trị enum cụ thể.

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

Phiên bản mới nhất có thể được tìm thấy trên github tại đây:

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


0

Có nhiều câu trả lời khác cho vấn đề này nhưng tôi nghĩ một cách tốt hơn là sử dụng các tính năng của C ++ 17 và sử dụng constexpr để các bản dịch được thực hiện trong thời gian biên dịch. Đây là loại an toàn và chúng tôi không cần phải lộn xộn với macro. Xem bên dưới:

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

}

Điều này sau đó có thể dễ dàng được sử dụng để phát hiện lỗi khóa chuỗi tại thời điểm biên dịch:

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

Mã này dài dòng hơn một số giải pháp khác nhưng chúng ta có thể dễ dàng thực hiện chuyển đổi Enum thành Chuỗi và chuyển đổi Chuỗi thành Enum tại thời gian biên dịch và phát hiện lỗi loại. Với một số tính năng của C ++ 20 trong tương lai, điều này có thể được đơn giản hóa hơn một chút.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.