enum เป็นสตริงใน C ++ 11 / C ++ 14 / C ++ 17 ที่ทันสมัยและ C ++ ในอนาคต 20


354

ตรงกันข้ามกับคำถามที่คล้ายกันทั้งหมดคำถามนี้เกี่ยวกับการใช้คุณสมบัติ C ++ ใหม่

หลังจากอ่านคำตอบมากมายฉันยังไม่พบสิ่งใด:

  • วิธีที่สง่างามโดยใช้ภาษา C ++ 11 , C ++ 14หรือC ++ 17คุณสมบัติใหม่
  • หรือสิ่งที่พร้อมใช้งานในBoost
  • มีอะไรที่วางแผนไว้สำหรับC ++ 20

ตัวอย่าง

ตัวอย่างมักจะดีกว่าคำอธิบายที่ยาว
คุณสามารถรวบรวมและเรียกใช้ข้อมูลโค้ดนี้ในColiru
( อีกตัวอย่างก่อนหน้านี้ยังมีอยู่)

#include <map>
#include <iostream>

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

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

ข้อ จำกัด

  • กรุณาไม่มีการทำซ้ำไร้ค่าของคำตอบอื่น ๆหรือการเชื่อมโยงขั้นพื้นฐาน
  • โปรดหลีกเลี่ยงคำตอบจากแมโคร bloat หรือพยายามลด#defineค่าใช้จ่ายให้น้อยที่สุดเท่าที่จะทำได้
  • กรุณาไม่มีคู่มือenum-> stringการทำแผนที่

ยินดีที่ได้

  • enumค่าสนับสนุนเริ่มต้นจากตัวเลขที่แตกต่างจากศูนย์
  • รองรับenumค่าลบ
  • สนับสนุนenumค่าที่อยู่อย่างกระจัดกระจาย
  • การสนับสนุนclass enum(C ++ 11)
  • สนับสนุนการclass enum : <type>มีการอนุญาตใด ๆ<type>(C ++ 11)
  • การคอมไพล์เวลา (ไม่ใช่การรันไทม์) การแปลงเป็นสตริง
    หรือการดำเนินการอย่างรวดเร็วในเวลาทำงาน (เช่นstd::mapไม่ใช่ความคิดที่ดี ... )
  • constexpr (C ++ 11 จากนั้นผ่อนคลายใน C ++ 14/17/20)
  • noexcept (C ++ 11)
  • C ++ 17 / C ++ 20ตัวอย่างข้อมูลที่เป็นมิตร

แนวคิดหนึ่งที่เป็นไปได้คือการใช้ความสามารถของคอมไพเลอร์ C ++ เพื่อสร้างโค้ด C ++ ที่เวลารวบรวมโดยใช้เทคนิคการเขียนโปรแกรมเมตาตามvariadic template classและconstexprฟังก์ชั่น ...


4
(อาจเป็นของหัวข้อ) ดูที่บล็อกที่เกี่ยวข้องกับ Qt นี้ woboq.com/blog/reflection-in-cpp-and-qt-moc.html อธิบายความเป็นไปได้ของการแทนที่ moc ของ mt (meta-object-compiler) โดยใช้การสะท้อน C ++ (มาตรฐานที่เสนอ)
ibre5041

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
ecatmur

1
ฉันแก้ไขปัญหานี้เป็นการส่วนตัวด้วยการปรับใช้ยูทิลิตี้ไลบรารีตัวประมวลผลล่วงหน้าขนาดเล็กซึ่งอนุญาตให้ฉันวนลูปมากกว่าอาร์กิวเมนต์แมโครแบบแปรปรวน ฉันส่งค่า enum เป็นอาร์กิวเมนต์มาโครและฉันสร้าง enum และอาร์เรย์สตริงผ่านตัวประมวลผลล่วงหน้าโดยอัตโนมัติ คุณสามารถทำได้โดยใช้ Boost Preprocessor เช่นกัน
Vittorio Romeo

2
ทุกอย่างต้องแก้ไขด้วย C ++ หรือไม่ มันง่ายมากที่จะสร้างรหัสโดยอัตโนมัติสำหรับการแสดงสตริงเพียงสองสามบรรทัดของโค้ด
Karoly Horvath

2
"กรุณาอย่าให้คำตอบ C มหภาคตามถ้าเป็นไปได้" ดีจนกว่าคุณจะยินดีที่จะรอสำหรับ C ++ 17 มีแทบจะไม่สามารถใช้งานได้อะไรและก็ไม่ได้เป็นที่ที่ดีที่จะประกาศ enums ของคุณเป็นDEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))เว้นแต่คุณจะต้อง maintaint การสร้างมาโคร ... และในกรณีที่การใส่ภาษาเป็นเพียงแฮ็คชนิดอื่นแทนเทมเพลต / แมโครไฮบริดที่มีประสิทธิภาพมากกว่า เราไม่ควรเพิ่มการใช้แมโครที่มีประโยชน์เหล่านั้นทั้งหมดลงในภาษาเพื่อให้สามารถกล่าวได้ว่ามาโครไม่มีประโยชน์อีกต่อไป
PlasmaHH

คำตอบ:


43

ไลบรารีเฉพาะส่วนหัวMagic Enumให้การสะท้อนแบบคงที่สำหรับ enums (ไปยังสตริงจากสตริงการทำซ้ำ) สำหรับ C ++ 17

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

สำหรับตัวอย่างเพิ่มเติมบ้านตรวจสอบพื้นที่เก็บข้อมูลhttps://github.com/Neargye/magic_enum

ข้อเสียเปรียบอยู่ที่ไหน

ไลบรารีนี้ใช้แฮ็คเฉพาะคอมไพเลอร์ (อิงจาก__PRETTY_FUNCTION__/ __FUNCSIG__) ซึ่งทำงานกับ Clang> = 5, MSVC> = 15.3 และ GCC> = 9

ค่า Enum [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]ต้องอยู่ในช่วง

  • โดยค่าเริ่มต้น,MAGIC_ENUM_RANGE_MIN = -128MAGIC_ENUM_RANGE_MAX = 128

  • หากต้องการความหลากหลายสำหรับทุกประเภท enum โดยค่าเริ่มต้นอีก redefine แมโครและMAGIC_ENUM_RANGE_MINMAGIC_ENUM_RANGE_MAX

  • MAGIC_ENUM_RANGE_MINจะต้องน้อยกว่าหรือเท่ากับมากกว่าและต้องมากกว่า0INT16_MIN

  • MAGIC_ENUM_RANGE_MAXต้องมากกว่าและต้องน้อยกว่า0INT16_MAX

  • หากต้องการช่วงอื่นสำหรับประเภท enum เฉพาะให้เพิ่มความเชี่ยวชาญ enum_range สำหรับประเภท enum ที่จำเป็น

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }

ทำไมจึง จำกัด ช่วง? มันเป็นการ จำกัด ความลึกของการเรียกซ้ำบางส่วนหรือเพราะการค้นหาเชิงเส้นแบบเรียบบางครั้ง?
Emile Cormier

มันอัศจรรย์มาก. ขอบคุณ! มันอาจจะมีประสิทธิภาพถ้าคอมไพเลอร์ฉลาดพอที่จะประเมินค่า constexpr std :: array ได้เพียงครั้งเดียวเท่านั้น ดีมากมาก
iestyn

87

(วิธีการของห้องสมุดbetter_enums )

มีวิธีที่จะทำให้ Enum เป็นสตริงใน C ++ ปัจจุบันที่มีลักษณะดังนี้:

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

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

การใช้งาน:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

