จะแปลงตัวแปรประเภท enum เป็นสตริงได้อย่างไร?


128

จะทำให้ printf แสดงค่าของตัวแปรที่เป็น enum ได้อย่างไร? ตัวอย่างเช่น:

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

และสิ่งที่ฉันต้องการก็คือ

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

ซึ่งต้องแสดงสตริง "Linux" ไม่ใช่จำนวนเต็ม

ฉันคิดว่าก่อนอื่นฉันต้องสร้างอาร์เรย์ของสตริงที่มีดัชนีค่า แต่ฉันไม่รู้ว่านั่นเป็นวิธีที่สวยงามที่สุดที่จะทำหรือไม่ เป็นไปได้หรือไม่


2
เวลาเลือกของคุณ: ที่นี่ , ที่นี่ , ที่นี่ มันเป็นเรื่องยุ่งเหยิงพวกมันทั้งหมดกึ่งซ้ำกัน
rubenvb

อีกอันอันนี้สมบูรณ์กว่า
bit2shift

อาจซ้ำกันของstackoverflow.com/questions/207976/…สำหรับ C ++ และstackoverflow.com/questions/9907160/…สำหรับ C
Ciro Santilli 郝海东冠状病六四事件法轮功

คำตอบมากมายยังไม่มี "enum class"
Vivick

คำตอบ:


70

ไม่มีวิธีที่สวยงามในการทำเช่นนี้ เพียงตั้งค่าอาร์เรย์ของสตริงที่จัดทำดัชนีโดย enum

หากคุณทำเอาต์พุตจำนวนมากคุณสามารถกำหนดตัวดำเนินการ << ที่รับพารามิเตอร์ enum และทำการค้นหาให้คุณ


2
นอกจากนี้คุณสามารถตรวจสอบในเวลาคอมไพล์ว่าอาร์เรย์ของคุณมีจำนวนสตริงตามที่คาดไว้หรือไม่
markh44

2
ฉันรู้ว่าฉันเป็นคนส่วนน้อยที่มีสิ่งนี้ แต่สำหรับโปรแกรมเมอร์เช่นฉันที่ไม่ต้องการพึ่งพาไลบรารีของบุคคลที่สามขนาดใหญ่และ / หรือรหัสที่เป็นปริศนาเพื่อแก้ไขข้อบกพร่องที่เกิดจากภาษาฉันคิดว่านี่เป็น วิธีที่ง่ายและบริสุทธิ์ที่สุดสำหรับมาตรฐานในปัจจุบัน +1
Syndog

13
@Syndog จากนั้นตัวแจงนับจำนวน 56 ตัวในรหัสการผลิตของคุณจะได้รับการอัปเดตโดยโปรแกรมเมอร์คนนี้ภายใต้ความกดดันอย่างมากในการปล่อยคุณลักษณะที่ค้างชำระและเขาลืมที่จะอัปเดตอาร์เรย์ที่จัดทำดัชนี enum นั้น ไม่มีใครสังเกตเห็นเนื่องจากสิ่งอำนวยความสะดวกการพิมพ์ที่เกี่ยวข้องถูกใช้โดยรหัสดีบักของแอปพลิเคชันเท่านั้น 2 เดือนต่อมาคุณเป็นคนแรกที่เรียกใช้โค้ดดีบักนั้นจริง: จากนั้นให้ข้อมูลที่ไม่ถูกต้องดังนั้นคุณจึงเสียเวลาครึ่งวันในการสร้างสมมติฐานจากข้อมูลที่ไม่ถูกต้องนี้ก่อนที่จะรู้ว่าคุณต้องดีบักโค้ดดีบักก่อน: การออกแบบอาศัยการทำซ้ำอย่างชัดเจน
Ad N

1
@AdN การออกแบบนั้นผิด ไม่ควรใช้การแม็ปจาก enum เป็นสตริงที่มนุษย์อ่านได้เป็นอาร์เรย์ของสตริงที่จัดทำดัชนีโดยค่า enum ประสบการณ์ของคุณ (น่าจะ) แสดงให้เห็นว่าทำไม การจับคู่ควรเป็นอาร์เรย์ (enum, string) ที่ชัดเจนดังนั้นหากคุณลืมใส่รายการสำหรับค่า enum ใหม่ของคุณคุณจะได้รับ "???" เป็นผลลัพธ์ แต่อย่างน้อยก็จะไม่ทำให้ชื่อของ enums อื่น ๆ ทั้งหมดของคุณเสียหาย
brewbuck

8
@AdN สถานการณ์ของคุณคือสาเหตุที่ฉันชอบฟังก์ชันที่มีสวิตช์ (โดยไม่มีอนุประโยคเริ่มต้น) มากกว่าอาร์เรย์และเพื่อตั้งค่าสวิตช์คอมไพเลอร์ในไฟล์บิลด์เพื่อให้เกิดข้อผิดพลาดสำหรับสวิตช์เหนือ enum ที่ไม่ครอบคลุมทั้งหมด ค่าที่เป็นไปได้ การเพิ่มรายการ enum ใหม่โดยไม่ต้องอัปเดตคำสั่งสวิตช์ที่เกี่ยวข้องจะทำให้เกิดข้อผิดพลาดในการคอมไพล์
divegeek