constexprการดำเนินงานทั้งหมดสามารถทำ คุณยังสามารถใช้ข้อเสนอการสะท้อน C ++ 17 ที่กล่าวถึงในคำตอบโดย @ecatmur

  • มีเพียงแมโครเดียวเท่านั้น ฉันเชื่อว่านี่เป็นค่าต่ำสุดที่เป็นไปได้เนื่องจากการประมวลผลสตริง (preprocessor #) เป็นวิธีเดียวที่จะแปลงโทเค็นเป็นสตริงใน C ++ ปัจจุบัน
  • แมโครนั้นไม่สร้างความรำคาญเลยทีเดียว - การประกาศอย่างต่อเนื่องรวมถึงการเริ่มต้นถูกวางลงในการประกาศ enum ในตัว ซึ่งหมายความว่าพวกเขามีไวยากรณ์และความหมายเช่นเดียวกับใน enum ในตัว
  • การทำซ้ำถูกกำจัด
  • การดำเนินงานที่เป็นธรรมชาติมากที่สุดและมีประโยชน์อย่างน้อย C ++ 11 constexprเนื่องจาก นอกจากนี้ยังสามารถทำให้การทำงานกับ C ++ 98 __VA_ARGS__+ มันเป็น C ++ ที่ทันสมัยอย่างแน่นอน

นิยามของมาโครค่อนข้างเกี่ยวข้องดังนั้นฉันจึงตอบคำถามนี้ได้หลายวิธี

  • ส่วนใหญ่ของคำตอบนี้เป็นการใช้งานที่ฉันคิดว่าเหมาะสมสำหรับข้อ จำกัด ด้านพื้นที่ใน StackOverflow
  • นอกจากนี้ยังมีบทความ CodeProject ที่อธิบายถึงพื้นฐานของการใช้งานในแบบฝึกหัดแบบยาว [ ฉันควรย้ายที่นี่หรือไม่ ฉันคิดว่ามันมากเกินไปสำหรับคำตอบดังนั้น ]
  • มีไลบรารี่เต็มรูปแบบ "Better Enums"ที่ใช้มาโครในไฟล์ส่วนหัวเดียว นอกจากนี้ยังใช้แบบสอบถามคุณสมบัติชนิด N4428การแก้ไขปัจจุบันของข้อเสนอการสะท้อน C ++ 17 N4113 ดังนั้นอย่างน้อยสำหรับ enums ที่ประกาศผ่านแมโครนี้คุณสามารถมีการสะท้อน Enum ของ C ++ 17 ที่เสนอในขณะนี้ใน C ++ 11 / C ++ 14

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

ข้อจำกัดความรับผิดชอบ : ฉันเป็นผู้เขียนทั้งบทความ CodeProject และห้องสมุด

คุณสามารถลองรหัสในคำตอบนี้ , ห้องสมุดและการดำเนินงานของ N4428ออนไลน์อยู่ใน Wandbox เอกสารประกอบของไลบรารียังมีภาพรวมของวิธีใช้เป็น N4428ซึ่งอธิบายส่วน enums ของข้อเสนอนั้น


คำอธิบาย

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

กลยุทธ์คือการสร้างสิ่งนี้:

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

ปัญหาคือ:

  1. เราจะจบลงด้วยสิ่งที่ชอบ{Red = 1, Green, Blue}เป็น initializer สำหรับอาร์เรย์ค่า นี่ไม่ใช่ C ++ ที่ถูกต้องเนื่องจากRedไม่ใช่นิพจน์ที่กำหนดได้ นี้จะแก้ไขได้โดยการหล่อแต่ละอย่างต่อเนื่องเพื่อประเภทTที่มีผู้ประกอบการที่ได้รับมอบหมาย {(T)Red = 1, (T)Green, (T)Blue}แต่จะลดลงได้รับมอบหมาย:
  2. ในทำนองเดียวกันเราจะลงท้ายด้วย{"Red = 1", "Green", "Blue"}initializer สำหรับอาร์เรย์ชื่อ " = 1"เราจะต้องตัดออก ฉันไม่ทราบวิธีที่ดีในการทำเช่นนี้ในเวลารวบรวมดังนั้นเราจะเลื่อนเวลาดำเนินการนี้ไป ดังนั้น_to_stringจะไม่เป็นconstexprแต่_from_stringยังสามารถเป็นได้constexprเพราะเราสามารถรักษาช่องว่างและเครื่องหมายเท่ากับว่าเป็นเทอร์มิเนเตอร์เมื่อเปรียบเทียบกับสตริงที่ไม่ได้รับการดัดแปลง
  3. ทั้งความต้องการดังกล่าวข้างต้น "การทำแผนที่" __VA_ARGS__แมโครที่สามารถนำไปใช้แมโครอีกครั้งเพื่อให้แต่ละองค์ประกอบใน นี่เป็นมาตรฐานที่ค่อนข้างดี คำตอบนี้มีเวอร์ชั่นง่าย ๆ ที่สามารถรองรับองค์ประกอบได้มากถึง 8 องค์ประกอบ
  4. ถ้าแมโครนั้นมีอยู่ในตัวเองอย่างแท้จริงมันจำเป็นต้องประกาศว่าไม่มีข้อมูลสแตติกที่ต้องใช้คำจำกัดความแยกต่างหาก ในทางปฏิบัติหมายถึงอาร์เรย์จำเป็นต้องได้รับการดูแลเป็นพิเศษ มีสองวิธีที่เป็นไปได้: constexpr(หรือเพียงแค่const) อาร์เรย์ที่ขอบเขตเนมสเปซหรืออาร์เรย์ปกติในconstexprฟังก์ชั่นอินไลน์ไม่คงที่ รหัสในคำตอบนี้สำหรับ C ++ 11 และใช้วิธีการเดิม บทความ CodeProject สำหรับ C ++ 98 และใช้ในภายหลัง

รหัส

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

และ

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

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

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

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


Heya @mrhthepie ขออภัยที่การแก้ไขของคุณถูกปฏิเสธ ฉันเพิ่งเห็นอีเมลเกี่ยวกับเรื่องนี้ ฉันจะรวมมันเข้าไปในคำตอบ - ขอบคุณสำหรับข้อผิดพลาด!
antron

มันเยี่ยมมาก สิ่งนี้จะใช้งานได้หรือไม่ถ้าฉันต้องการบิตจำนวนหนึ่ง? เช่นเดียวกับฉันต้องการบิตของ BitFlags แต่ละอันถูก1Uเลื่อนไปตามจำนวนเงินหรือไม่
user3240688

1
ดูเหมือนว่ามีการรั่วไหลของหน่วยความจำใน_trimmed_names()รหัสที่คุณโพสต์ที่นี่ ( new char[length + 1]แต่คุณไม่ได้ตั้งค่าinitializedเป็นจริง) ฉันพลาดอะไรไปรึเปล่า? ฉันไม่เห็นปัญหาเดียวกันในรหัส github ของคุณ
user3240688

1
มันถูกตั้งค่าเป็นtrueแต่อยู่นอกifสาขา (หน่วยความจำรั่วเดิมถูกจับได้โดย @mrhthepie) ควรจะย้ายเข้าไปข้างใน ... การแก้ไข ขอบคุณสำหรับการมองอย่างใกล้ชิดทั้งเรื่องนี้และรหัส GH
antron

1
to_stringสามารถส่งคืน a string_viewจาก C ++ 17 ซึ่งไม่ต้องการการยกเลิกค่า null และกลายเป็น constexpr
Yakk - Adam Nevraumont

74

สำหรับC ++ 17 C ++ 20 คุณจะสนใจงานของ Reflection Study Group (SG7) มีชุดเอกสารที่ขนานกันครอบคลุมถ้อยคำ ( P0194 ) และเหตุผลการออกแบบและวิวัฒนาการ ( P0385 ) (ลิงก์แก้ไขไปยังกระดาษล่าสุดในแต่ละซีรี่ส์)

ตั้งแต่ P0194r2 (2016-10-15) ไวยากรณ์จะใช้reflexprคำสำคัญที่เสนอ:

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

ตัวอย่างเช่น (ดัดแปลงมาจากเสียงสะท้อนจากMatus Choclik's branch ):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

การสะท้อนคงที่ล้มเหลวในการทำให้เป็น C ++ 17 (แทนที่จะเป็นร่างสุดท้ายที่นำเสนอในการประชุมมาตรฐานเดือนพฤศจิกายน 2559 ที่ Issaquah) แต่มีความมั่นใจว่าจะทำให้เป็น C ++ 20; จากรายงานการเดินทางของ Herb Sutter :

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


2
@antron ขออภัยการแก้ไขของคุณถูกปฏิเสธ ฉันจะอนุมัติมันถ้าฉันเห็นมันทันเวลา ฉันไม่เห็น N4428 ดังนั้นขอบคุณที่สละหัว
ecatmur

3
ไม่มีปัญหาขอขอบคุณที่รวมไว้ ฉันสงสัยว่าทำไมมันถึงถูกปฏิเสธ ฉันเห็นเหตุผลของ "สำเร็จรูป" ที่ไม่ถูกต้องมากขึ้น แต่ก็มีความแม่นยำมากกว่าสำหรับยุคปัจจุบัน
antron

1
ขอบคุณ :-) ฉันได้แยกตัวอย่างสุดท้ายเพื่อหลีกเลี่ยงแถบเลื่อนแนวนอน ช่างน่าเสียดายที่ค่าMyEnum::AAAนั้นไม่สามารถผ่านเป็นอาร์กิวเมนต์ที่สองของstd::meta::get_enumerators_m: - /
olibre

1
ความจริงที่ว่างานง่าย ๆ ในเชิงมโนทัศน์ต้องใช้อาร์กิวเมนต์เทมเพลตซ้อนกัน 3 ระดับซึ่งมีการใช้งานมากเกินไป ฉันแน่ใจว่ามีเหตุผลทางเทคนิคเฉพาะเจาะจง แต่นั่นไม่ได้หมายความว่าผลลัพธ์ที่ได้นั้นเป็นมิตรกับผู้ใช้ ฉันรัก C ++ และรหัสนั้นสมเหตุสมผลสำหรับฉัน แต่ 90% ของโปรแกรมเมอร์คนอื่น ๆ ที่ฉันทำงานด้วยในแต่ละวันหลีกเลี่ยง C ++ เพราะมีรหัสเช่นนี้ ฉันผิดหวังที่ไม่ได้เห็นวิธีแก้ไขปัญหาที่เรียบง่ายกว่านี้
void.pointer

2
ดูเหมือนว่าการประมาณการปัจจุบันสำหรับการรวม TS Reflection ที่กำลังจะมาถึงในมาตรฐานคือC ++ 23 : herbalutter.com/2018/04/02/…
Tim Rae

25

เรื่องนี้คล้ายกับยูริฟินเกลสไตน์; แต่ไม่จำเป็นต้องเพิ่ม ฉันใช้แผนที่เพื่อให้คุณสามารถกำหนดค่าใด ๆ ให้กับ enums, ลำดับใดก็ได้

การประกาศของ enum class เป็น:

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

รหัสต่อไปนี้จะสร้างคลาส enum และโอเวอร์โหลดโดยอัตโนมัติ:

  • '+' '+ =' สำหรับ std :: string
  • '<<' สำหรับสตรีม
  • '~' เพียงแค่แปลงเป็นสตริง (ตัวดำเนินการเอกจะทำ แต่ฉันไม่ชอบมันเพื่อความชัดเจน)
  • '*' เพื่อรับจำนวน enums

ไม่จำเป็นต้องเพิ่มฟังก์ชั่นที่จำเป็นทั้งหมดที่มีให้

รหัส:

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

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

ตัวอย่าง:

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

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here


1
เราสามารถแบ่งบรรทัดภายในนิยามแมโครนี้ได้หรือไม่?
einpoklum

1
ฉันเพิ่มเกินพิกัดสำหรับการ*ที่จะได้รับการนับ enums ... ฉันหวังว่าคุณจะไม่ทราบ :-)
ปีเตอร์วาร์กา

1
มีเหตุผลใดบ้างที่การใช้งานนี้ใช้การstd::mapทำดัชนี (O (log (n))) มากกว่าstd::unordered_map(O (1) การทำดัชนี)?
แม่น้ำ

1
นอกจากนี้ฉันคิดว่าควรทำเครื่องหมายวิธีการinlineเพื่อให้คุณสามารถประกาศ enums ในไฟล์ส่วนหัวเช่นปกติโดยไม่ได้รับข้อผิดพลาด "หลายคำจำกัดความ" จากตัวเชื่อมโยง (ไม่แน่ใจว่าที่จริงที่สะอาด / ทางออกที่ดีที่สุดแม้ว่า)
แม่น้ำต๋ำ

1
(ขออภัยที่เป็นสแปม แต่ฉันไม่สามารถแก้ไขความคิดเห็นได้ในวันนี้) มีปัญหาอื่น ๆ ที่เกิดขึ้นกับไฟล์ส่วนหัว E##MapNameต้องย้ายแผนที่ ( ) ไปยังหน่วยการรวบรวมที่เข้าถึง enum ได้เช่นกัน ฉันได้สร้างวิธีแก้ไข แต่ไม่สะอาดมากและฉันต้องได้รับอนุญาตให้แชร์ สำหรับตอนนี้ฉันแค่แสดงความคิดเห็นว่าไม่มีประโยชน์ในการทำเครื่องหมายวิธีการแบบอินไลน์โดยไม่ต้องมีคุณสมบัติเพิ่มเติมที่จำเป็นในการสนับสนุนการใช้งานในไฟล์ส่วนหัว
แม่น้ำต๋ำ

19

ย้อนกลับไปในปี 2554 ฉันใช้เวลาช่วงสุดสัปดาห์ปรับจูนโซลูชั่นแบบมาโครและลงเอยด้วยการไม่เคยใช้เลย

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

Vim macros สนุกกว่ามาโคร C ++

ตัวอย่างชีวิตจริง:

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

ฉันจะสร้างสิ่งนี้:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

และนั่นคือวิธีที่ฉันได้รับโดย

การสนับสนุนดั้งเดิมสำหรับ enum stringification จะดีกว่ามาก ฉันสนใจมากที่จะเห็นผลลัพธ์ของกลุ่มงานไตร่ตรองใน C ++ 17

ทางเลือกที่จะทำมันถูกโพสต์โดย @sehe ในการแสดงความคิดเห็น


1
ฉันทำสิ่งนี้ แม้ว่าฉันมักจะใช้เสียงเรียกรอบ ๆ และปิดกั้นการเลือกตลอดทาง
sehe

@sehe ที่น่าสนใจ ฉันควรจะดูที่ "เซอร์ราวด์" เพราะฉันต้องการวิธีการกดแป้นจำนวนมากในขณะนี้
StackedCrooked

นี่คือเต็มไปด้วยเลือดเต็มไม่มีมาโคร (ยกเว้นการ.นับ): i.imgur.com/gY4ZhBE.gif
sehe

1
อนิเมชั่น gif นั้นน่ารัก แต่ก็ยากที่จะบอกเมื่อมันเริ่มและสิ้นสุด ... จริง ๆ แล้วเกาว่ามันไม่น่ารัก แต่ก็เสียสมาธิ ฉันบอกว่าฆ่ามัน
einpoklum

วิธีการป้องกันตัวเลือกนี้เป็นกลุ่มเป็นสิ่งที่ดีและทุก แต่ทำไมไม่เพียงแค่ใช้สิ่งที่ชอบ:'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan

14

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

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

สิ่งที่แย่ที่สุดเกี่ยวกับวิธีการนี้คือความเจ็บปวดในการรักษา แต่มันก็เป็นความเจ็บปวดในการรักษาปัญหาที่คล้ายกันอื่น ๆ ใช่ไหม?

ข้อดีของโปรแกรมนี้:

  • การใช้ตัวแปรชั่วคราว (คุณสมบัติ C ++ 14)
  • ด้วยความเชี่ยวชาญด้านเทมเพลตเราสามารถ "ตรวจจับ" เมื่อมีการใช้ค่าที่ไม่ถูกต้อง (แต่ฉันไม่แน่ใจว่าสิ่งนี้จะมีประโยชน์หรือไม่)
  • มันดูเรียบร้อย
  • การค้นหาชื่อเสร็จสิ้นในเวลารวบรวม

Live example

แก้ไข