131

แน่นอนวิธีแก้ปัญหาที่ไร้เดียงสาคือการเขียนฟังก์ชันสำหรับการแจงนับแต่ละรายการที่ดำเนินการแปลงเป็นสตริง:

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

อย่างไรก็ตามนี่เป็นความหายนะในการบำรุงรักษา ด้วยความช่วยเหลือของไลบรารี Boost.Preprocessor ซึ่งสามารถใช้ได้กับทั้งรหัส C และ C ++ คุณสามารถใช้ประโยชน์จากตัวประมวลผลล่วงหน้าได้อย่างง่ายดายและปล่อยให้มันสร้างฟังก์ชันนี้ให้คุณ มาโครการสร้างมีดังนี้:

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

มาโครตัวแรก (เริ่มต้นด้วยX_) ถูกใช้ภายในโดยอันที่สอง มาโครที่สองจะสร้างการแจงนับก่อนจากนั้นจึงสร้างไฟล์ToStringฟังก์ชันที่รับวัตถุประเภทนั้นและส่งคืนชื่อตัวนับเป็นสตริง (การใช้งานนี้ด้วยเหตุผลที่ชัดเจนต้องการให้ตัวแจงจับคู่กับค่าที่ไม่ซ้ำกัน)

ใน C ++ คุณสามารถใช้ToStringฟังก์ชันนี้เป็นoperator<<โอเวอร์โหลดแทนได้ แต่ฉันคิดว่ามันค่อนข้างดีกว่าที่จะต้องใช้ ExplicitToStringเพื่อแปลงค่าเป็นรูปแบบสตริง

ตามตัวอย่างการใช้งานการOS_typeแจงนับของคุณจะถูกกำหนดดังนี้:

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

แม้ว่ามาโครจะดูในตอนแรกเหมือนกับว่าเป็นงานจำนวนมากและคำจำกัดความของOS_typeรูปลักษณ์ค่อนข้างแปลกใหม่โปรดจำไว้ว่าคุณต้องเขียนมาโครหนึ่งครั้งจากนั้นจึงสามารถใช้สำหรับการแจงนับทุกครั้ง คุณสามารถเพิ่มฟังก์ชันการทำงานเพิ่มเติมได้ (เช่นรูปแบบสตริงเพื่อการแปลง enum) โดยไม่มีปัญหามากเกินไปและช่วยแก้ปัญหาการบำรุงรักษาได้อย่างสมบูรณ์เนื่องจากคุณต้องระบุชื่อเพียงครั้งเดียวเมื่อคุณเรียกใช้มาโคร

จากนั้นสามารถใช้การแจงนับได้ราวกับว่ามีการกำหนดตามปกติ:

#include <iostream>

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

ข้อมูลโค้ดในโพสต์นี้เริ่มต้นด้วย#include <boost/preprocessor.hpp>บรรทัดสามารถรวบรวมได้ตามที่โพสต์เพื่อสาธิตการแก้ปัญหา

โซลูชันเฉพาะนี้มีไว้สำหรับ C ++ เนื่องจากใช้ C ++ - ไวยากรณ์เฉพาะ (เช่นไม่typedef enum) และฟังก์ชันโอเวอร์โหลด แต่จะเป็นการง่ายที่จะทำให้สิ่งนี้ทำงานร่วมกับ C ได้เช่นกัน


7
+1 เครื่องจักรในการใช้งานนั้นน่ากลัว แต่อินเทอร์เฟซสุดท้ายนั้นยากที่จะเอาชนะเพื่อความสง่างาม
deft_code

4
ยังมีอีกไหมที่จะได้รับสิ่งนี้เพื่อให้คุณให้ค่า enum จำนวนเต็ม ตัวอย่างเช่น Windows จะเป็น 3, Linux 5 และ Apple 7?
มาร์ค

4
ใช่คุณสามารถเปลี่ยน(Windows)เข้าไป(Windows, 3)แล้วแทนที่ด้วยการเขียนอย่างเหมาะสมBOOST_PP_SEQ_ENUM BOOST_PP_SEQ_FOR_EACHฉันไม่มีตัวอย่างของที่มีประโยชน์ แต่ฉันสามารถเขียนได้ถ้าคุณต้องการ
James McNellis

2
@JamesMcNellis ฉันต้องการตัวอย่างรหัสที่บรรลุสิ่งที่มาร์คขออย่างแน่นอนคุณจะใจดีมากพอที่จะแสดงให้เราเห็นหรือไม่? :)
Omer Raviv

2
หมายเหตุ: boost preprocessor มีขีด จำกัด อย่างหนักที่ 256 องค์ประกอบ สำหรับ enum ที่มีขนาดใหญ่ขึ้นจำเป็นต้องใช้โซลูชันอื่น
dshin

32

นี่คือบล็อกก่อนโปรเซสเซอร์

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

นิยาม Enum

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

โทรโดยใช้

GetStringOs_type(winblows);

นำมาจากที่นี่ . จะเด็ดขนาดไหน :)


1
นี่เป็นวิธีเดียวที่ใช้ได้ผลเมื่อ enum ของคุณมีองค์ประกอบมากกว่า 256 รายการ
dshin

8

ใช้std::map<OS_type, std::string>และเติมข้อมูลด้วย enum เป็นคีย์และการแทนค่าสตริงเป็นค่าจากนั้นคุณสามารถทำได้:

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

7

ปัญหาเกี่ยวกับ C enums คือไม่ใช่ประเภทของตัวเองเหมือนใน C ++ enum ใน C เป็นวิธีการแมปตัวระบุกับค่าอินทิกรัล แค่นั้น. นั่นเป็นเหตุผลที่ค่า enum ใช้แทนกันได้กับค่าจำนวนเต็ม

ตามที่คุณเดาถูกวิธีที่ดีคือสร้างการจับคู่ระหว่างค่า enum และสตริง ตัวอย่างเช่น:

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

ฉันคิดว่า - ไม่ถูกต้อง - ภาษาโปรแกรมถูก จำกัด ไว้ที่ C.
Andrew

1
คุณเป็นคนขี้น้อยใจenum เป็นประเภทใน C. ค่าคงที่ประเภทการแจงนับอินทิกรัลเป็นประเภทintและไม่ใช่enumประเภทที่พวกเขากำหนดไว้อาจเป็นสิ่งที่คุณตั้งใจจะพูด แต่ฉันไม่เห็นเลยว่ามันเกี่ยวข้องกับคำถามอะไร
Jens Gustedt

7

ฉันได้รวมโซลูชันของJames , HowardและÉderและสร้างการใช้งานทั่วไปมากขึ้น:

  • ค่า int และการแสดงสตริงที่กำหนดเองสามารถกำหนดเป็นทางเลือกสำหรับแต่ละองค์ประกอบ enum
  • ใช้ "enum class"

โค้ดเต็มเขียนร้อง (ใช้ "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" เพื่อกำหนด enum) ( การสาธิตออนไลน์ )

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

นี่คือคำตอบที่ดีที่สุดจนถึงตอนนี้
Arnout

6

ตัวอย่างง่ายๆนี้ใช้ได้ผลสำหรับฉัน หวังว่านี่จะช่วยได้

#include <iostream>
#include <string>

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

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

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

13
จะไม่ทำงานถ้าคุณมี DIRECTION a = NORTH; แล้วเขียน ENUM_TO_STR (a)
mathreadler

5

คุณลองทำสิ่งนี้:

#define stringify( name ) # name

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

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

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

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

stringify()แมโครสามารถใช้ในการเปิดข้อความใด ๆ ในรหัสของคุณลงในสตริง แต่เฉพาะข้อความที่แน่นอนระหว่างวงเล็บ ไม่มีการอ้างอิงตัวแปรหรือการแทนที่มาโครหรือสิ่งอื่นใดที่เสร็จสิ้น

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


อันนี้จะเป็นอันดับต้น ๆ จริงๆแม้ว่าแค่อันแรกก็เพียงพอแล้ว :)
pholat

ใช้งานได้ดี แต่คุณควรเพิ่ม #ifndef stringify ที่ด้านบนเพื่อหลีกเลี่ยงข้อผิดพลาดในการคอมไพล์ ฉันยังเปลี่ยน enum type เป็น std :: string ตามที่ dgmz แนะนำ
astarakastara

5

มีคำตอบดีๆมากมายที่นี่ แต่ฉันคิดว่าบางคนอาจคิดว่าของฉันมีประโยชน์ ฉันชอบเพราะอินเทอร์เฟซที่คุณใช้กำหนดมาโครนั้นเรียบง่ายอย่างที่คิด นอกจากนี้ยังมีประโยชน์เพราะคุณไม่จำเป็นต้องรวมไลบรารีเพิ่มเติมใด ๆ - ทั้งหมดนี้มาพร้อมกับ C ++ และไม่จำเป็นต้องใช้เวอร์ชันล่าสุด ฉันดึงชิ้นส่วนจากที่ต่างๆทางออนไลน์ดังนั้นฉันจึงไม่สามารถรับเครดิตได้ทั้งหมด แต่ฉันคิดว่ามันมีเอกลักษณ์พอที่จะรับประกันคำตอบใหม่ได้

ก่อนอื่นให้สร้างไฟล์ส่วนหัว ... เรียกมันว่า EnumMacros.h หรืออะไรทำนองนั้นแล้วใส่สิ่งนี้ไว้:

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

จากนั้นในโปรแกรมหลักของคุณคุณสามารถทำได้ ...

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

โดยผลลัพธ์จะเป็น >> ค่าของ 'Apple' คือ: 2 จาก 4

สนุก!