Misterious user673679คุณพูดถูก วิธีเทมเพลตตัวแปร C ++ 14 นั้นไม่ได้จัดการกับกรณีรันไทม์มันเป็นความผิดของฉันที่จะลืมมัน :(

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

มาเริ่มใช้ชื่อแทนแม่แบบเพื่อย่อการเข้าถึงแผนที่ enum-to-string

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

จากนั้นแม่แบบ Variadic ที่ใช้เล่ห์เหลี่ยม:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

" เคล็ดลับที่ดีที่สุด " ที่นี่คือการใช้แม่แบบตัวแปรสำหรับแผนที่ซึ่งมีค่าและชื่อของแต่ละรายการ enum; แผนที่นี้จะเหมือนกันในแต่ละหน่วยการแปลและมีชื่อเหมือนกันทุกที่ดังนั้นค่อนข้างตรงไปตรงมาและเรียบร้อยถ้าเราเรียกinitializeฟังก์ชั่นเช่นนี้:

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

เรากำลังกำหนดชื่อให้แต่ละMyEnumรายการและสามารถใช้ในรันไทม์:

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

แต่สามารถปรับปรุงได้ด้วย SFINAE และ<<ตัวดำเนินการโอเวอร์โหลด:

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

ด้วยความถูกต้องoperator <<ตอนนี้เราสามารถใช้ Enum ด้วยวิธีนี้:

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

นี่เป็นเรื่องที่น่ารำคาญในการรักษาและปรับปรุงให้ดีขึ้น แต่หวังว่าคุณจะเข้าใจ

Live example


สิ่งนี้ดูค่อนข้างเรียบร้อย (เป็นไปได้หรือไม่ที่จะไม่กำหนดตัวแปรที่ไม่ได้กำหนด?) บางทีฉันอาจจะพลาดบางสิ่งบางอย่าง แต่ฉันไม่เห็นว่าจะจัดการกับกรณีรันไทม์ได้อย่างไร
673679

@Paula_plus_plus: คุณไม่ควรใช้std::arrayแผนที่แทนเทอะทะหรือเปล่า? มันจะกลายเป็นที่นิยมสำหรับ enums เริ่มต้นที่ ... อะไรค่า 2 ^ 10? บางทียิ่งกว่านั้น
einpoklum

@einpoklum ที่จะเป็นที่น่าตื่นตาตื่นใจถ้าเราสามารถมั่นใจได้ว่าที่รันไทม์วิธีการหลายองค์ประกอบenumมี น่าเสียดายที่เราทำไม่ได้ และจุดทั้งหมดของแผนที่ก็เพื่อเชื่อมโยงชื่อกับค่าซึ่งเป็นสิ่งที่std::mapดีสำหรับ
PaperBirdMaster

@Paula_plus_plus: คุณกำลังเรียกใช้initialize()ฟังก์ชันที่มีจำนวนอาร์กิวเมนต์เป็นจำนวนค่า enum ดังนั้นคุณจึงทราบจำนวนค่าในเวลารวบรวม เป็นเพียงค่าเฉพาะที่คุณขอให้พิมพ์ซึ่งเป็นที่รู้จักกันในเวลาทำงานเท่านั้น นอกจากนี้แม้ว่าคุณจะไม่ทราบหมายเลขนั้น std :: vector จะเร็วกว่า std :: map อีกครั้งในเกือบทุกกรณีที่เหมือนจริง
einpoklum

@ einpoklum เป็นจุดที่ดีมากแน่นอนฉันจะคิดถึงมันขอบคุณ! สิ่งเดียวที่ทำให้ฉันกังวลก็คือstd::arrayมันไม่ใช่คอนเทนเนอร์แบบคีย์ค่าดังนั้นจึงไม่มีวิธีการค้นหา ยังไงฉันก็จะคิด
PaperBirdMaster

7

หากคุณenumดูเหมือน

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

คุณสามารถย้ายเนื้อหาของenumไปยังไฟล์ใหม่:

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

จากนั้นค่าสามารถถูกล้อมรอบด้วยมาโคร:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

ขั้นตอนถัดไปอาจรวมถึงรายการในenumอีกครั้ง:

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

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

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

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


3
ไม่จำเป็นต้องใช้ไฟล์แยกต่างหาก นี้เป็นหลักx-แมโคร
HolyBlackCat

@HolyBlackCat หากคุณแยกการแก้ปัญหาในไฟล์บางไฟล์คุณสามารถนำค่า enum กลับมาใช้ใหม่เพื่อจุดประสงค์ที่แตกต่างกันได้
eferion

ฉันพยายามที่จะบอกคุณว่าคุณสามารถทำสิ่งเดียวกันถ้าคุณใส่รายการของค่าลงในแมโครเดียวข้างคำนิยาม enum ในส่วนหัว
HolyBlackCat

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

ยุติธรรมพอสมควร ไม่ควรลดระดับลงฉันคิดว่าเนื่องจากสามารถใช้ประโยชน์ได้อย่างแน่นอน (ให้อภัยการแก้ไขหุ่นจำลองระบบล็อคการลงคะแนนของฉันเป็นอย่างอื่น)
HolyBlackCat

6

ฉันมีปัญหาเดียวกันสองสามวันที่ผ่านมา ฉันไม่สามารถหาวิธีแก้ปัญหา C ++ ได้หากไม่มีแมโคเวทแปลก ๆ ดังนั้นฉันจึงตัดสินใจเขียนตัวสร้างโค้ด CMakeเพื่อสร้างคำสั่งตัวพิมพ์กรณีที่เรียบง่าย

การใช้งาน:

enum2str_generate(
  PATH          <path to place the files in>
  CLASS_NAME    <name of the class (also prefix for the files)>
  FUNC_NAME     <name of the (static) member function>
  NAMESPACE     <the class will be inside this namespace>
  INCLUDES      <LIST of files where the enums are defined>
  ENUMS         <LIST of enums to process>
  BLACKLIST     <LIST of constants to ignore>
  USE_CONSTEXPR <whether to use constexpr or not (default: off)>
  USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)

ฟังก์ชั่นการค้นหาไฟล์รวมในระบบไฟล์ (ใช้ไดเรกทอรีรวมให้มาพร้อมกับคำสั่ง include_directories) อ่านพวกเขาและจะ regex บางส่วนเพื่อสร้างชั้นเรียนและฟังก์ชั่น

หมายเหตุ: constexpr หมายถึงแบบอินไลน์ใน C ++ ดังนั้นการใช้ตัวเลือก USE_CONSTEXPR จะสร้างคลาสส่วนหัวเท่านั้น!

ตัวอย่าง:

./includes/ah:

enum AAA : char { A1, A2 };

typedef enum {
   VAL1          = 0,
   VAL2          = 1,
   VAL3          = 2,
   VAL_FIRST     = VAL1,    // Ignored
   VAL_LAST      = VAL3,    // Ignored
   VAL_DUPLICATE = 1,       // Ignored
   VAL_STRANGE   = VAL2 + 1 // Must be blacklisted
} BBB;

./CMakeLists.txt:

include_directories( ${PROJECT_SOURCE_DIR}/includes ...)

enum2str_generate(
   PATH       "${PROJECT_SOURCE_DIR}"
   CLASS_NAME "enum2Str"
   NAMESPACE  "abc"
   FUNC_NAME  "toStr"
   INCLUDES   "a.h" # WITHOUT directory
   ENUMS      "AAA" "BBB"
   BLACKLIST  "VAL_STRANGE")

สร้าง:

./enum2Str.hpp:

/*!
  * \file enum2Str.hpp
  * \warning This is an automatically generated file!
  */

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

#include <string>
#include <a.h>

namespace abc {

class enum2Str {
 public:
   static std::string toStr( AAA _var ) noexcept;
   static std::string toStr( BBB _var ) noexcept;
};

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

/*!
  * \file enum2Str.cpp
  * \warning This is an automatically generated file!
  */

#include "enum2Str.hpp"

namespace abc {

/*!
 * \brief Converts the enum AAA to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( AAA _var ) noexcept {
   switch ( _var ) {
      case A1: return "A1";
      case A2: return "A2";
      default: return "<UNKNOWN>";
   }
}

/*!
 * \brief Converts the enum BBB to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( BBB _var ) noexcept {
   switch ( _var ) {
      case VAL1: return "VAL1";
      case VAL2: return "VAL2";
      case VAL3: return "VAL3";
      default: return "<UNKNOWN>";
   }
}
}

ปรับปรุง:

ตอนนี้สคริปต์ยังรองรับการระบุขอบเขต (enum class | struct) และฉันย้ายมันไปที่ repo ที่แยกจากกันด้วยสคริปต์อื่น ๆ ที่ฉันมักใช้: https://github.com/mensinda/cmakeBuildTools


ว้าว! ความคิดที่เป็นต้นฉบับและสร้างสรรค์มาก :-) ฉันหวังว่าคุณจะมีความกล้าที่จะอัพเกรดเครื่องกำเนิดไฟฟ้าของคุณเพื่อที่จะให้ a constexprและnoexceptversion ;-) ฉันเพิ่งจะจ้องโครงการ GitHub ของคุณ ;-) ไชโย
olibre

1
อัพเดทเครื่องกำเนิดไฟฟ้า ตอนนี้ฟังก์ชั่นจะเป็น constexpr และ enum: <type> ได้รับการสนับสนุนแล้ว ขอบคุณสำหรับดาว :)
Mense

ลิงก์ใช้งานไม่ได้ ... -.-
Yeoman

ลิงค์ได้รับการแก้ไขแล้ว
Mense

4

เพียงแค่สร้าง enums ของคุณ การเขียนตัวสร้างเพื่อจุดประสงค์นั้นใช้เวลาประมาณห้านาที

รหัสเครื่องกำเนิดไฟฟ้าใน java และ python ง่ายต่อการย้ายไปยังภาษาใด ๆ ที่คุณต้องการรวมถึง C ++

นอกจากนี้ยังง่ายต่อการขยายด้วยฟังก์ชั่นที่คุณต้องการ

อินพุตตัวอย่าง:

First = 5
Second
Third = 7
Fourth
Fifth=11

ส่วนหัวที่สร้าง:

#include <iosfwd>

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

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

สร้างไฟล์ cpp

#include <ostream>

#include "Hallo.h"

std::ostream & operator << (std::ostream &out, const Hallo&value)
{
    switch(value)
    {
    case Hallo::First:
        out << "First";
        break;
    case Hallo::Second:
        out << "Second";
        break;
    case Hallo::Third:
        out << "Third";
        break;
    case Hallo::Fourth:
        out << "Fourth";
        break;
    case Hallo::Fifth:
        out << "Fifth";
        break;
    default:
        out << "<unknown>";
    }

    return out;
}

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

package cppgen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EnumGenerator
{
    static void fail(String message)
    {
        System.err.println(message);
        System.exit(1);
    }

    static void run(String[] args)
    throws Exception
    {
        Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
        Charset charset = Charset.forName("UTF8");
        String tab = "    ";

        if (args.length != 3)
        {
            fail("Required arguments: <enum name> <input file> <output dir>");
        }

        String enumName = args[0];

        File inputFile = new File(args[1]);

        if (inputFile.isFile() == false)
        {
            fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
        }

        File outputDir = new File(args[2]);

        if (outputDir.isDirectory() == false)
        {
            fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
        }

        File headerFile = new File(outputDir, enumName + ".h");
        File codeFile = new File(outputDir, enumName + ".cpp");

        for (File file : new File[] { headerFile, codeFile })
        {
            if (file.exists())
            {
                fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
            }
        }

        int nextValue = 0;

        Map<String, Integer> fields = new LinkedHashMap<>();

        try
        (
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
        )
        {
            while (true)
            {
                String line = reader.readLine();

                if (line == null)
                {
                    break;
                }

                if (line.trim().length() == 0)
                {
                    continue;
                }

                Matcher matcher = pattern.matcher(line);

                if (matcher.matches() == false)
                {
                    fail("Syntax error: [" + line + "]");
                }

                String fieldName = matcher.group(1);

                if (fields.containsKey(fieldName))
                {
                    fail("Double fiend name: " + fieldName);
                }

                String valueString = matcher.group(2);

                if (valueString != null)
                {
                    int value = Integer.parseInt(valueString);

                    if (value < nextValue)
                    {
                        fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
                    }

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

        try
        (
            PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
            PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
        )
        {
            headerWriter.println();
            headerWriter.println("#include <iosfwd>");
            headerWriter.println();
            headerWriter.println("enum class " + enumName);
            headerWriter.println('{');
            boolean first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                if (first == false)
                {
                    headerWriter.println(",");
                }

                headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());

                first = false;
            }
            if (first == false)
            {
                headerWriter.println();
            }
            headerWriter.println("};");
            headerWriter.println();
            headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
            headerWriter.println();

            codeWriter.println();
            codeWriter.println("#include <ostream>");
            codeWriter.println();
            codeWriter.println("#include \"" + enumName + ".h\"");
            codeWriter.println();
            codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
            codeWriter.println('{');
            codeWriter.println(tab + "switch(value)");
            codeWriter.println(tab + '{');
            first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
                codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
                codeWriter.println(tab + tab + "break;");

                first = false;
            }
            codeWriter.println(tab + "default:");
            codeWriter.println(tab + tab + "out << \"<unknown>\";");
            codeWriter.println(tab + '}');
            codeWriter.println();
            codeWriter.println(tab + "return out;");
            codeWriter.println('}');
            codeWriter.println();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            run(args);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            System.exit(1);
        }
    }
}

และพอร์ตไปยัง Python 3.5 เพราะแตกต่างกันมากพอที่จะเป็นประโยชน์

import re
import collections
import sys
import io
import os

def fail(*args):
    print(*args)
    exit(1)

pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = "    "

if len(sys.argv) != 4:
    n=0
    for arg in sys.argv:
        print("arg", n, ":", arg, " / ", sys.argv[n])
        n += 1
    fail("Required arguments: <enum name> <input file> <output dir>")

enumName = sys.argv[1]

inputFile = sys.argv[2]

if not os.path.isfile(inputFile):
    fail("Not a file: [" + os.path.abspath(inputFile) + "]")

outputDir = sys.argv[3]

if not os.path.isdir(outputDir):
    fail("Not a directory: [" + os.path.abspath(outputDir) + "]")

headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")

for file in [ headerFile, codeFile ]:
    if os.path.exists(file):
        fail("Will not overwrite file [" + os.path.abspath(file) + "]")

nextValue = 0

fields = collections.OrderedDict()

for line in open(inputFile, 'r'):
    line = line.strip()

    if len(line) == 0:
        continue

    match = pattern.match(line)

    if match == None:
        fail("Syntax error: [" + line + "]")

    fieldName = match.group(1)

    if fieldName in fields:
        fail("Double field name: " + fieldName)

    valueString = match.group(2)

    if valueString != None:
        value = int(valueString)

        if value < nextValue:
            fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')

try:
    headerWriter.write("\n")
    headerWriter.write("#include <iosfwd>\n")
    headerWriter.write("\n")
    headerWriter.write("enum class " + enumName + "\n")
    headerWriter.write("{\n")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",\n")

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

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

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

1
ขอบคุณมากสำหรับการแบ่งปันเครื่องมือสร้างของคุณในสองภาษา :-) แต่คุณมีความคิดวิธีการสร้างในเวลารวบรวม? ตัวอย่างเช่นเราสามารถจินตนาการการแปลเครื่องกำเนิดของคุณโดยใช้คำสั่ง CMake เพื่อรีเฟรชรหัส C ++ ที่สร้างขึ้นเมื่อมีการเปลี่ยนแปลงข้อมูลอินพุตหรือไม่? ความฝันของฉันคือการบังคับให้คอมไพเลอร์ C ++ สร้าง enums เมื่อมีการคอมไพล์โดยใช้การเขียนโปรแกรมเมตา ( variadic template classและconstexprฟังก์ชั่น)
olibre

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

เกี่ยวกับการสร้างสิ่งต่าง ๆ ในเวลารวบรวมมันเป็นเรื่องง่ายใน LISP เพราะไวยากรณ์นั้นสะอาดและง่ายมาก ที่ได้รับความช่วยเหลือจากความจริงที่ว่ามันถูกพิมพ์แบบไดนามิกซึ่งช่วยให้มันสั้นและอ่านง่ายโดยไม่ต้องใช้ไวยากรณ์มาก มาโคร LISP ที่เทียบเท่าใน C ++ จะต้องใช้วิธีที่ซับซ้อนมากในการอธิบาย AST ของสิ่งที่คุณกำลังพยายามสร้าง และ AST สำหรับ C ++ นั้นไม่เคยสวยเลย :(
Yeoman

โดยตรงใน Make แทน cmake มันง่ายมาก btw เพียงสร้างเป้าหมาย. h และ. cpp สำหรับแต่ละไฟล์. enum ผ่านการค้นหาและให้เป้าหมายเหล่านี้ขึ้นอยู่กับ enum defs ดังกล่าวดังนั้นพวกเขาจึงสร้างขึ้นใหม่โดยอัตโนมัติเมื่อไฟล์. enum def เปลี่ยนแปลง มันอาจจะง่ายขึ้นมากใน CMake เพราะมันเต็มไปด้วยความมหัศจรรย์สำหรับชนิดของสิ่งนี้ แต่ผมใช้เป็นประจำทำให้มดและ gradle แต่มีความรู้ จำกัด ของ Maven, CMake และแสม :)
องค์รักษ์

ขอบคุณสำหรับคำตอบของคุณ :-) ฉันคิดว่านักพัฒนา C ++ ส่วนใหญ่จะขอบคุณถ้าเครื่องกำเนิดของคุณสามารถตรวจจับ enums โดยตรงภายในรหัส C ++ เช่นenum class Hallo{ First=5, Second=6, Third=7, Fourth=8};หรือในหลายบรรทัด :-D คุณคิดว่าคุณสามารถปรับตัวสร้างของคุณเพื่อตรวจจับenumภายใน C ++ ไฟล์? วิธีที่ดีที่สุดคือการสร้างรหัสในการตรวจจับแท็กเช่น/*<Generate enum to string here>*/เท่านั้น จากนั้นตัวสร้างของคุณจะเขียนโค้ด C ++ ที่สร้างขึ้นแทน (แทนที่โค้ดที่สร้างก่อนหน้านี้) ^ _ ^ เครื่องกำเนิดไฟฟ้าที่ยอดเยี่ยมมันคืออะไร? ไชโย :-)
โอลิเบร

3

ตามคำขอจาก OP ต่อไปนี้เป็นวิธีแก้ปัญหามาโครที่น่าเกลียดในเวอร์ชั่นนี้โดยใช้Boost Preprosessorและมาโคร Variadicแมโคร

จะช่วยให้รายการง่าย ๆ เช่นไวยากรณ์ขององค์ประกอบ enumerator พร้อมกับการตั้งค่าสำหรับองค์ประกอบเฉพาะเพื่อให้

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

ขยายเป็น

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

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

รหัสที่สมบูรณ์สามารถมองเห็นได้ในการกระทำที่ทั้งIdeoneและColiru

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

ไลบรารี (ผสานภายในไฟล์ส่วนหัวเดียว)

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

การใช้

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

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

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

การรวบรวม (คัดลอกส่วนหัวของการวางภายในmain.cpp)

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

เอาท์พุต

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

5
บล็อกรหัสนี้เป็นการเดินทางที่บ้าคลั่งผ่านทิวทัศน์อันน่าทึ่งของเมตาโปรแกรมดำ จริง ๆ แล้วฉันรู้สึกโล่งใจเมื่อถึงmain- บ้านบ้านหวาน!
Quentin

เพิ่งเพิ่มลิงค์ไปยัง coliru เพื่อตรวจสอบผลลัพธ์ (มีคำเตือนให้คลิกที่ลิงค์ภายในคำตอบของคุณ) ฉันยังแบ่งเป็น Lib / การใช้งาน namespace xxxสามารถย้ายสิ่งของไปยังตำแหน่งส่วนหัวได้หรือไม่? คุณสามารถพูดได้ในบทนำใช้งานของคุณboost/preprocessor.hppและดังนั้นคำตอบที่มีความทันสมัยสอดคล้องกับ C ++ โปรดแก้ไขคำเตือนและล้างซอร์สโค้ดเล็กน้อยเพื่อคุณภาพที่ดีขึ้น
olibre

@olibre: มันเป็น copypastad จากฉันคิดว่า 5 ส่วนหัวที่แตกต่างกันในห้องสมุดของเรา enum_cast นั้นมาจากอีกส่วนที่กว้างกว่า แต่ฉันคิดว่าจะเพิ่มเข้าไปด้วยเพื่อดูว่า do_enum_cast ในมาโครมีไว้สำหรับอะไร .. คำเตือนนั้นมาจากmain<tab>vim ของฉันรวมถึง args ที่ฉันไม่ได้ใช้ด้วย ฉันไม่คิดว่ารหัสนี้สามารถทำความสะอาดได้จริง ๆ มันเป็นเพียงการแสดงสิ่งที่สามารถทำได้และไม่ควร;) และถ้าฉันเปลี่ยนที่นี่มันไม่ใช่รหัสที่ฉันใช้ในการผลิตอีกต่อไป ... มันเป็นหนึ่งในสิ่งที่บอบบาง เมื่อมันได้ผลคุณจะไม่มีวันแตะต้องเลยเพราะมันจะล้มลงในแบบที่ไม่มีใครทำนายได้
PlasmaHH

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

สวัสดีพลาสม่า ฉันได้ทำการล้างซอร์สโค้ด + เสร็จสิ้นแล้วโดยการรวบรวมและเรียกใช้เอาต์พุต กรุณาตรวจสอบการแก้ไขของฉัน ฉันหวังว่านี่จะโอเคสำหรับคุณ คำตอบมีค่ามากกว่านี้หรือไม่? อย่างไรก็ตามค่าใช้จ่ายของมาโครยังคงแย่มาก! ขอให้มีความสุขในวันนี้ :-) ไชโย
olibre