สิ่งสำคัญที่ฉันชอบเกี่ยวกับวิธีการนี้คือมันทำงานร่วมกับไวยากรณ์ที่คั่นด้วยเครื่องหมายจุลภาคปกติของ enum (ตราบใดที่ไม่รวมการกำหนดการตั้งค่าใด ๆ ภายใน enum) ในกรณีของฉันฉันต้องทำงานกับ enum ที่มีอยู่กับสมาชิกจำนวนมากดังนั้นจึงง่ายกว่าวิธีการเพิ่มมาก
CuriousKea

4

สมมติว่า enum ของคุณถูกกำหนดไว้แล้วคุณสามารถสร้างอาร์เรย์ของคู่:

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

ตอนนี้คุณสามารถสร้างแผนที่:

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

ตอนนี้คุณสามารถใช้แผนที่ หาก enum ของคุณมีการเปลี่ยนแปลงคุณต้องเพิ่ม / ลบคู่จากคู่อาร์เรย์ [] ฉันคิดว่ามันเป็นวิธีที่ดีที่สุดในการรับสตริงจาก enum ใน C ++


2
นอกเหนือจากความคิดเห็นที่เป็นธรรมว่าไม่จำเป็นต้องใช้ Qt ที่นี่แล้วอีกประเด็นหนึ่งคืออาจต้องการใช้ Boost bimapในกรณีที่ต้องการแยกวิเคราะห์ชื่อและเปลี่ยนเป็น enums (เช่นจากไฟล์ XML)
Dmitri Nesteruk

4
ไม่ควรใช้ประเภท Qt ในคำถาม C ++ ทั่วไป
เวกเตอร์

3

สำหรับ C99 มีP99_DECLARE_ENUMในP99ที่ให้คุณประกาศenumดังนี้:

P99_DECLARE_ENUM(color, red, green, blue);

จากนั้นใช้color_getname(A)เพื่อรับสตริงที่มีชื่อสี


2

นี่คือรหัส C ++ ของฉัน:

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

ไปงานปาร์ตี้สายนิดหน่อย แต่นี่คือวิธีแก้ปัญหา C ++ 11 ของฉัน:

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

ความชอบของฉันเองคือลดทั้งการพิมพ์ซ้ำ ๆ และมาโครที่เข้าใจยากและหลีกเลี่ยงการแนะนำนิยามมาโครในพื้นที่คอมไพเลอร์ทั่วไป

ดังนั้นในไฟล์ส่วนหัว:

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

static Level readLevel(const char *);

และการใช้งาน cpp คือ:

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

สังเกต #undef ของมาโครทันทีที่เราทำเสร็จแล้ว


2

วิธีแก้ปัญหาของฉันไม่ใช้บูสต์:

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

และนี่คือวิธีการใช้งาน

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

2

อีกคนมาสายปาร์ตี้โดยใช้ตัวประมวลผลล่วงหน้า:

 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

(ฉันแค่ใส่หมายเลขบรรทัดเพื่อให้ง่ายต่อการพูดถึง) บรรทัด 1-4 คือสิ่งที่คุณแก้ไขเพื่อกำหนดองค์ประกอบของ enum (ฉันเรียกมันว่า "มาโครรายการ" เพราะเป็นมาโครที่สร้างรายการสิ่งต่างๆ @Lundin แจ้งให้ฉันทราบว่านี่เป็นเทคนิคที่รู้จักกันดีที่เรียกว่า X-macros)

บรรทัดที่ 7 กำหนดมาโครภายในเพื่อเติมคำประกาศ enum จริงในบรรทัดที่ 8-11 บรรทัดที่ 12 ไม่ได้กำหนดมาโครภายใน (เพื่อปิดเสียงเตือนของคอมไพเลอร์)

บรรทัดที่ 14 กำหนดมาโครภายในเพื่อสร้างเวอร์ชันสตริงของชื่อองค์ประกอบ enum จากนั้นบรรทัดที่ 15-18 จะสร้างอาร์เรย์ที่สามารถแปลงค่า enum เป็นสตริงที่เกี่ยวข้อง

บรรทัดที่ 21-27 สร้างฟังก์ชันที่แปลงสตริงเป็นค่า enum หรือส่งคืนค่า NULL หากสตริงไม่ตรงกับค่าใด ๆ

นี่เป็นเรื่องยุ่งยากเล็กน้อยในการจัดการกับองค์ประกอบที่ 0 ฉันเคยทำแบบนั้นมาแล้วในอดีต

ฉันยอมรับว่าเทคนิคนี้รบกวนผู้ที่ไม่ต้องการคิดว่าตัวประมวลผลล่วงหน้าสามารถตั้งโปรแกรมให้เขียนโค้ดให้คุณได้ ฉันคิดว่ามันยิ่งแสดงให้เห็นถึงความแตกต่างระหว่างการอ่านและการบำรุงรักษา รหัสอ่านยาก แต่ถ้า enum มีองค์ประกอบไม่กี่ร้อยรายการคุณสามารถเพิ่มลบหรือจัดเรียงองค์ประกอบใหม่ได้และยังต้องแน่ใจว่าโค้ดที่สร้างขึ้นไม่มีข้อผิดพลาด


"X มาโคร" แทบจะไม่เคยเป็นวิธีแก้ปัญหาที่สวยงาม ในกรณีนี้จะสามารถอ่านได้มากขึ้นเพียงแค่กำหนดรายการมาโคร#define TEST_1 hello #define TEST_2 worldจากtypedef enum { TEST_1, TEST_2 } test_t;นั้นสร้างตารางการค้นหาสตริงที่ใช้มาโคร stringify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; มีคำตอบหลายคำที่บ่งบอกถึงโซลูชันที่คล้ายคลึงกันอยู่แล้ว อ่านได้ไกลขึ้น
Lundin

@Lundin: ฉันอ้างเพียง 1) สิ่งนี้ใช้ได้กับคอมไพเลอร์ C ดั้งเดิมที่สุดและ 2) การเพิ่มหรือลบองค์ประกอบเป็นการแก้ไข 1 บรรทัด
Mike Dunlavey

ผมเคยโพสต์คำตอบของตัวเอง: การstackoverflow.com/a/39877228/584518 หวังว่ามันจะช่วยชีวิตที่น่าสงสารจากโซลูชัน x มาโครได้
Lundin

1
ฉันใช้วิธีแก้ปัญหาของคุณ ผมว่าดีที่สุด ไวยากรณ์ C ยังคงอยู่ที่นั่นเพื่อให้คุณเข้าใจสิ่งที่เกิดขึ้นและรายการจะถูกกำหนดเพียงครั้งเดียว คุณสามารถลบองค์ประกอบที่ 0 ได้โดยวางลูกน้ำไว้หลังรายการใน DEFINE_ENUM_ELEMENT ของคุณ
เข้า

1

นี่คือวิธี Old Skool (เคยใช้กันอย่างแพร่หลายใน gcc) โดยใช้เพียง C pre-processor มีประโยชน์หากคุณกำลังสร้างโครงสร้างข้อมูลที่ไม่ต่อเนื่อง แต่ต้องรักษาลำดับให้สอดคล้องกัน รายการใน mylist.tbl สามารถขยายไปสู่สิ่งที่ซับซ้อนกว่านี้ได้

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

แล้ว 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
เทคนิคนี้เรียกว่า x macros!
Watusimoto

0

ใน c ++ เช่นนี้:

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>

จากhttp://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-Cและหลัง

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

แทรก

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

ทำงานได้ดีถ้าค่าใน enum ไม่ซ้ำกัน

โค้ดตัวอย่างสำหรับการแปลงค่า enum เป็นสตริง:

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

ตัวอย่างโค้ดตรงข้าม:

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

0

ขอบคุณเจมส์สำหรับคำแนะนำของคุณ มันมีประโยชน์มากดังนั้นฉันจึงใช้วิธีอื่นเพื่อมีส่วนร่วมในทางใดทางหนึ่ง

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

using namespace std;

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

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

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


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

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

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

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

    return 0;
}

0

เพื่อขยายคำตอบของ James มีคนต้องการโค้ดตัวอย่างเพื่อรองรับ enum define ด้วยค่า int ฉันมีข้อกำหนดนี้ด้วยดังนั้นนี่คือวิธีของฉัน:

อย่างแรกคือมาโครใช้ภายในซึ่ง 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) ),

และนี่คือมาโครกำหนด:

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

ดังนั้นเมื่อใช้งานคุณอาจต้องการเขียนดังนี้:

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

ซึ่งจะขยายเป็น:

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

แนวคิดพื้นฐานคือการกำหนด SEQ ซึ่งทุกองค์ประกอบเป็น TUPLE ดังนั้นเราจึงสามารถใส่ค่าเพิ่มเติมสำหรับสมาชิก enum ได้ ใน FOR_EACH ลูปตรวจสอบรายการขนาด TUPLE ถ้าขนาดเท่ากับ 2 ให้ขยายโค้ดเป็น KEY = VALUE มิฉะนั้นให้เก็บองค์ประกอบแรกของ TUPLE ไว้

เนื่องจาก SEQ อินพุตเป็น TUPLE จริง ๆ ดังนั้นหากคุณต้องการกำหนดฟังก์ชัน STRINGIZE คุณอาจต้องประมวลผลตัวนับอินพุตล่วงหน้าก่อนนี่คือมาโครในการทำงาน:

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

มาโคร DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQจะเก็บเฉพาะองค์ประกอบแรกในทุก TUPLE และต่อมาแปลงเป็น SEQ ตอนนี้แก้ไขโค้ดของ James คุณจะมีพลังเต็มที่

การใช้งานของฉันอาจไม่ใช่วิธีที่ง่ายที่สุดดังนั้นหากคุณไม่พบรหัสที่สะอาดใด ๆ ขอให้ฉันเป็นข้อมูลอ้างอิงของคุณ


0

น้ำยาที่สะอาดปลอดภัยในมาตรฐาน C:

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

เอาท์พุต

0 hello
1 world
1 world

หลักการและเหตุผล