2

โซลูชันต่อไปนี้ใช้พื้นฐานของ std::array<std::string,N>สำหรับ enum ที่กำหนด

สำหรับenumการstd::stringแปลงเราก็สามารถเปลี่ยน enum เป็นsize_tและค้นหาสตริงจากอาร์เรย์ การดำเนินการคือ O (1) และไม่ต้องการการจัดสรรฮีป

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

สำหรับstd::stringการenumแปลงเราจะต้องทำการค้นหาเชิงเส้นผ่านอาร์เรย์และส่งดัชนีอาร์เรย์ไปที่enumแปลงเราจะต้องทำให้การค้นหาเส้นตรงในอาร์เรย์และออกเสียงดัชนีอาร์เรย์

ลองที่นี่พร้อมกับตัวอย่างการใช้งาน: http://coliru.stacked-crooked.com/a/e4212f93bee65076

แก้ไข: ทำใหม่โซลูชันของฉันเพื่อให้ Enum ที่กำหนดเองสามารถใช้ภายในชั้นเรียนได้


ขอบคุณสำหรับคำตอบที่น่าสนใจ โปรดทำใหม่ข้อเสนอของคุณเพื่อใช้แมโครของคุณภายในชั้นเรียน ดูcoliru.stacked-crooked.com/a/00d362eba836d04bยิ่งกว่านั้นลองใช้constexprและnoexeptคำหลักเมื่อเป็นไปได้ Cheers :-)
olibre

คำถามไม่ได้ระบุสิ่งที่ต้องการนี้
FKaria

คำถามมีการอัปเดต (ดูตัวอย่าง) ข้อกำหนดอื่นอีกสองข้อ: (1) ประเภทการสนับสนุนของ enum และ (2) ค่าอาจแตกต่างจากลำดับ 0, 1, 2 ...
olibre

ฉันทำใหม่โซลูชันของฉันเพื่อให้สามารถใช้ในชั้นเรียน ฉันไม่ได้หาวิธีทำให้ค่าแตกต่างจาก 0,1,2 ..
FKaria

สวัสดี FKaria ขอบคุณมากสำหรับการทำใหม่ของคุณ ฉันทำการเปลี่ยนแปลงบางอย่างเพื่อสนับสนุน enums หลายอย่างภายในชั้นเรียนเดียวกันและเพื่อรองรับenum class X : Typeรูปแบบ โปรดตรวจสอบการมีส่วนร่วมของฉัน: coliru.stacked-crooked.com/a/b02db9190d3491a3คุณคิดอย่างไรเกี่ยวกับการเปลี่ยนแปลงของฉัน คุณมีความคิดที่จะสนับสนุนค่าที่กำหนดภายใน enum หรือไม่? ตัวอย่างenum E{A=3, B=6, C=A-B};Cheers
olibre

2

สรุปสาระสำคัญนี้ให้การทำแผนที่ง่ายขึ้นอยู่กับแม่แบบตัวแปร C ++

นี่คือ C ++ เวอร์ชันย่อ 17 ของแผนที่แบบอิงจากส่วนสำคัญ :

#include <cstring> // http://stackoverflow.com/q/24520781

template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                  "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
            ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
            ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
            ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};

template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

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

enum class fasion {
    fancy,
    classic,
    sporty,
    emo,
    __last__ = emo,
    __unknown__ = -1
};

#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
    NAME(fancy)
    NAME(classic)
    NAME(sporty)
    NAME(emo)
}

template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
    typedef decltype(K) key_t;
    typedef decltype(V) name_t;
    static constexpr key_t  key = K; // enum id value
    static constexpr name_t val = V; // enum id name
};

typedef names<fasion,
    _<fasion::fancy, name::fancy>,
    _<fasion::classic, name::classic>,
    _<fasion::sporty, name::sporty>,
    _<fasion::emo, name::emo>,
    _<fasion::__unknown__, nullptr>
> fasion_names;

map<KeyValues...>สามารถนำมาใช้ในทั้งสองทิศทาง:

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

ตัวอย่างนี้มีอยู่ในgodbolt.org

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

ผลลัพธ์จาก gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
วิธีการเขียนโปรแกรมเมตาน่าสนใจมาก ฉันพยายามทำให้คำตอบง่ายขึ้นเล็กน้อย (โดยไม่ต้องพึ่งพาลิงก์ Gist) เพื่อให้กระชับและเข้าใจได้ในที่สุดฉันก็ได้แก้ไขคำตอบของคุณเป็นจำนวนมาก คุณยังเห็นด้วยกับการเปลี่ยนแปลงของฉันหรือไม่? Cheers ;-)
olibre

2

ฉันได้รับความผิดหวังจากปัญหานี้มานานเกินไปพร้อมกับปัญหาของการแปลงชนิดเป็นสตริงในวิธีที่เหมาะสม อย่างไรก็ตามสำหรับปัญหาล่าสุดฉันรู้สึกประหลาดใจกับวิธีแก้ไขที่อธิบายไว้ในมันเป็นไปได้ไหมที่จะพิมพ์ประเภทของตัวแปรในมาตรฐาน C ++ โดยใช้ความคิดจากฉันจะได้รับชื่อประเภท C ++ ในทาง constexpr? . การใช้เทคนิคนี้สามารถสร้างฟังก์ชันแบบอะนาล็อกเพื่อรับค่า enum เป็นสตริงได้:

#include <iostream>
using namespace std;

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    constexpr static_string(const char(&a)[N]) noexcept
        : p_(a)
        , sz_(N - 1)
    {}

    constexpr static_string(const char* p, std::size_t N) noexcept
        : p_(p)
        , sz_(N)
    {}

    constexpr const char* data() const noexcept { return p_; }
    constexpr std::size_t size() const noexcept { return sz_; }

    constexpr const_iterator begin() const noexcept { return p_; }
    constexpr const_iterator end()   const noexcept { return p_ + sz_; }

    constexpr char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 37, p.size() - 37 - 7);
#endif

}

namespace details
{
    template <class Enum>
    struct EnumWrapper
    {
        template < Enum enu >
        static static_string name()
        {
#ifdef __clang__
            static_string p = __PRETTY_FUNCTION__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
            static_string p = __FUNCSIG__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
        }
    };
}

/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
    return details::EnumWrapper<Enum>::template name<enu>();
}

enum class Color
{
    Blue = 0,
    Yellow = 1
};


int main() 
{
    std::cout << "_" << typeName<Color>() << "_"  << std::endl;
    std::cout << "_" << enumName<Color, Color::Blue>() << "_"  << std::endl;
    return 0;
}