เมื่อแก้ปัญหาหลัก "มีค่าคงที่กับสตริงที่สอดคล้องกัน" โปรแกรมเมอร์ที่เหมาะสมจะมาพร้อมกับข้อกำหนดต่อไปนี้:

  • หลีกเลี่ยงการเขียนโค้ดซ้ำ (หลักการ "DRY")
  • รหัสต้องสามารถปรับขนาดได้บำรุงรักษาและปลอดภัยแม้ว่าจะมีการเพิ่มหรือลบรายการภายใน enum ก็ตาม
  • รหัสทั้งหมดควรมีคุณภาพสูง: อ่านง่ายดูแลรักษาง่าย

ข้อกำหนดแรกและอาจเป็นข้อที่สองสามารถตอบสนองได้ด้วยโซลูชันมาโครที่ยุ่งเหยิงต่างๆเช่นเคล็ดลับ "x มาโคร" ที่น่าอับอายหรือรูปแบบอื่น ๆ ของเวทมนตร์มาโคร ปัญหาในการแก้ปัญหาดังกล่าวคือพวกมันทำให้คุณมีมาโครลึกลับที่ไม่สามารถอ่านได้อย่างสมบูรณ์ - ไม่เป็นไปตามข้อกำหนดที่สามข้างต้น

สิ่งเดียวที่จำเป็นในที่นี้คือมีตารางค้นหาสตริงซึ่งเราสามารถเข้าถึงได้โดยใช้ตัวแปร enum เป็นดัชนี ตารางดังกล่าวต้องสอดคล้องกับ enum โดยตรงและในทางกลับกัน เมื่อมีการอัปเดตรายการใดรายการหนึ่งจะต้องอัปเดตด้วยไม่เช่นนั้นจะใช้งานไม่ได้


คำอธิบายของรหัส

สมมติว่าเรามี enum เช่น

typedef enum
{
  hello,
  world
} test_t;

สิ่งนี้สามารถเปลี่ยนเป็น

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

ด้วยข้อได้เปรียบที่ตอนนี้ค่าคงที่ของแมโครเหล่านี้สามารถนำไปใช้ที่อื่นได้เช่นสร้างตารางค้นหาสตริง การแปลงค่าคงที่ก่อนตัวประมวลผลเป็นสตริงสามารถทำได้ด้วยมาโคร "stringify":

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

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

และนั่นแหล่ะ เมื่อใช้helloเราจะได้ค่าคงที่ enum ด้วยค่า 0 โดยใช้test_str[hello]เราได้สตริง "hello"

เพื่อให้ตาราง enum และ look-up สอดคล้องกันโดยตรงเราต้องตรวจสอบให้แน่ใจว่ามีจำนวนรายการเท่ากัน หากมีคนรักษารหัสและเปลี่ยนเฉพาะ enum ไม่ใช่ตารางค้นหาหรือในทางกลับกันวิธีนี้จะไม่ได้ผล

วิธีแก้ปัญหาคือต้องมี enum เพื่อบอกว่ามีกี่รายการ มีเคล็ดลับ C ที่ใช้กันทั่วไปสำหรับสิ่งนี้เพียงแค่เพิ่มรายการในตอนท้ายซึ่งเติมเต็มจุดประสงค์ในการบอกว่า enum มีกี่รายการ:

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

ตอนนี้เราสามารถตรวจสอบในเวลาคอมไพล์ได้ว่าจำนวนรายการใน enum มีมากเท่ากับจำนวนรายการในตารางการค้นหาโดยเฉพาะอย่างยิ่งด้วยการยืนยันแบบคงที่ C11:

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

(มีวิธีที่น่าเกลียด แต่ใช้งานได้เต็มรูปแบบในการสร้างการยืนยันแบบคงที่ในมาตรฐาน C เวอร์ชันเก่าด้วยเช่นกันหากมีคนยืนยันที่จะใช้คอมไพเลอร์ไดโนเสาร์สำหรับ C ++ ก็รองรับการยืนยันแบบคงที่เช่นกัน)


ตามหมายเหตุด้านข้างใน C11 เราสามารถบรรลุความปลอดภัยประเภทที่สูงขึ้นได้โดยการเปลี่ยนมาโครสตริง:

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

( intเนื่องจากค่าคงที่การแจงนับเป็นประเภทintไม่ใช่test_t)

สิ่งนี้จะป้องกันไม่ให้STRINGIFY(random_stuff)คอมไพล์โค้ด like


ฉันเข้าใจสิ่งที่คุณพูด แต่ประเด็นยังคงอยู่ การเปลี่ยนแปลงที่คาดการณ์ได้โดยทั่วไปควรมีการแก้ไขเพียงเล็กน้อย (เช่น 1 บรรทัด) (ฉันคิดว่านั่นเป็นเหตุผลเบื้องหลัง DRY) ที่นี่ถ้าขนาดของ enum เท่ากับ 500 และคุณต้องการแทรกองค์ประกอบใหม่ตรงกลาง (หรือลบ / เปลี่ยนชื่อ / แลกเปลี่ยน) คุณต้องมีโค้ดกี่บรรทัด การเปลี่ยนแปลงและคุณต้องตรวจสอบมากแค่ไหนเพื่อให้แน่ใจว่าคุณไม่ได้ทำผิดพลาด? นอกจากนี้ยังอาจมีโค้ดอื่น ๆ ที่ทำบางอย่างเหมือนกันสำหรับแต่ละองค์ประกอบของรายการ
Mike Dunlavey