รหัสข้างต้นได้รับการทดสอบบน Clang เท่านั้น (ดูhttps://ideone.com/je5Quv ) และ VS2015 แต่ควรปรับให้เข้ากับคอมไพเลอร์อื่น ๆ โดยทำการเล่นซอกับค่าคงที่จำนวนเต็ม แน่นอนว่ามันยังคงใช้มาโครภายใต้ประทุน แต่อย่างน้อยก็ไม่จำเป็นต้องเข้าถึงการใช้งาน enum


สิ่งนี้ล้มเหลวด้วย g ++ 6.3.0 และ C ++ 14
einpoklum

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

2

ฉันใช้ความคิดจาก @antron และใช้มันต่างกัน: สร้างคลาส enumจริงชั้น

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

นี่ไม่ใช่ข้อ จำกัด ที่แท้จริง - เพียงว่าฉันไม่ได้ใช้ค่า ad-hoc enum หากจำเป็นต้องมีสิ่งนี้สามารถแทนที่การค้นหาแบบเวกเตอร์ด้วยการใช้สวิตช์ / ตัวพิมพ์แบบดั้งเดิม

โซลูชันใช้ c ++ 17 บางตัวสำหรับตัวแปรอินไลน์ แต่สามารถหลีกเลี่ยงได้อย่างง่ายดายหากจำเป็น นอกจากนี้ยังใช้boost: trimเนื่องจากความเรียบง่าย

สิ่งสำคัญที่สุดคือมันใช้รหัสเพียง 30 บรรทัดและไม่มีมาโครเวทย์มนตร์ดำ รหัสด้านล่าง มันหมายถึงการใส่ในส่วนหัวและรวมอยู่ในหลายโมดูลรวบรวม

สามารถใช้แบบเดียวกับที่แนะนำไว้ก่อนหน้าในชุดข้อความนี้:

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

กรุณาแจ้งให้เราทราบหากสิ่งนี้มีประโยชน์และจะปรับปรุงได้อย่างไร


#include <boost/algorithm/string.hpp>   
struct EnumSupportBase {
  static std::vector<std::string> split(const std::string s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector<std::string> tokens;
    while (std::getline(ss, item, delim)) {
        auto pos = item.find_first_of ('=');
        if (pos != std::string::npos)
            item.erase (pos);
        boost::trim (item);
        tokens.push_back(item);
    }
    return tokens;
  }
};
#define ENUM(EnumName, Underlying, ...) \
    enum class EnumName : Underlying { __VA_ARGS__, _count }; \
    struct EnumName ## Support : EnumSupportBase { \
        static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
        static constexpr const char* get_name(EnumName enum_value) { \
            int index = (int)enum_value; \
            if (index >= (int)EnumName::_count || index < 0) \
               return "???"; \
            else \
               return _token_names[index].c_str(); \
        } \
    }; \
    inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
        return os << EnumName##Support::get_name(es); \
    } 

2

ตราบใดที่คุณไม่เป็นไรเมื่อเขียน.h/.cppคู่แยกสำหรับแต่ละ enum ที่สืบค้นได้โซลูชันนี้จะทำงานกับไวยากรณ์และความสามารถเกือบเหมือนกันกับ c ++ enum ปกติ:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

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

.cppไฟล์ 3 สายของสำเร็จรูป:

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

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

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

รหัส

โซลูชันนี้ต้องการไฟล์ต้นฉบับ 2 ไฟล์:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...และ

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

คำอธิบาย

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

เมื่อETRAITSได้รับการประเมินในบริบทของEnumTraits.inlมันจะขยายออกไปเป็นคำนิยามสมาชิกคงที่สำหรับEnumTraits<>ชั้นเรียน

EDECLแปลงแมโครสมาชิกแต่ละคนลงใน enum ค่ารายการ initializer ซึ่งต่อมาได้รับการผ่านเข้าสู่ตัวสร้างสมาชิกในการสั่งซื้อเพื่อเติมข้อมูล enum

EnumInitGuardชั้นถูกออกแบบมาเพื่อใช้ enum initializer ค่าแล้วยุบ - ออกจากรายการที่บริสุทธิ์ของข้อมูล enum

ประโยชน์ที่ได้รับ

  • c++- เหมือนไวยากรณ์
  • ทำงานเหมือนกันสำหรับทั้งสองenumและenum class(* เกือบ)
  • ใช้งานได้กับenumประเภทใด ๆ ที่เป็นตัวเลขพื้นฐาน
  • ใช้งานได้กับenumประเภทที่มีค่าเริ่มต้นโดยอัตโนมัติอย่างชัดเจนและแยกส่วน
  • ใช้งานได้สำหรับการเปลี่ยนชื่อจำนวนมาก (การเชื่อมโยงภายในที่เก็บรักษาไว้)
  • สัญลักษณ์พรีโปรเซสเซอร์เพียง 5 ตัว (3 ตัวเท่านั้น)

* ในทางตรงกันข้ามกับenumsinitializers ในenum classประเภทที่อ้างอิงค่าอื่น ๆ จาก enum เดียวกันจะต้องมีค่าที่ผ่านการรับรองโดยสมบูรณ์

Disbenefits

  • ต้องการ.h/.cppคู่แยกต่างหากสำหรับแต่ละข้อสงสัยenum
  • ขึ้นอยู่กับความซับซ้อนmacroและincludeเวทมนตร์
  • ข้อผิดพลาดทางไวยากรณ์เล็กน้อยเกิดการระเบิดเป็นข้อผิดพลาดที่ใหญ่กว่ามาก
  • การกำหนดclassหรือการกำหนดnamespaceขอบเขต enums เป็นเรื่องไม่สำคัญ
  • ไม่มีการเริ่มต้นเวลารวบรวม

ความคิดเห็น

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

#ifndef ENUM_INCLUDE_MULTIบล็อกที่ด้านบนของไฟล์ส่วนหัวคือการแกล้งเล็กน้อยที่ได้อาจหดตัวลงไปในแมโครหรืออะไร แต่ก็พอขนาดเล็กที่จะอยู่กับที่ในปัจจุบันมีขนาด

การประกาศขอบเขต enum ของเนมสเปซต้องการให้ enum ถูกประกาศไปข้างหน้าภายในขอบเขตของเนมสเปซก่อนจากนั้นกำหนดไว้ในเนมสเปซส่วนกลาง นอกจากนี้ค่าเริ่มต้นของ enum ใด ๆ ที่ใช้ค่าของ enum เดียวกันจะต้องมีค่าที่ผ่านการรับรองโดยสมบูรณ์

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

2

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

สิ่งที่ฉันต้องการเปิดใช้งานนั้นเทียบเท่า

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

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

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

ซึ่งควรพิมพ์

ONE
TWO
13

ฉันไม่ใช่แฟนของมาโคร อย่างไรก็ตามเว้นแต่ว่า c ++ จะรองรับการแปลง enums เป็นสตริงอย่างใดอย่างหนึ่งต้องใช้การสร้างรหัสและ / หรือมาโครบางประเภท (และฉันสงสัยว่าจะเกิดขึ้นเร็วเกินไป) ฉันใช้มาโคร X :

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

ส่วนใหญ่เป็นการกำหนดและยกเลิกการกำหนดสัญลักษณ์ที่ผู้ใช้จะส่งผ่านเป็นพารามิเตอร์ไปยัง X-marco ผ่านการรวม การใช้งานเป็นเช่นนี้

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

การสาธิตสด

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

หลังจากเขียนนี้ฉันก็รู้ว่ามันค่อนข้างคล้ายกับคำตอบของการพูด บางทีฉันอาจจะอ่านมันมาก่อนและอาจเป็นแหล่งที่มาของแรงบันดาลใจ ฉันมักจะล้มเหลวในการทำความเข้าใจ X-macros จนกว่าฉันจะเขียนเอง;)


1

โซลูชันที่ใช้ enum ภายในคลาส / struct (ค่าเริ่มต้น struct กับสมาชิกสาธารณะ) และตัวดำเนินการที่โอเวอร์โหลด:

struct Color
{
    enum Enum { RED, GREEN, BLUE };
    Enum e;

    Color() {}
    Color(Enum e) : e(e) {}

    Color operator=(Enum o) { e = o; return *this; }
    Color operator=(Color o) { e = o.e; return *this; }
    bool operator==(Enum o) { return e == o; }
    bool operator==(Color o) { return e == o.e; }
    operator Enum() const { return e; }

    std::string toString() const
    {
        switch (e)
        {
        case Color::RED:
            return "red";
        case Color::GREEN:
            return "green";
        case Color::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
};

จากภายนอกดูคล้ายกับคลาส enum:

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

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

สิ่งนี้จะส่งออก "สีแดง 1 2" คุณอาจโอเวอร์โหลด << เพื่อทำให้สตริงสีน้ำเงินแสดงผล (แม้ว่ามันอาจทำให้เกิดความกำกวมดังนั้นจึงเป็นไปไม่ได้) แต่มันจะไม่ทำงานกับ Color :: GREEN เพราะมันจะไม่แปลงเป็น Color โดยอัตโนมัติ

จุดประสงค์ของการมีการแปลงโดยปริยายเป็น Enum (ซึ่งแปลงโดยปริยายเป็น int หรือชนิดที่กำหนด) จะสามารถทำได้:

Color color;
switch (color) ...

มันใช้งานได้ แต่มันก็หมายความว่ามันใช้ได้เช่นกัน:

int i = color;

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

โซลูชันอื่นจะเกี่ยวข้องกับการใช้คลาส enum จริงและสมาชิกแบบสแตติก:

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

    //same as previous...
};

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

แต่ฉันคิดว่ามันง่ายกว่าและดูดีกว่ารหัสอื่น ๆ ที่ฉันเคยเห็น นอกจากนี้ยังมีศักยภาพในการเพิ่มฟังก์ชันการทำงานซึ่งทุกคนสามารถกำหนดขอบเขตภายในชั้นเรียน

แก้ไข : งานนี้และส่วนใหญ่สามารถรวบรวมได้ก่อนดำเนินการ:

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

    constexpr Color() : e(Enum::RED) {}
    constexpr Color(Enum e) : e(e) {}

    constexpr bool operator==(Enum o) const { return e == o; }
    constexpr bool operator==(Color o) const { return e == o.e; }
    constexpr operator Enum() const { return e; }

    Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
    Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }

    std::string toString() const
    {
        switch (e)
        {
        case Enum::RED:
            return "red";
        case Enum::GREEN:
            return "green";
        case Enum::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
private:
    const Enum e;
};

นี้เป็นที่น่าสนใจมาก :-) case Enum::RED: return "red";แต่รุ่นปัจจุบันของคุณหมายถึงคุณต้องเขียนสิ่งที่ตนเอง คำถามเกี่ยวกับการทำให้สิ่งนี้เป็นอัตโนมัติโดยคอมไพเลอร์ (ณ เวลารวบรวม) ความคิดของคำถามคือการเปลี่ยนหรือเพิ่ม enum toString()เฉพาะค่าโดยไม่ต้องมีการปรับปรุงสิ่งที่ คุณเห็นไหม? ขอบคุณ
olibre

1

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

#include <vector>
#include <string>
#include <regex>
#include <iterator>

std::vector<std::string> split(const std::string& s, 
                               const std::regex& delim = std::regex(",\\s*"))
{
    using namespace std;
    vector<string> cont;
    copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), 
         regex_token_iterator<string::const_iterator>(),
         back_inserter(cont));
    return cont;
}

#define EnumType(Type, ...)     enum class Type { __VA_ARGS__ }

#define EnumStrings(Type, ...)  static const std::vector<std::string> \
                                Type##Strings = split(#__VA_ARGS__);

#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
                                EnumStrings(Type, __VA_ARGS__)

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

EnumToString(MyEnum, Red, Green, Blue);

ขอบคุณ Malem สำหรับความคิดสร้างสรรค์ของคุณ ฉันได้แก้ไขคำตอบของคุณเพื่อปรับปรุงความสามารถในการอ่าน ฉันหวังว่าคุณจะชอบการเปลี่ยนแปลงของฉัน โปรดปรับปรุงคำตอบของคุณต่อ: (1) ขยายส่วน"ตัวอย่างการใช้งาน"ด้วยบางสิ่งเช่นauto name = MyEnumStrings["Red"];- (2) ทำไมคุณถึงใช้งานenum class? - (3) คุณสนับสนุนenum class MyEnum : char { Red, Green, Blue };หรือไม่ - (4) อธิบายฟังก์ชั่นsplit()- (5) คุณต้องการพารามิเตอร์const std::regex& delimหรือไม่ - (6) สิ่งที่เกี่ยวกับการสร้างMyEnumStringsในเวลารวบรวม? => คุณสามารถใช้constexpr? ... ไชโย :-)
olibre

ฉันชอบวิธีนี้มาก สั้นจริงๆและเข้าใจง่าย
Anton Holmberg

1

แก้ไข: ตรวจสอบด้านล่างสำหรับรุ่นที่ใหม่กว่า

ดังที่ได้กล่าวไว้ข้างต้นN4113เป็นทางออกสุดท้ายสำหรับเรื่องนี้ แต่เราจะต้องรอนานกว่าหนึ่งปีจึงจะเห็นมันออกมา

ในขณะเดียวกันหากคุณต้องการฟีเจอร์ดังกล่าวคุณจะต้องหันไปใช้เทมเพลต "ง่าย" และเวทย์มนตร์ preprocessor

แจงนับ

template<typename T>
class Enum final
{
    const char* m_name;
    const T m_value;
    static T m_counter;

public:
    Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}

    const T value() const {return m_value;}
    const char* name() const {return m_name;}
};

template<typename T>
T Enum<T>::m_counter = 0;

#define ENUM_TYPE(x)      using Enum = Enum<x>;
#define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
#define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);

การใช้

#include <iostream>

//the initialization order should be correct in all scenarios
namespace Level
{
    ENUM_TYPE(std::uint8)
    ENUM(OFF)
    ENUM(SEVERE)
    ENUM(WARNING)
    ENUM(INFO, 10)
    ENUM(DEBUG)
    ENUM(ALL)
}

namespace Example
{
    ENUM_TYPE(long)
    ENUM(A)
    ENUM(B)
    ENUM(C, 20)
    ENUM(D)
    ENUM(E)
    ENUM(F)
}

int main(int argc, char** argv)
{
    Level::Enum lvl = Level::WARNING;
    Example::Enum ex = Example::C;
    std::cout << lvl.value() << std::endl; //2
    std::cout << ex.value() << std::endl; //20
}

คำอธิบายง่ายๆ

Enum<T>::m_counterถูกตั้งค่าเป็น 0 ภายในการประกาศเนมสเปซแต่ละรายการ
( ใครบางคนสามารถชี้ให้ฉันเห็นว่า ^^ พฤติกรรมนี้ ^^ ถูกกล่าวถึงในมาตรฐานหรือไม่ )
เวทมนต์ผู้ประมวลผลล่วงหน้าทำการประกาศของผู้แจกแจงโดยอัตโนมัติ

ข้อเสีย

  • มันไม่ใช่enumประเภทที่แท้จริงดังนั้นจึงไม่สามารถส่งเสริมได้
  • ไม่สามารถใช้ในกรณีสวิตช์ได้

ทางเลือกการแก้ปัญหา

นี้หนึ่งเสียสละหมายเลขบรรทัด (ไม่จริง) แต่สามารถนำมาใช้ในกรณีที่สวิทช์

#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x)      constexpr type x{__LINE__,#x}

template<typename T>
struct Enum final
{
    const T value;
    const char* name;

    constexpr operator const T() const noexcept {return value;}
    constexpr const char* operator&() const noexcept {return name;}
};

คหบดี

#line 0ขัดแย้งกับ-pedanticGCC และเสียงดังกราว

วิธีแก้ปัญหา

ทั้งสองเริ่มต้นที่และลบจาก#line 1 1 หรือไม่ได้ใช้ และในขณะที่เราอยู่ที่นี่หลีกเลี่ยง VC ++ ที่ค่าใช้จ่ายทั้งหมดมันเป็นเรื่องตลกของคอมไพเลอร์เสมอ__LINE__
-pedantic

การใช้

#include <iostream>

namespace Level
{
    ENUM_TYPE(short);
    #line 0
    ENUM(OFF);
    ENUM(SEVERE);
    ENUM(WARNING);
    #line 10
    ENUM(INFO);
    ENUM(DEBUG);
    ENUM(ALL);
    #line <next line number> //restore the line numbering
};

int main(int argc, char** argv)
{
    std::cout << Level::OFF << std::endl;   // 0
    std::cout << &Level::OFF << std::endl;  // OFF

    std::cout << Level::INFO << std::endl;  // 10
    std::cout << &Level::INFO << std::endl; // INFO

    switch(/* any integer or integer-convertible type */)
    {
    case Level::OFF:
        //...
        break;

    case Level::SEVERE:
        //...
        break;

    //...
    }

    return 0;
}

การใช้งานและการใช้งานจริง

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

อ้างอิงด่วน

#line lineno - cppreference.com


0

ฉันเขียนห้องสมุดเพื่อแก้ปัญหานี้ทุกอย่างเกิดขึ้นในเวลารวบรวมยกเว้นการรับข้อความ

การใช้งาน:

ใช้แมโครDEF_MSGเพื่อกำหนดแมโครและคู่ข้อความ:

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

CODE_OKเป็นแมโครที่จะใช้และ"OK!"เป็นข้อความที่เกี่ยวข้อง

ใช้get_message()หรือgm()เพื่อรับข้อความ:

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

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

ข้อความที่กำหนดไว้ล่วงหน้า:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

โครงการ: libcodemsg


ห้องสมุดไม่ได้สร้างข้อมูลเพิ่มเติม ทุกอย่างเกิดขึ้นในเวลารวบรวม ในmessage_def.hมันสร้างenumเรียกMSG_CODE; ในการจะสร้างตัวแปรสตริงถือทั้งหมดในmessage_def.cstatic const char* _g_messages[]

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

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

MSG_CODE ret = foo();

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

สิ่งที่ฉันชอบการออกแบบนี้ก็คือคุณสามารถจัดการคำจำกัดความข้อความในไฟล์ที่แตกต่างกัน


ฉันพบวิธีแก้ปัญหาสำหรับคำถามนี้ดูดีขึ้นมาก


สวัสดี Madwyn ขอบคุณสำหรับความคิดของคุณ แต่มันทำงานอย่างไร ค่าโสหุ้ยคืออะไร? (ศูนย์ค่าใช้จ่ายเป็นศูนย์หรือมันจะสร้างข้อมูลพิเศษ?) ข้อเสนอของคุณดูเหมือนจะดี แต่น่าเสียดายDEF_MSGที่ต้องมีการใช้ / อัปเดต / ปรับปรุงหนึ่งคำสั่งสำหรับแต่ละenumค่า: - / และนี่คือสิ่งที่เราต้องการหยุดการทำเช่นนั้น ... ไชโย
ol

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

ขอบคุณสำหรับคำอธิบายที่ต่อท้ายในคำตอบของคุณ :-) lib ของคุณใช้ได้ แต่ไม่สามารถใช้กับหลาย enums: - / การสนับสนุนของenum class(C ++ 11)คืออะไร คุณสามารถใช้constexprเพื่อ จำกัด_g_messagesเวลาทำงาน สนับสนุนหลายenumประเภท (หลีกเลี่ยง_g_messages) โดยใช้เมตาการเขียนโปรแกรม (ชนิดลำเลียง {enum ชนิด enum มูลค่า}) หรืออาจจะตัวแปรแม่แบบ (C ++ 14) ฉันคิดว่า lib ของคุณไม่ตรงกับข้อกำหนดของ C ++ 11/14/17 คุณคิดอย่างไร? Cheers ;-)
olibre

1
ขอบคุณสำหรับการติดตาม ฉันเรียนรู้สิ่งใหม่วันนี้! ตัวแปรคลาส enum และเทมเพลตดูดี ฉันคิดว่าคำตอบของฉันคือ "ปิดหัวข้อ" เล็กน้อยเนื่องจากเป็นรสซี
Madwyn

0
#define ENUM_MAKE(TYPE, ...) \
        enum class TYPE {__VA_ARGS__};\
        struct Helper_ ## TYPE { \
            static const String& toName(TYPE type) {\
                int index = static_cast<int>(type);\
                return splitStringVec()[index];}\
            static const TYPE toType(const String& name){\
                static std::unordered_map<String,TYPE> typeNameMap;\
                if( typeNameMap.empty() )\
                {\
                    const StringVector& ssVec = splitStringVec();\
                    for (size_t i = 0; i < ssVec.size(); ++i)\
                        typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
                }\
                return typeNameMap[name];}\
            static const StringVector& splitStringVec() {\
                static StringVector typeNameVector;\
                if(typeNameVector.empty()) \
                {\
                    typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
                    for (auto& name : typeNameVector)\
                    {\
                        name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
                        name = String(#TYPE) + "::" + name;\
                    }\
                }\
                return typeNameVector;\
            }\
        };


using String = std::string;
using StringVector = std::vector<String>;

   StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
    {
        StringVector ret;
        // Pre-allocate some space for performance
        ret.reserve(maxSplits ? maxSplits+1 : 10);    // 10 is guessed capacity for most case

        unsigned int numSplits = 0;

        // Use STL methods 
        size_t start, pos;
        start = 0;
        do 
        {
            pos = str.find_first_of(delims, start);
            if (pos == start)
            {
                // Do nothing
                start = pos + 1;
            }
            else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
            {
                // Copy the rest of the string
                ret.push_back( str.substr(start) );
                break;
            }
            else
            {
                // Copy up to delimiter
                ret.push_back( str.substr(start, pos - start) );

                if(preserveDelims)
                {
                    // Sometimes there could be more than one delimiter in a row.
                    // Loop until we don't find any more delims
                    size_t delimStart = pos, delimPos;
                    delimPos = str.find_first_not_of(delims, delimStart);
                    if (delimPos == String::npos)
                    {
                        // Copy the rest of the string
                        ret.push_back( str.substr(delimStart) );
                    }
                    else
                    {
                        ret.push_back( str.substr(delimStart, delimPos - delimStart) );
                    }
                }

                start = pos + 1;
            }
            // parse up to next real data
            start = str.find_first_not_of(delims, start);
            ++numSplits;

        } while (pos != String::npos);



        return ret;
    }