ขอขอบคุณที่บอกฉันเหล่านี้เรียกว่าX-แมโคร ฉันไม่รู้เรื่องนั้น สิ่งที่ฉันไม่เห็นคือผู้คนดูถูกพวกเขาโดยทั่วไป
Mike Dunlavey

@MikeDunlavey ไม่ว่า enum จะมีขนาดเท่าใดคุณจะต้องเปลี่ยน 3 บรรทัด: เพิ่ม a #define, เพิ่มการอ้างอิงที่กำหนดในการประกาศ enum และตารางการค้นหา หากคุณจะทำผิดเมื่อเพิ่มบรรทัดเหล่านั้นโปรแกรมจะไม่คอมไพล์ ตัวเลขที่ฉันเพิ่มลงในตัวระบุนั้นไม่ได้บังคับคุณสามารถเขียน#define APPLES helloและ#define ORANGES worldตามด้วยtypedef enum { APPES, ORANGES, TEST_N } test_t;และอื่น ๆ ได้เช่นกัน
Lundin

@MikeDunlavey เกี่ยวกับมาโคร X อาร์กิวเมนต์ที่ต่อต้านจะเหมือนกับอาร์กิวเมนต์เทียบกับมาโครที่เหมือนฟังก์ชันใด ๆ คุณไม่จำเป็นต้องมองไปไกลเพื่อค้นหาคำวิจารณ์ที่ถูกต้องมากมายเกี่ยวกับมาโครที่เหมือนฟังก์ชัน
Lundin

0

สิ่งที่ฉันทำคือการรวมกันของสิ่งที่ฉันได้เห็นที่นี่และในคำถามที่คล้ายกันในไซต์นี้ ฉันทำให้เป็น Visual Studio 2013 ฉันยังไม่ได้ทดสอบกับคอมไพเลอร์อื่น

ก่อนอื่นฉันกำหนดชุดมาโครที่จะทำเทคนิค

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

จากนั้นกำหนดมาโครเดียวที่จะสร้างคลาส enum และฟังก์ชันเพื่อรับสตริง

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

ตอนนี้การกำหนดประเภท enum และมีสตริงสำหรับมันกลายเป็นเรื่องง่ายมาก สิ่งที่คุณต้องทำคือ:

ENUM(MyEnumType, A, B, C);

คุณสามารถใช้บรรทัดต่อไปนี้เพื่อทดสอบได้

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

สิ่งนี้จะส่งออก:

3
A
B
C
A
B
C
A
B
C

ฉันเชื่อว่ามันสะอาดและใช้งานง่ายมาก มีข้อ จำกัด บางประการ:

  • คุณไม่สามารถกำหนดค่าให้กับสมาชิก enum
  • ค่าสมาชิก enum ถูกใช้เป็นดัชนี แต่ก็น่าจะใช้ได้เพราะทุกอย่างถูกกำหนดไว้ในมาโครเดียว
  • คุณไม่สามารถใช้เพื่อกำหนดประเภท enum ภายในคลาสได้

หากคุณสามารถแก้ไขปัญหานี้ได้ ฉันคิดว่าโดยเฉพาะอย่างยิ่งวิธีใช้นี่เป็นสิ่งที่ดีและเรียบง่าย ข้อดี:

  • ง่ายต่อการใช้.
  • ไม่จำเป็นต้องแยกสตริงเมื่อรันไทม์
  • มีการแยกสตริงในเวลาคอมไพล์
  • อ่านง่าย มาโครชุดแรกอาจต้องใช้เวลาเพิ่มอีกเป็นวินาที แต่ก็ไม่ได้ซับซ้อนขนาดนั้น

0

วิธีแก้ปัญหาที่สะอาดสำหรับปัญหานี้คือ:

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

ข้อดีของการแก้ปัญหานี้คือมันง่ายและการสร้างฟังก์ชันสามารถทำได้อย่างง่ายดายผ่านการคัดลอกและแทนที่ โปรดทราบว่าหากคุณกำลังจะทำการแปลงจำนวนมากและ enum ของคุณมีค่าที่เป็นไปได้มากเกินไปโซลูชันนี้อาจใช้งาน CPU มาก


0

ฉันช้าไปหน่อย แต่นี่คือวิธีแก้ปัญหาของฉันโดยใช้ g ++ และไลบรารีมาตรฐานเท่านั้น ฉันพยายามลดมลพิษเนมสเปซให้น้อยที่สุดและไม่จำเป็นต้องพิมพ์ชื่อ enum ซ้ำ

ไฟล์ส่วนหัว "my_enum.hpp" คือ:

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

ตัวอย่างการใช้งาน:

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

สิ่งนี้จะส่งออก:

MERCURY
EARTH

คุณต้องกำหนดทุกอย่างเพียงครั้งเดียวเนมสเปซของคุณไม่ควรมีมลพิษและการคำนวณทั้งหมดจะทำเพียงครั้งเดียว (ส่วนที่เหลือเป็นเพียงการค้นหา) อย่างไรก็ตามคุณไม่ได้รับประเภทความปลอดภัยของคลาส enum (ยังคงเป็นเพียงจำนวนเต็มสั้น ๆ ) คุณไม่สามารถกำหนดค่าให้กับ enums ได้คุณต้องกำหนด enums ที่ใดที่หนึ่งที่คุณสามารถกำหนดเนมสเปซได้ (เช่นทั่วโลก)

ฉันไม่แน่ใจว่าประสิทธิภาพของสิ่งนี้ดีเพียงใดหรือเป็นความคิดที่ดี (ฉันเรียนรู้ C ก่อน C ++ ดังนั้นสมองของฉันยังคงทำงานในลักษณะนั้น) หากใครทราบว่าเหตุใดจึงเป็นความคิดที่ไม่ดีอย่าลังเลที่จะชี้ให้เห็น


0

มันเป็นปี 2017 แต่คำถามยังคงมีชีวิตอยู่

อีกวิธีหนึ่ง:

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

ขาออก:

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

นี่คือคลาส enum รุ่นขยายอย่างละเอียด ... มันไม่ได้เพิ่มค่า enum อื่น ๆ นอกเหนือจากที่ให้มา

การใช้งาน: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)


0

ฉันต้องการสิ่งนี้เพื่อทำงานในทั้งสองทิศทางและฉันมักจะฝัง enums ของฉันไว้ในคลาสที่มีส่วนประกอบดังนั้นฉันจึงเริ่มด้วยวิธีการแก้ปัญหาโดย James McNellis ทางที่ด้านบนของคำตอบเหล่านี้ แต่ฉันทำโซลูชันนี้ โปรดทราบว่าฉันชอบคลาส enum มากกว่าแค่ enum ซึ่งทำให้คำตอบค่อนข้างซับซ้อน

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

หากต้องการใช้ในชั้นเรียนคุณสามารถทำสิ่งนี้ได้:

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

และฉันได้เขียนการทดสอบ CppUnit ซึ่งสาธิตวิธีการใช้งาน:

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

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

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

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

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

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

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

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

คุณต้องเลือกมาโครที่จะใช้ DEFINE_ENUMERATION หรือ DEFINE_ENUMERATION_INSIDE_CLASS คุณจะเห็นว่าฉันใช้อย่างหลังเมื่อกำหนด ComponentStatus :: Status แต่ฉันใช้แบบเดิมเมื่อกำหนดสถานะ ข้อแตกต่างง่ายๆ ในชั้นเรียนฉันจะนำหน้าเมธอด to / from เป็น "static" และถ้าไม่ใช่ในคลาสฉันจะใช้ "inline" ความแตกต่างเล็กน้อย แต่จำเป็น

น่าเสียดายที่ฉันไม่คิดว่าจะมีวิธีหลีกเลี่ยงที่จะทำสิ่งนี้ได้อย่างชัดเจน:

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

แม้ว่าคุณจะสามารถสร้างเมธอดแบบอินไลน์ได้ด้วยตนเองหลังจากนิยามคลาสของคุณซึ่งเชื่อมโยงกับเมธอดคลาสเท่านั้นสิ่งที่ต้องการ:

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

0

คำตอบของฉันเองไม่ได้ใช้บูสต์ - โดยใช้แนวทางของฉันเองโดยไม่ต้องกำหนดเวทย์มนตร์อย่างหนักและวิธีนี้มีข้อ จำกัด ที่ไม่สามารถกำหนดค่า enum ที่เฉพาะเจาะจงได้

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

เวอร์ชันล่าสุดสามารถพบได้ใน github ที่นี่:

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


0

มีคำตอบอื่น ๆ อีกมากมายสำหรับสิ่งนี้ แต่ฉันคิดว่าวิธีที่ดีกว่าคือใช้คุณสมบัติ C ++ 17 และใช้ constexpr เพื่อให้การแปลเสร็จสิ้นในเวลาคอมไพล์ ประเภทนี้ปลอดภัยและเราไม่จำเป็นต้องยุ่งกับมาโคร ดูด้านล่าง:

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

}

จากนั้นสามารถใช้สิ่งนี้ได้อย่างง่ายดายเพื่อให้ตรวจพบข้อผิดพลาดของคีย์สตริงในเวลาคอมไพล์:

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

โค้ดมีรายละเอียดมากกว่าโซลูชันอื่น ๆ แต่เราสามารถทำการแปลง Enum เป็น String และการแปลง String to Enum ได้อย่างง่ายดายในเวลาคอมไพล์และตรวจจับข้อผิดพลาดประเภท ด้วยคุณสมบัติบางอย่างของ C ++ 20 ในอนาคตสิ่งนี้อาจทำให้ง่ายขึ้นอีกเล็กน้อย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.