ตัวอย่าง

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


    MY_TEST s1 = MY_TEST::MY_1;
    MY_TEST s2 = MY_TEST::MY_2;
    MY_TEST s3 = MY_TEST::MY_3;

    String z1 = Helper_MY_TEST::toName(s1);
    String z2 = Helper_MY_TEST::toName(s2);
    String z3 = Helper_MY_TEST::toName(s3);

    MY_TEST q1 = Helper_MY_TEST::toType(z1);
    MY_TEST q2 = Helper_MY_TEST::toType(z2);
    MY_TEST q3 = Helper_MY_TEST::toType(z3);

มาโคร ENUM_MAKE อัตโนมัติจะสร้าง 'คลาส enum' และคลาสตัวช่วยด้วย 'ฟังก์ชั่นการสะท้อน enum'

เพื่อลดข้อผิดพลาดในครั้งเดียวทุกอย่างถูกกำหนดด้วย ENUM_MAKE เพียงอันเดียว

ข้อได้เปรียบของรหัสนี้ถูกสร้างขึ้นโดยอัตโนมัติสำหรับการสะท้อนและมองใกล้ที่รหัสแมโครรหัสที่เข้าใจง่าย 'enum to string', 'string to enum' ประสิทธิภาพทั้งสองคืออัลกอริทึม O (1)

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


ในขณะที่รหัสนี้อาจตอบคำถามมันจะดีกว่าที่จะอธิบายวิธีการแก้ปัญหาโดยไม่ต้องแนะนำคนอื่น ๆ และทำไมต้องใช้มัน คำตอบแบบรหัสเท่านั้นไม่มีประโยชน์ในระยะยาว
JAL

เฮ้พวกฉันขอโทษฉันพูดอังกฤษไม่เก่ง
desperado_98

มาโคร ENUM_MAKE อัตโนมัติจะสร้าง 'คลาส enum' และคลาสตัวช่วยด้วย 'ฟังก์ชั่นการสะท้อน enum' / เพื่อลดข้อผิดพลาดในครั้งเดียวทุกอย่างถูกกำหนดด้วย ENUM_MAKE เพียงอันเดียว ข้อได้เปรียบของรหัสนี้ถูกสร้างขึ้นโดยอัตโนมัติสำหรับการสะท้อนและมองใกล้ที่รหัสแมโครรหัสที่เข้าใจง่าย 'enum to string', 'string to enum' ประสิทธิภาพทั้งสองคืออัลกอริทึม O (1) ข้อเสียคือเมื่อใช้ครั้งแรกคลาสผู้ช่วยสำหรับเวกเตอร์สตริง enum relection และแผนที่จะเริ่มต้น แต่ถ้าคุณต้องการคุณจะได้รับการเริ่มต้นด้วย
desperado_98

สวัสดี desperado_98 ขอขอบคุณสำหรับการสนับสนุนของคุณ. โปรดแก้ไขคำตอบของคุณและใส่ไว้ในเนื้อหาความคิดเห็นของคุณ constexprคอมไพเลอร์สามารถคำนวณตัวอย่างของคุณที่รวบรวมเวลาถ้าคุณใช้บางเทคนิคเมตาและการเขียนโปรแกรม ฉันหมายถึงฟังก์ชั่นtoName()และtoType()สามารถประเมินได้ในระหว่างการรวบรวมและไม่ในระหว่างการดำเนินการ (เวลาทำงาน) โปรดใช้สไตล์ C ++ 11/14/17 ในคำตอบของคุณ Cheers ;-)
olibre

นอกจากนี้: แมโครของคุณเข้ากันได้กับ enum class MyEnum : short { A, B, C };หรือไม่
olibre

0

โซลูชันของฉันไม่มีการใช้มาโคร

ข้อดี:

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

ข้อเสีย:

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

ดังนั้น ... จนกระทั่งถึงวันที่ C ++ จะใช้งานฟังก์ชัน C # Enum.Parse ฉันจะติดอยู่กับสิ่งนี้:

            #include <unordered_map>

            enum class Language
            { unknown, 
                Chinese, 
                English, 
                French, 
                German
                // etc etc
            };

            class Enumerations
            {
            public:
                static void fnInit(void);

                static std::unordered_map <std::wstring, Language> m_Language;
                static std::unordered_map <Language, std::wstring> m_invLanguage;

            private:
                static void fnClear();
                static void fnSetValues(void);
                static void fnInvertValues(void);

                static bool m_init_done;
            };

            std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
            std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();

            void Enumerations::fnInit()
            {
                fnClear();
                fnSetValues();
                fnInvertValues();
            }

            void Enumerations::fnClear()
            {
                m_Language.clear();
                m_invLanguage.clear();
            }

            void Enumerations::fnSetValues(void)
            {   
                m_Language[L"unknown"] = Language::unknown;
                m_Language[L"Chinese"] = Language::Chinese;
                m_Language[L"English"] = Language::English;
                m_Language[L"French"] = Language::French;
                m_Language[L"German"] = Language::German;
                // and more etc etc
            }

            void Enumerations::fnInvertValues(void)
            {
                for (auto it = m_Language.begin(); it != m_Language.end(); it++)
                {
                    m_invLanguage[it->second] = it->first;
                }
            }

            // usage -
            //Language aLanguage = Language::English;
            //wstring sLanguage = Enumerations::m_invLanguage[aLanguage];

            //wstring sLanguage = L"French" ;
            //Language aLanguage = Enumerations::m_Language[sLanguage];

0

ก็เป็นอีกทางเลือกหนึ่ง กรณีการใช้งานทั่วไปคือที่ที่คุณต้องการค่าคงที่สำหรับคำกริยา HTTP รวมถึงการใช้ค่าเวอร์ชันสตริง

ตัวอย่าง:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
    std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
    std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
    std::cout << "no" << std::endl;
  }

}

คลาสของ VERB:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};

  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
    // std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

1
เพื่อลดการใช้หน่วยความจำคุณอาจแทนที่สมาชิกโดยเพียงแค่const std::string text theStrings[v]อย่างไรก็ตามคำถามเกี่ยวกับคุณสมบัติจาก C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 เพื่อหลีกเลี่ยงการต้องเขียนคลาสดังกล่าวด้วยตนเอง: - /
olibre

0

คำตอบของฉันอยู่ที่นี่

คุณสามารถรับชื่อค่า enum และดัชนีเหล่านี้ได้พร้อมกันเป็นการลบสตริง

วิธีนี้ต้องการเพียงคัดลอกและวางและแก้ไขเล็กน้อย

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

enum class myenum
{
  one = 0,
  two,
  three,
};

deque<string> ssplit(const string &_src, boost::regex &_re)
{
  boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
  boost::sregex_token_iterator e;
  deque<string> tokens;
  while (it != e)
    tokens.push_back(*it++);
  return std::move(tokens);
}

int main()
{
  regex re(",");
  deque<string> tokens = ssplit("one,two,three", re);
  for (auto &t : tokens) cout << t << endl;
    getchar();
  return 0;
}

0

คุณสามารถใช้ไลบรารีรีเฟลคได้เช่นPonder :

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

0

(อะนาล็อกของhttps://stackoverflow.com/a/54967187/2338477แก้ไขเล็กน้อย)

นี่คือวิธีการแก้ปัญหาของฉันเองที่มีคาถากำหนดขั้นต่ำและการสนับสนุนการกำหนด Enum แต่ละรายการ

นี่คือไฟล์ส่วนหัว:

#pragma once
#include <string>
#include <map>
#include <regex>

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

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


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




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

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

    From string to enumeration:

       enumName value;

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

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

และนี่คือตัวอย่างโปรแกรมทดสอบ:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

ไฟล์ส่วนหัวรุ่นเดียวกันที่อัปเดตจะถูกเก็บไว้ที่นี่:

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


-5

แล้วสตรีมมิ่งโอเวอร์โหลดแบบธรรมดาล่ะ คุณยังคงต้องทำแผนที่หากคุณไม่ต้องการทำเวทย์มนตร์มาโคร แต่ฉันคิดว่ามันสะอาดกว่าโซลูชันดั้งเดิมของคุณ

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

5
1) มันสร้างการทำซ้ำที่มากยิ่งขึ้น 2) มันบังคับให้คุณใช้สตรีม
Karoly Horvath

6
-1 @dau_sama ขออภัย แต่วัตถุประสงค์ของการเหล่านี้enum สตริงคำถามที่เกิดขึ้นคือการหลีกเลี่ยงการบำรุงรักษาของ enum ทำแผนที่ / สตริง หากคุณคิดว่าคำตอบของคุณไม่ตรงกับวัตถุประสงค์โปรดพิจารณาลบคำตอบ ขอให้โชคดีในคำตอบถัดไปของคุณ;) ไชโย
olibre

-9

วิธีที่ง่ายที่สุด?
ใช้ Ada: Enumeration'Image( Value )ทำสิ่งที่คุณต้องการ หากคุณต้องการ C ++ จริงๆคุณสามารถลองส่งออกฟังก์ชัน:

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

4
คำถามนี้จะตอบคำถามได้อย่างไร? คำถามที่ระบุอย่างชัดเจนการแปลง Enum เป็นสตริงใน C ++ ที่ทันสมัย
Michael Choi

1
@MichaelChoi - แต่ก็ยังมีปัญหาในการใช้เครื่องมือที่เหมาะสมสำหรับงาน: เพียงเพราะ C + + เป็นทัวริงที่สมบูรณ์และดังนั้นจึงสามารถแก้ปัญหาที่แก้ไขได้ทั้งหมดไม่ได้หมายความว่าวิธีการแก้ปัญหาคือรวดเร็วบำรุงรักษาหรือมีประสิทธิภาพ การใช้ภาษาที่มีฟังก์ชั่นที่เหมาะสม / ต้องการและส่งออกมันเป็นโซลูชั่นที่ถูกต้อง
Shark8

3
ในประโยคแรกของคำถาม "คำถามนี้เกี่ยวกับการใช้คุณสมบัติ C ++ ใหม่" จากนั้น "[ฉันยังไม่พบใด ๆ ] วิธีที่หรูหราโดยใช้คุณสมบัติใหม่ C ++ 11, C ++ 14 หรือ C ++ 17" ผู้เขียนมองหาโซลูชัน C ++ อย่างชัดเจน คุณให้วิธีแก้ปัญหาใน Ada ดังนั้นคุณจึงไม่ได้ตอบคำถาม คุณกำลังแนะนำให้รวมการพึ่งพาที่แตกต่างกันอย่างสิ้นเชิงเพื่อแก้ไขสิ่งที่อาจไม่อยู่ในขอบเขตคำถาม
Michael Choi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.