enum to string trong C ++ 11 / C ++ 14 / C ++ 17 hiện đại và C ++ 20 trong tương lai


354

Trái với tất cả các câu hỏi tương tự khác, câu hỏi này là về việc sử dụng các tính năng mới của C ++.

Sau khi đọc nhiều câu trả lời, tôi vẫn chưa tìm thấy câu trả lời nào:

  • Cách thanh lịch sử dụng các tính năng mới của C ++ 11 , C ++ 14 hoặc C ++ 17
  • Hoặc một cái gì đó sẵn sàng để sử dụng trong Boost
  • Khác một cái gì đó được lên kế hoạch cho C ++ 20

Thí dụ

Một ví dụ thường tốt hơn một lời giải thích dài.
Bạn có thể biên dịch và chạy đoạn trích này trên Coliru .
( Một ví dụ khác cũng có sẵn)

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

Những ràng buộc

  • Xin vui lòng không trùng lặp vô giá trị của các câu trả lời khác hoặc liên kết cơ bản .
  • Vui lòng tránh trả lời dựa trên vĩ mô, hoặc cố gắng giảm #definechi phí tối thiểu nhất có thể.
  • Xin vui lòng không có hướng dẫn enum-> stringánh xạ.

Rất vui được có

  • Các enumgiá trị hỗ trợ bắt đầu từ một số khác 0
  • Hỗ trợ enumgiá trị âm
  • Hỗ trợ các enumgiá trị phân mảnh
  • Hỗ trợ class enum(C ++ 11)
  • Hỗ trợ class enum : <type>có bất kỳ sự cho phép <type>(C ++ 11)
  • Biên dịch thời gian biên dịch (không phải thời gian chạy) thành một chuỗi
    hoặc ít nhất là thực hiện nhanh vào thời gian chạy (ví dụ: std::mapkhông phải là một ý tưởng tuyệt vời ...)
  • constexpr (C ++ 11, sau đó thư giãn trong C ++ 14/17/20)
  • noexcept (C ++ 11)
  • Đoạn trích thân thiện C ++ 17 / C ++ 20

Một ý tưởng khả thi có thể là sử dụng các khả năng của trình biên dịch C ++ để tạo mã C ++ tại thời điểm biên dịch bằng các thủ thuật lập trình meta dựa trên variadic template classvà các constexprhàm ...


4
(có thể của chủ đề) nhìn vào blog liên quan đến Qt này. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Mô tả khả năng thay thế moc của Qt (trình biên dịch đối tượng meta) bằng cách sử dụng phản xạ C ++ (tiêu chuẩn đề xuất).
ioust5041 3/03/2015

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

1
Cá nhân tôi đã giải quyết vấn đề này bằng cách triển khai một thư viện tiện ích tiền xử lý nhỏ cho phép tôi lặp qua các đối số macro biến đổi và thực hiện một chức năng trên tất cả chúng. Tôi chuyển các giá trị enum dưới dạng đối số macro và tôi tự động tạo enum và mảng chuỗi thông qua bộ tiền xử lý. Bạn có thể cũng có thể làm điều này bằng cách sử dụng Boost Pre xử lý.
Vittorio Romeo

2
Có phải mọi thứ phải được giải quyết với C ++? Thật dễ dàng để tự động tạo mã cho biểu diễn chuỗi, chỉ cần một vài dòng mã.
Karoly Horvath

2
"Xin đừng cung cấp câu trả lời C vĩ mô dựa trên nếu có thể" tốt, trừ khi bạn sẵn sàng chờ cho C ++ 17 hầu như không có bất cứ điều gì có thể sử dụng, và nó không phải là xấu để khai báo sự đếm của bạn như là DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))trừ khi bạn phải maintaint sự tạo macro ... và imho đưa các trường hợp đó vào ngôn ngữ chỉ là một loại hack khác thay cho mẫu lai / macro mạnh hơn. Chúng ta không nên thêm tất cả các usecase hữu ích đó vào ngôn ngữ để có thể nói rằng macro không còn sử dụng được nữa.
PlasmaHH 3/03/2015

Câu trả lời:


43

Thư viện chỉ tiêu đề Magic Enum cung cấp phản xạ tĩnh cho enum (đến chuỗi, từ chuỗi, lặp) cho 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
};

Để biết thêm ví dụ, hãy kiểm tra kho lưu trữ tại nhà https://github.com/Neargye/magic_enum .

Nhược điểm ở đâu?

Thư viện này sử dụng hack dành riêng cho trình biên dịch (dựa trên __PRETTY_FUNCTION__/ __FUNCSIG__), hoạt động trên Clang> = 5, MSVC> = 15.3 và GCC> = 9.

Giá trị Enum phải nằm trong phạm vi [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Theo mặc định MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Nếu cần một phạm vi khác cho tất cả các loại enum theo mặc định, hãy xác định lại macro MAGIC_ENUM_RANGE_MINMAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINphải nhỏ hơn hoặc bằng hơn 0và phải lớn hơn INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXphải lớn hơn 0và phải nhỏ hơn INT16_MAX.

  • Nếu cần một phạm vi khác cho loại enum cụ thể, hãy thêm chuyên môn enum_range cho loại enum cần thiết.

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

Tại sao giới hạn phạm vi? Là nó để hạn chế một số loại độ sâu đệ quy, hoặc vì một số loại tìm kiếm tuyến tính thời gian biên dịch?
Cileier Cormier

Thật đáng kinh ngạc. Cảm ơn bạn! Nó thậm chí có thể hiệu quả nếu trình biên dịch đủ thông minh để đánh giá constexpr std :: mảng chỉ một lần. Rất rất tốt.
iestyn

87

(Cách tiếp cận của thư viện better_enums )

Có một cách để thực hiện enum thành chuỗi trong C ++ hiện tại giống như thế này:

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

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

Sử dụng:

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

Tất cả các hoạt động có thể được thực hiện constexpr. Bạn cũng có thể thực hiện đề xuất phản ánh C ++ 17 được đề cập trong câu trả lời của @ecatmur.

  • Chỉ có một vĩ mô. Tôi tin rằng đây là mức tối thiểu có thể, bởi vì #chuỗi chuỗi tiền xử lý ( ) là cách duy nhất để chuyển đổi mã thông báo thành chuỗi trong C ++ hiện tại.
  • Macro là không phô trương - các khai báo liên tục, bao gồm các trình khởi tạo, được dán vào một khai báo enum tích hợp. Điều này có nghĩa là chúng có cùng cú pháp và ý nghĩa như trong một enum tích hợp.
  • Sự lặp lại được loại bỏ.
  • Việc triển khai là tự nhiên và hữu ích nhất trong ít nhất C ++ 11, do constexpr. Nó cũng có thể được thực hiện để hoạt động với C ++ 98 + __VA_ARGS__. Nó chắc chắn là C ++ hiện đại.

Định nghĩa của macro có phần liên quan, vì vậy tôi trả lời điều này theo nhiều cách.

  • Phần lớn câu trả lời này là một triển khai mà tôi nghĩ là phù hợp với các hạn chế về không gian trên StackOverflow.
  • Ngoài ra còn có một bài viết CodeProject mô tả những điều cơ bản của việc thực hiện trong một hướng dẫn dài. [ Tôi có nên di chuyển nó ở đây? Tôi nghĩ rằng đó là quá nhiều cho một câu trả lời SO ].
  • Có một thư viện đầy đủ tính năng "Better Enums" thực hiện macro trong một tệp tiêu đề duy nhất. Nó cũng thực hiện Truy vấn thuộc tính loại N4428 , phiên bản hiện tại của đề xuất phản ánh C ++ 17 N4113. Vì vậy, ít nhất là đối với các enum được khai báo thông qua macro này, bạn có thể có phản xạ enum C ++ 17 được đề xuất ngay bây giờ, trong C ++ 11 / C ++ 14.

Thật đơn giản để mở rộng câu trả lời này cho các tính năng của thư viện - không có gì "quan trọng" bị bỏ lại ở đây. Tuy nhiên, nó khá tẻ nhạt và có những lo ngại về tính di động của trình biên dịch.

Tuyên bố miễn trừ trách nhiệm : Tôi là tác giả của cả bài viết CodeProject và thư viện.

Bạn có thể thử mã trong câu trả lời này , thư việnviệc triển khai N4428 trực tuyến trong Wandbox. Tài liệu thư viện cũng chứa một cái nhìn tổng quan về cách sử dụng nó như N4428 , giải thích phần enums của đề xuất đó.


Giải trình

Mã dưới đây thực hiện chuyển đổi giữa enums và chuỗi. Tuy nhiên, nó có thể được mở rộng để làm những việc khác nữa, chẳng hạn như lặp. Câu trả lời này kết thúc một enum trong a struct. structThay vào đó, bạn cũng có thể tạo ra một đặc điểm bên cạnh enum.

Chiến lược là tạo ra một cái gì đó như thế này:

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

Các vấn đề là:

  1. Chúng tôi sẽ kết thúc với một cái gì đó như {Red = 1, Green, Blue}là trình khởi tạo cho mảng giá trị. Đây không phải là C ++ hợp lệ, vì Redkhông phải là biểu thức có thể gán. Điều này được giải quyết bằng cách chuyển từng hằng số thành một loại Tcó toán tử gán, nhưng sẽ bỏ phép gán : {(T)Red = 1, (T)Green, (T)Blue}.
  2. Tương tự, chúng tôi sẽ kết thúc với {"Red = 1", "Green", "Blue"}tư cách là trình khởi tạo cho mảng tên. Chúng tôi sẽ cần phải cắt bỏ " = 1". Tôi không biết một cách tuyệt vời để làm điều này vào thời gian biên dịch, vì vậy chúng tôi sẽ trì hoãn việc này để chạy thời gian. Kết quả là, _to_stringsẽ không constexpr, nhưng _from_stringvẫn có thể constexpr, bởi vì chúng ta có thể coi khoảng trắng và dấu bằng là dấu chấm dứt khi so sánh với các chuỗi không được đánh giá.
  3. Cả hai điều trên đều cần một macro "ánh xạ" có thể áp dụng một macro khác cho mỗi phần tử trong __VA_ARGS__. Đây là tiêu chuẩn khá. Câu trả lời này bao gồm một phiên bản đơn giản có thể xử lý tối đa 8 yếu tố.
  4. Nếu macro thực sự khép kín, nó cần khai báo không có dữ liệu tĩnh yêu cầu một định nghĩa riêng. Trong thực tế, điều này có nghĩa là mảng cần điều trị đặc biệt. Có hai giải pháp khả thi: constexpr(hoặc chỉ const) mảng ở phạm vi không gian tên hoặc mảng thông thường trong các constexprhàm nội tuyến không tĩnh. Mã trong câu trả lời này là dành cho C ++ 11 và sử dụng cách tiếp cận trước đây. Bài viết CodeProject dành cho C ++ 98 và lấy cái sau.

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

Chương trình trên in Red, như bạn mong đợi. Có một mức độ an toàn về loại, vì bạn không thể tạo enum mà không khởi tạo nó và xóa một trong các trường hợp khỏi switchsẽ dẫn đến cảnh báo từ trình biên dịch (tùy thuộc vào trình biên dịch và cờ của bạn). Ngoài ra, lưu ý rằng "Red"đã được chuyển đổi thành một enum trong quá trình biên dịch.


Heya @mrhthepie, xin lỗi vì chỉnh sửa của bạn đã bị từ chối. Tôi chỉ thấy email về nó. Tôi sẽ kết hợp nó vào câu trả lời - cảm ơn vì lỗi này!
antron

điều đó thật tuyệt. Điều này cũng sẽ hoạt động nếu tôi muốn một enum của bit? Giống như tôi muốn một enum của BitFlags, mỗi cái được 1Uchuyển qua một số tiền?
dùng3240688

1
dường như có sự rò rỉ bộ nhớ trong _trimmed_names()mã bạn đã đăng ở đây ( new char[length + 1]nhưng bạn không đặt initializedthành đúng). tui bỏ lỡ điều gì vậy? Tôi không thấy vấn đề tương tự trong mã github của bạn.
dùng3240688

1
Nó được đặt thành true, nhưng bên ngoài ifnhánh (rò rỉ bộ nhớ ban đầu được bắt bởi @mrhthepie). Nên di chuyển nó vào bên trong ... Chỉnh sửa. Cảm ơn đã xem xét kỹ cả mã này và mã GH.
antron 7/11/2016

1
to_stringcó thể trả về a string_viewtừ C ++ 17, không yêu cầu kết thúc null và trở thành constexpr.
Yakk - Adam Nevraumont

74

Đối với C ++ 17 C ++ 20, bạn sẽ hứng thú với công việc của Nhóm nghiên cứu phản chiếu (SG7). Có một loạt các bài báo song song bao gồm từ ngữ ( P0194 ) và lý do, thiết kế và tiến hóa ( P0385 ). (Liên kết giải quyết bài báo mới nhất trong mỗi loạt.)

Kể từ P0194r2 (2016-10-15), cú pháp sẽ sử dụng reflexprtừ khóa được đề xuất :

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

Ví dụ: (được điều chỉnh từ nhánh phản xạ của Matus Choclik ):

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

Sự phản chiếu tĩnh đã thất bại trong việc đưa nó vào C ++ 17 (đúng hơn là vào bản dự thảo cuối cùng được trình bày tại cuộc họp tiêu chuẩn tháng 11 năm 2016 tại Issaquah) nhưng có sự tin tưởng rằng nó sẽ biến nó thành C ++ 20; từ báo cáo chuyến đi của Herb Sutter :

Cụ thể, nhóm nghiên cứu Reflection đã xem xét đề xuất phản xạ tĩnh được hợp nhất mới nhất và thấy nó đã sẵn sàng để tham gia các nhóm Evolution chính trong cuộc họp tiếp theo của chúng tôi để bắt đầu xem xét đề xuất phản xạ tĩnh thống nhất cho TS hoặc cho tiêu chuẩn tiếp theo.


2
@antron xin lỗi chỉnh sửa của bạn đã bị từ chối; Tôi đã chấp thuận nếu tôi thấy nó kịp thời. Tôi đã không thấy N4428 vì vậy cảm ơn vì đã cho những người đứng đầu.
ecatmur

3
Không có vấn đề, cảm ơn vì đã kết hợp nó. Tôi tự hỏi tại sao nó bị từ chối. Tôi thấy lý do soạn thảo "không làm cho nó chính xác hơn", nhưng rõ ràng nó chính xác hơn cho ngày nay.
antron

1
Cảm ơn :-) Tôi đã tách ví dụ cuối cùng để tránh thanh cuộn ngang. Thật đáng tiếc giá trị MyEnum::AAAkhông thể được thông qua như là đối số thứ hai của std::meta::get_enumerators_m: - /
olibre

1
Thực tế là một tác vụ đơn giản về mặt khái niệm như vậy đòi hỏi 3 cấp đối số khuôn mẫu lồng nhau là rất áp đảo. Tôi chắc chắn có lý do cụ thể, kỹ thuật cho nó. Nhưng điều đó không có nghĩa là kết quả cuối cùng là thân thiện với người dùng. Tôi yêu C ++ và mã có ý nghĩa với tôi. Nhưng 90% các lập trình viên khác mà tôi làm việc hàng ngày trốn tránh C ++ vì mã như thế này. Tôi thất vọng khi không thấy bất kỳ giải pháp tích hợp đơn giản hơn.
void.pulum

2
Có vẻ như ước tính hiện tại để đưa Reflection TS sắp tới vào tiêu chuẩn là C ++ 23 : Herbutter.com/2018/04/02/ trên
Tim Rae

25

Điều này tương tự với Yuri Finkelstein; nhưng không cần tăng. Tôi đang sử dụng bản đồ để bạn có thể gán bất kỳ giá trị nào cho enum, bất kỳ thứ tự nào.

Tuyên bố của lớp enum như:

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

Đoạn mã sau sẽ tự động tạo lớp enum và quá tải:

  • '+' '+ =' cho std :: chuỗi
  • '<<' cho các luồng
  • '~' chỉ để chuyển đổi thành chuỗi (Bất kỳ toán tử đơn nguyên nào cũng được, nhưng cá nhân tôi không thích nó cho rõ ràng)
  • '*' để có được số đếm

Không cần tăng, tất cả các chức năng cần thiết được cung cấp.

Mã số:

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

Thí dụ:

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
Chúng ta có thể có ngắt dòng bên trong định nghĩa vĩ mô này không?
einpoklum

1
Tôi đã thêm quá tải *để có được số lượng enum ... Tôi hy vọng bạn không phiền :-)
Peter VARGA

1
Có bất kỳ lý do nào việc triển khai này sử dụng std::maplập chỉ mục (O (log (n))) thay vì lập chỉ mục std::unordered_map(O (1)) không?
Sông Tam

1
Ngoài ra, tôi nghĩ rằng các phương thức nên được đánh dấu inlineđể bạn có thể khai báo enum trong các tệp tiêu đề như bình thường mà không gặp lỗi "nhiều định nghĩa" từ trình liên kết. (tuy nhiên không chắc đó thực sự là giải pháp sạch nhất / tốt nhất)
River Tam

1
(xin lỗi vì spam nhưng tôi dường như không thể chỉnh sửa bình luận ngày hôm nay) có vấn đề khác với việc này nằm trong tệp tiêu đề. Bản đồ ( E##MapName) cần được chuyển đến một đơn vị biên dịch có quyền truy cập vào enum. Tôi đã tạo ra một giải pháp, nhưng nó không sạch lắm và tôi phải xin phép để chia sẻ nó. Hiện tại, tôi chỉ bình luận để nói rằng không có điểm nào trong việc đánh dấu các phương thức nội tuyến mà không có các tính năng bổ sung cần thiết để hỗ trợ sử dụng trong tệp tiêu đề.
Sông Tam

19

Trở lại năm 2011, tôi đã dành một cuối tuần để tinh chỉnh một giải pháp dựa trên vĩ mô và cuối cùng không bao giờ sử dụng nó.

Quy trình hiện tại của tôi là khởi động Vim, sao chép các điều tra viên trong thân công tắc trống, bắt đầu một macro mới, chuyển đổi điều tra viên đầu tiên thành một câu lệnh tình huống, di chuyển con trỏ đến đầu dòng tiếp theo, dừng macro và tạo trường hợp còn lại báo cáo bằng cách chạy macro trên các điều tra viên khác.

Các macro Vim thú vị hơn các macro C ++.

Ví dụ thực tế:

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

Tôi sẽ tạo cái này:

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

Và đó là cách tôi có được.

Hỗ trợ riêng cho chuỗi enum sẽ tốt hơn nhiều mặc dù. Tôi rất thích xem kết quả của nhóm làm việc phản chiếu trong C ++ 17.

Một cách khác để làm điều đó đã được đăng bởi @sehe trong các bình luận .


1
Tôi làm chính xác điều này. Mặc dù tôi thường sử dụng Surround vim và chặn các lựa chọn trên đường đi
sehe 11/03/2015

@sehe Thú vị. Tôi nên có một cái nhìn về "bao quanh" bởi vì hiện tại tôi yêu cầu phải có nhiều tổ hợp phím.
StackedCrooking 11/03/2015

Đây là bản đầy đủ, không có macro (trừ khi .tính): i.imgur.com/gY4ZhBE.gif
sehe 11/03/2015

1
Gif hoạt hình rất dễ thương, nhưng thật khó để biết khi nào nó bắt đầu và kết thúc, và chúng ta đang ở bao xa. ... thực sự, gãi rằng, nó không dễ thương, nó gây mất tập trung. Tôi nói giết nó.
einpoklum

Cách tiếp cận lựa chọn khối này trong vim là tốt và tất cả, nhưng tại sao không chỉ đơn giản là sử dụng một cái gì đó như thế :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/nào?
Ruslan

14

Tôi không biết liệu bạn có thích điều này hay không, tôi không hài lòng với giải pháp này nhưng đó là cách tiếp cận thân thiện với C ++ 14 vì nó đang sử dụng các biến mẫu và lạm dụng chuyên môn hóa mẫu:

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

Điều tồi tệ nhất về phương pháp này là một nỗi đau để duy trì, nhưng cũng là một nỗi đau để duy trì một số lời xin lỗi tương tự khác, phải không?

Điểm hay về aproach này:

  • Sử dụng tempates biến (tính năng C ++ 14)
  • Với chuyên môn hóa mẫu, chúng tôi có thể "phát hiện" khi sử dụng giá trị không hợp lệ (nhưng tôi không chắc liệu điều này có hữu ích không).
  • Nó trông gọn gàng.
  • Việc tra cứu tên được thực hiện tại thời điểm biên dịch.

Live example

Biên tập

Người dùng khó tính673679 bạn đúng; Cách tiếp cận mẫu biến C ++ 14 không xử lý trường hợp thời gian chạy, đó là lỗi của tôi để quên nó :(

Nhưng chúng ta vẫn có thể sử dụng một số tính năng C ++ hiện đại và khuôn mẫu biến cộng với thủ thuật khuôn mẫu biến đổi để đạt được một bản dịch thời gian chạy từ giá trị enum sang chuỗi ... nó cũng gây khó chịu như những cái khác nhưng vẫn đáng được đề cập.

Hãy bắt đầu sử dụng bí danh mẫu để rút ngắn quyền truy cập vào bản đồ 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{};

Sau đó, thủ thuật mẫu matrixdic:

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

" Bí quyết tốt nhất " ở đây là việc sử dụng mẫu biến cho bản đồ chứa các giá trị và tên của từng mục nhập enum; bản đồ này sẽ giống nhau ở mỗi đơn vị dịch thuật và có cùng tên ở mọi nơi nên khá đơn giản và gọn gàng, nếu chúng ta gọi initializehàm như thế này:

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

Chúng tôi đang sắp xếp tên cho từng MyEnum mục và có thể được sử dụng trong thời gian chạy:

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

Nhưng có thể được cải thiện với SFINAE và quá tải << toán tử :

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

Với chính xác operator << bây giờ chúng ta có thể sử dụng enum theo cách này:

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

Điều này cũng gây khó chịu để duy trì và có thể được cải thiện, nhưng hy vọng bạn có được ý tưởng.

Live example


Điều này trông khá gọn gàng (có thể chỉ không xác định biến không chuyên biệt?). Có lẽ tôi đang thiếu một cái gì đó, mặc dù tôi không thấy cách nó xử lý trường hợp thời gian chạy.
dùng673679

@Paula_plus_plus: Không phải bạn nên sử dụng std::arraythay vì bản đồ khó sử dụng sao? Nó sẽ chỉ trở nên thích hợp hơn cho các enum bắt đầu từ ... cái gì, 2 ^ 10 giá trị? Có lẽ còn hơn thế nữa.
einpoklum

@einpoklum sẽ thật tuyệt vời nếu chúng ta có thể đảm bảo trong thời gian chạy có bao nhiêu phần tử enum. Đáng tiếc, chúng ta không thể. Và toàn bộ điểm của bản đồ chỉ là liên kết các tên với các giá trị, đó là những gì std::maptốt cho.
PaperBirdMaster

@Paula_plus_plus: Bạn đã gọi một initialize()hàm có số lượng đối số là số lượng giá trị enum, vì vậy bạn biết số lượng giá trị tại thời gian biên dịch. Đó chỉ là giá trị cụ thể mà bạn được yêu cầu in chỉ được biết ở thời gian chạy. Ngoài ra, ngay cả khi bạn không biết con số đó, một vectơ std :: sẽ nhanh hơn bản đồ std ::, trong hầu hết các trường hợp thực tế.
einpoklum

@einpoklum đó thực sự là một điểm rất tốt, tôi sẽ nghĩ về nó, cảm ơn! Điều duy nhất khiến tôi lo lắng là đó std::arraykhông phải là một thùng chứa giá trị khóa và do đó thiếu các phương thức tìm kiếm; Dù sao tôi cũng sẽ suy nghĩ.
PaperBirdMaster

7

Nếu bạn enumtrông như

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

Bạn có thể chuyển nội dung của enumtệp sang tệp mới:

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

Và sau đó các giá trị có thể được bao quanh bởi một macro:

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

Bước tiếp theo có thể bao gồm các mục trong enummột lần nữa:

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

Và cuối cùng bạn có thể tạo các hàm tiện ích về điều này 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");
}

Giải pháp có thể được áp dụng cho các tiêu chuẩn C ++ cũ hơn và nó không sử dụng các yếu tố C ++ hiện đại nhưng nó có thể được sử dụng để tạo ra nhiều mã mà không cần quá nhiều nỗ lực và bảo trì.


3
Không cần một tập tin riêng biệt. Đây thực chất là một x-macro .
HolyBlackCat

@HolyBlackCat nếu bạn chia giải pháp trong một số tệp, bạn có thể sử dụng lại các giá trị enum cho các mục đích khác nhau
eferion

Tôi đang cố gắng nói với bạn rằng bạn có thể làm điều tương tự nếu bạn đặt danh sách các giá trị vào một macro duy nhất bên cạnh định nghĩa enum trong tiêu đề.
HolyBlackCat

@HolyBlackCat vâng tôi hiểu bạn nhưng tôi thích giải pháp này. mặt khác, giải pháp này có thể được tìm thấy trong mã nguồn
clang

Đủ công bằng. Tôi không nên đánh giá thấp điều này, vì nó thực sự có thể có một số cách sử dụng. (Xin lỗi vì chỉnh sửa hình nộm, hệ thống sẽ khóa phiếu bầu của tôi nếu không.)
HolyBlackCat

6

Tôi đã có cùng một vấn đề một vài ngày trước. Tôi không thể tìm thấy bất kỳ giải pháp C ++ nào mà không có một số phép thuật vĩ mô kỳ lạ, vì vậy tôi đã quyết định viết một trình tạo mã CMake để tạo các câu lệnh tình huống chuyển đổi đơn giản.

Sử dụng:

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

Hàm tìm kiếm các tệp bao gồm trong hệ thống tệp (sử dụng các thư mục bao gồm được cung cấp với lệnh include_directories), đọc chúng và thực hiện một số biểu thức chính quy để tạo lớp và (các) hàm.

LƯU Ý: constexpr ngụ ý nội tuyến trong C ++, vì vậy sử dụng tùy chọn USE_CONSTEXPR sẽ tạo ra một lớp chỉ tiêu đề!

Thí dụ:

./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")

Tạo:

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

Cập nhật:

Tập lệnh bây giờ cũng hỗ trợ liệt kê phạm vi (lớp enum | struct) và tôi đã chuyển nó sang một repo riêng với một số tập lệnh khác mà tôi thường sử dụng: https://github.com/mensinda/cmakeBuildTools


ồ Ý tưởng rất độc đáo và sáng tạo :-) Tôi hy vọng bạn có đủ can đảm để nâng cấp máy phát điện của bạn để cung cấp một constexprnoexceptphiên bản ;-) Tôi cũng chỉ nhìn chằm chằm dự án GitHub của bạn ;-) Chúc mừng
olibre

1
Cập nhật máy phát điện. Các hàm bây giờ sẽ luôn là constexpr và enum: <type> hiện được hỗ trợ. Cảm ơn vì ngôi sao :)
Mense

Liên kết bị hỏng ... -.-
yeoman

Liên kết hiện đã được sửa.
Mense

4

Chỉ cần tạo enum của bạn. Viết một máy phát điện cho mục đích đó là khoảng năm phút làm việc.

Mã trình tạo trong java và python, siêu dễ dàng để chuyển sang bất kỳ ngôn ngữ nào bạn thích, bao gồm cả C ++.

Cũng siêu dễ dàng để mở rộng bởi bất kỳ chức năng bạn muốn.

ví dụ đầu vào:

First = 5
Second
Third = 7
Fourth
Fifth=11

tiêu đề được tạo:

#include <iosfwd>

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

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

tạo tập tin 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;
}

Và trình tạo, ở dạng rất ngắn gọn như một khuôn mẫu để chuyển và mở rộng. Mã ví dụ này thực sự cố gắng tránh ghi đè bất kỳ tệp nào nhưng vẫn có nguy cơ sử dụng nó.

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

Và một cổng tới Python 3.5 vì đủ khác nhau để có khả năng hữu ích

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
Cảm ơn bạn rất nhiều vì đã chia sẻ trình tạo của bạn bằng hai ngôn ngữ :-) Nhưng bạn có ý tưởng nào để tạo vào thời gian biên dịch không? Chẳng hạn, chúng ta có thể tưởng tượng việc dịch trình tạo của bạn bằng cách sử dụng các câu lệnh CMake để làm mới mã được tạo C ++ khi dữ liệu đầu vào bị thay đổi không? Ước mơ của tôi là buộc trình biên dịch C ++ tạo ra các enum khi biên dịch bằng cách sử dụng lập trình meta ( variadic template classvà các constexprhàm).
olibre

Otoh, trong trường hợp quá rườm rà khi thêm lệnh cmake tùy chỉnh, bạn có thể tự động hóa IDE của mình hoặc gọi thủ công gererator và có đầu ra trong kiểm soát nguồn. Đôi khi, dù sao cũng nên tạo mã trong kiểm soát nguồn, miễn là nó không quá nhiều và mọi người hiểu rằng họ không nên thực hiện thay đổi thủ công, bởi vì đôi khi thật thú vị khi xem lịch sử của các tệp được tạo khi bạn Tôi đang gỡ lỗi một cái gì đó kỳ lạ và có sự nghi ngờ rằng một sự thay đổi gần đây đối với trình tạo có thể đã phá vỡ thứ gì đó :)
yeoman

Về việc tạo ra mọi thứ vào thời gian biên dịch, điều đó thật dễ dàng trong LISP vì cú pháp cực kỳ sạch sẽ và dễ dàng. Điều đó được giúp đỡ bởi thực tế là nó được gõ động, cho phép nó ngắn gọn và dễ đọc mà không cần nhiều cú pháp. Tương đương với các macro LISP trong C ++ sẽ cần một cách rất phức tạp để mô tả AST về những gì bạn đang cố gắng tạo ra. Và AST cho C ++ không bao giờ đẹp :(
yeoman

Trực tiếp trong Make thay vì cmake, btw siêu dễ dàng. Chỉ cần tạo các mục tiêu .h và .cpp cho mỗi tệp .enum thông qua find và các mục tiêu này phụ thuộc vào enum defs đã nói, vì vậy chúng sẽ tự động được tạo lại sau khi các tệp .enum def thay đổi. Có thể dễ dàng hơn trong cmake vì nó đầy ma thuật cho những thứ này nhưng tôi thường xuyên sử dụng Make, ant và gradle, nhưng chỉ có kiến ​​thức hạn chế về Maven, cmake và grunt :)
yeoman

Cảm ơn câu trả lời của bạn :-) Tôi nghĩ rằng hầu hết các nhà phát triển C ++ sẽ đánh giá cao nếu trình tạo của bạn có thể phát hiện ra các enum trực tiếp trong mã C ++ như enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};hoặc trong một số dòng :-D Bạn có nghĩ rằng bạn có thể điều chỉnh trình tạo của mình để phát hiện enumtrong C ++ không tập tin? Điều tốt nhất có thể là chỉ tạo mã khi phát hiện thẻ như thế nào /*<Generate enum to string here>*/. Sau đó, trình tạo của bạn ghi vào vị trí mã được tạo C ++ tương ứng (thay thế mã được tạo trước đó). ^ _ ^ Thật là một máy phát điện tuyệt vời phải không? Chúc mừng :-)
olibre

3

Theo yêu cầu từ OP, đây là phiên bản rút gọn của giải pháp macro xấu xí dựa trên Boost PreprosigatorVariadic Macros .

Nó cho phép một danh sách đơn giản như cú pháp của các phần tử liệt kê cùng với các giá trị cài đặt cho các phần tử cụ thể để

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

mở rộng đến

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

Cùng với các chức năng cần thiết để xuất và thực hiện một số chuyển đổi trở lại. Macro này đã có ở đây từ lâu và tôi không hoàn toàn chắc chắn rằng đó là cách hiệu quả nhất, hay đó là một cách phù hợp, nhưng nó đã từng hoạt động kể từ đó

Mã hoàn chỉnh có thể được nhìn thấy trong hành động ở cả Ideone Coliru .

Sự xấu xí khổng lồ của nó là ở trên; Tôi sẽ đặt nó phía sau những kẻ phá hoại để bảo vệ đôi mắt của bạn, nếu tôi biết cách, nhưng markdown không giống tôi.

Thư viện (được hợp nhất trong một tệp tiêu đề duy nhất)

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

Sử dụng

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

Biên dịch (sao chép tiêu đề dán trong 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)));
                               ^

Đầu ra

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

5
Khối mã này là một hành trình điên rồ qua những cảnh quan tuyệt vời của ma thuật đen siêu hình. Tôi thực sự cảm thấy nhẹ nhõm khi đến main- Nhà, ngôi nhà ngọt ngào!
Quentin

Chỉ cần thêm một liên kết đến coliru để kiểm tra đầu ra (có một số cảnh báo, nhấp vào liên kết trong câu trả lời của bạn). Tôi cũng đã chia thành Lib / Cách sử dụng. Liệu các công cụ namespace xxxcó thể được di chuyển đến vị trí tiêu đề? Bạn có thể nói trong phần giới thiệu của bạn boost/preprocessor.hppvà do đó câu trả lời là tuân thủ C ++ hiện đại . Vui lòng sửa các cảnh báo và làm sạch một chút mã nguồn để có chất lượng tốt hơn.
olibre

@olibre: Đó là copypastad từ tôi nghĩ 5 tiêu đề khác nhau trong thư viện của chúng tôi. Enum_cast là từ một phần tổng quát hơn nhưng tôi nghĩ cũng nên thêm nó để xem do_enum_cast trong macro dùng để làm gì .. Các cảnh báo chỉ là từ main<tab>vim của tôi bao gồm cả các đối số tôi không sử dụng. Tôi không nghĩ rằng mã này có thể được làm sạch thực sự, nó chỉ để hiển thị những gì có thể được thực hiện và không nên;) và nếu tôi thay đổi nó ở đây thì đó không phải là mã tôi sử dụng trong sản xuất nữa ... đó là một trong những điều dễ vỡ rằng một khi nó hoạt động tốt hơn bạn không bao giờ chạm vào vì nó có thể sụp đổ theo cách không ai có thể dự đoán.
PlasmaHH 3/03/2015

Được rồi, tôi thấy điều này có thể được coi là một Proof Of Concept . Nhưng có quá nhiều chi phí vĩ mô để được bình chọn. Tuy nhiên cảm ơn đã chia sẻ. Chúc mừng
olibre 4/03/2015

Chào Plasma. Tôi đã thực hiện làm sạch mã nguồn sâu + hoàn thành bằng cách biên dịch và chạy đầu ra. Vui lòng kiểm tra chỉnh sửa của tôi . Tôi hy vọng điều này là tốt cho bạn. Là câu trả lời có giá trị hơn? Tuy nhiên, chi phí vĩ mô vẫn còn kinh khủng! Chúc một ngày tốt lành :-) Chúc mừng
olibre

2

Các giải pháp sau đây được dựa trên một std::array<std::string,N>cho một enum nhất định.

Đối enumvới std::stringchuyển đổi chúng ta chỉ có thể cast enum để size_tvà tra cứu các chuỗi từ mảng. Hoạt động là O (1) và không yêu cầu phân bổ heap.

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

Đối std::stringvới enumchuyển đổi chúng ta sẽ phải thực hiện một tìm kiếm tuyến tính trên mảng và đúc chỉ số mảng enum.

Hãy thử ở đây với các ví dụ sử dụng: http://coliru.stacked-crooking.com/a/e4212f93bee65076

Chỉnh sửa: Làm lại giải pháp của tôi để Enum tùy chỉnh có thể được sử dụng trong một lớp.


Cảm ơn bạn đã trả lời thú vị của bạn. Vui lòng làm lại đề xuất của bạn để sử dụng macro của bạn trong một lớp. Xem coliru.stacked-crooking.com/a/00d362eba836d04b Ngoài ra, hãy thử sử dụng constexprnoexepttừ khóa khi có thể. Chúc mừng :-)
olibre

Câu hỏi không chỉ định điều cần thiết này.
FKaria

Câu hỏi cập nhật (xem ví dụ). Hai yêu cầu khác: (1) loại hỗ trợ của enum và (2) giá trị có thể khác với chuỗi 0, 1, 2 ...
olibre

Tôi đã làm lại giải pháp của mình cho nó có thể được sử dụng trong một lớp học. Mặc dù vậy, tôi vẫn chưa tìm ra cách làm cho các giá trị khác với 0,1,2.
FKaria

Xin chào FKaria. Cảm ơn bạn rất nhiều cho việc làm lại của bạn. Tôi đã thực hiện một số thay đổi để hỗ trợ một số enum trong cùng một lớp và cũng để hỗ trợ enum class X : Typeđịnh dạng. Vui lòng xem lại đóng góp của tôi: coliru.stacked-crooking.com/a/b02db9190d3491a3 Bạn nghĩ gì về những thay đổi của tôi? Bạn có ý tưởng nào để hỗ trợ các giá trị được đặt trong enum không? Ví dụ enum E{A=3, B=6, C=A-B};Cheers
olibre

2

Đây ý chính cung cấp một bản đồ đơn giản dựa trên C ++ templates variadic.

Đây là phiên bản đơn giản hóa C ++ 17 của bản đồ dựa trên loại từ ý chính :

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

Một ví dụ sử dụng:

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...>thể được sử dụng theo cả hai hướng:

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

Ví dụ này có sẵn trên godbolt.org

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

Kết quả từ gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Cách lập trình meta rất thú vị. Tôi đã cố gắng đơn giản hóa một chút câu trả lời là tự chủ (không phụ thuộc vào liên kết Gist). Để ngắn gọn và dễ hiểu, cuối cùng tôi đã chỉnh sửa rất nhiều câu trả lời của bạn. Bạn vẫn đồng ý với những thay đổi của tôi? Chúc mừng ;-)
olibre

2

Tôi đã thất vọng vì vấn đề này trong một thời gian dài, cùng với vấn đề nhận được một loại chuyển đổi thành chuỗi theo cách thích hợp. Tuy nhiên, đối với vấn đề cuối cùng, tôi đã rất ngạc nhiên bởi giải pháp được giải thích trong Có thể in một loại biến trong C ++ tiêu chuẩn không? , sử dụng ý tưởng từ Tôi có thể có được tên loại C ++ theo cách constexpr không? . Sử dụng kỹ thuật này, một hàm tương tự có thể được xây dựng để lấy giá trị enum dưới dạng chuỗi:

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

Mã ở trên chỉ được thử nghiệm trên Clang (xem https://ideone.com/je5Quv ) và VS2015, nhưng phải thích ứng với các trình biên dịch khác bằng cách thay đổi một chút với các hằng số nguyên. Tất nhiên, nó vẫn sử dụng các macro dưới mui xe, nhưng ít nhất một cái không cần truy cập để thực hiện enum.


Điều này không thành công với g ++ 6.3.0 và C ++ 14.
einpoklum

Thú vị bởi vì enum có thể được khai báo bình thường mà không cần phải bọc nó trong một macro. Mặc dù tôi không thích phần phụ thuộc trình biên dịch và hằng số ma thuật.
zett42

2

Tôi lấy ý tưởng từ @antron và thực hiện nó theo cách khác: tạo ra một lớp enum thực sự .

Việc triển khai này đáp ứng tất cả các yêu cầu được liệt kê trong câu hỏi ban đầu nhưng hiện tại chỉ có một giới hạn thực sự : nó giả sử các giá trị enum không được cung cấp hoặc, nếu được cung cấp, phải bắt đầu bằng 0 và tăng lên liên tục mà không có khoảng trống.

Đây không phải là một giới hạn nội tại - đơn giản là tôi không sử dụng các giá trị enum đặc biệt. Nếu điều này là cần thiết, người ta có thể thay thế tra cứu vector bằng cách thực hiện chuyển đổi / trường hợp truyền thống.

Giải pháp sử dụng một số c ++ 17 cho các biến nội tuyến nhưng điều này có thể dễ dàng tránh được nếu cần. Nó cũng sử dụng boost: trim vì đơn giản.

Quan trọng nhất, nó chỉ mất 30 dòng mã và không có macro ma thuật đen. Mã dưới đây. Nó có nghĩa là được đặt trong tiêu đề và được bao gồm trong nhiều mô-đun biên dịch.

Nó có thể được sử dụng theo cách tương tự như được đề xuất trước đó trong chủ đề này:

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

Xin vui lòng cho tôi biết nếu điều này là hữu ích và làm thế nào nó có thể được cải thiện hơn nữa.


#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

Miễn là bạn ổn với việc viết một .h/.cppcặp riêng cho mỗi enum có thể truy vấn, giải pháp này hoạt động với cú pháp và khả năng gần giống như một enum c ++ thông thường:

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

Các .cpptập tin là 3 dòng soạn sẵn:

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

Ví dụ sử dụng:

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

Giải pháp này yêu cầu 2 tệp nguồn:

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

... và

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

Giải trình

Việc triển khai này khai thác thực tế là danh sách các phần tử của một định nghĩa enum cũng có thể được sử dụng như một danh sách khởi tạo giằng để khởi tạo thành viên lớp.

Khi ETRAITSđược đánh giá trong ngữ cảnh EnumTraits.inl, nó sẽ mở rộng ra một định nghĩa thành viên tĩnh choEnumTraits<> lớp.

Các EDECLbiến đổi vĩ mô mỗi thành viên enum vào giá trị danh sách initializer mà sau đó có được thông qua vào constructor thành viên để cư các thông tin enum.

Các EnumInitGuardlớp học được thiết kế để tiêu thụ enum initializer giá trị và sau đó sụp đổ - để lại một danh sách tinh khiết của dữ liệu enum.

Những lợi ích

  • c++cú pháp giống như
  • Hoạt động giống hệt nhau cho cả hai enumenum class (* gần như)
  • Hoạt động cho enum các loại với bất kỳ loại cơ bản số
  • Hoạt động cho enum các loại với các giá trị khởi tạo tự động, rõ ràng và phân mảnh
  • Hoạt động để đổi tên hàng loạt (liên kết intellisense được bảo tồn)
  • Chỉ có 5 ký hiệu tiền xử lý (3 toàn cầu)

* Ngược lại enums, khởi tạo trongenum class các loại tham chiếu các giá trị khác từ cùng một enum phải có các giá trị đó đủ điều kiện

Mất giá

  • Yêu cầu một .h/.cppcặp riêng cho mỗi truy vấnenum
  • Phụ thuộc vào hỗn độn macroincludema thuật
  • Lỗi cú pháp nhỏ phát nổ thành lỗi lớn hơn nhiều
  • Xác định classhoặc namespacephạm vi phạm vi là không cần thiết
  • Không khởi tạo thời gian biên dịch

Bình luận

Intellisense sẽ phàn nàn một chút về quyền truy cập của thành viên tư nhân khi mở ra EnumTraits.inl, nhưng vì các macro mở rộng đang thực sự xác định các thành viên của lớp, nên đó thực sự không phải là vấn đề.

Các #ifndef ENUM_INCLUDE_MULTI khối ở phía trên cùng của tập tin tiêu đề là một ít phiền toái nhỏ mà có thể có thể được teo tóp lại thành một macro hoặc một cái gì đó, nhưng nó đủ nhỏ để sống với ở kích thước hiện tại của nó.

Khai báo một enum phạm vi không gian tên yêu cầu enum trước tiên được chuyển tiếp được khai báo bên trong phạm vi không gian tên của nó, sau đó được định nghĩa trong không gian tên toàn cục. Ngoài ra, bất kỳ trình khởi tạo enum nào sử dụng các giá trị của cùng một enum phải có các giá trị đó đủ điều kiện.

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

Tôi không chắc cách tiếp cận này đã được đề cập trong một trong những câu trả lời khác chưa (thực tế là như vậy, xem bên dưới). Tôi đã gặp vấn đề nhiều lần và không tìm thấy giải pháp nào không sử dụng macro bị che khuất hoặc thư viện của bên thứ ba. Do đó tôi quyết định viết phiên bản macro bị xáo trộn của riêng tôi.

Những gì tôi muốn kích hoạt là tương đương với

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
}

cái nào nên in

ONE
TWO
13

Tôi không phải là một fan hâm mộ của macro. Tuy nhiên, trừ khi c ++ thực sự hỗ trợ chuyển đổi enum thành chuỗi, người ta phải sử dụng một số loại tạo mã và / hoặc macro (và tôi nghi ngờ điều này sẽ xảy ra quá sớm). Tôi đang sử dụng macro 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

Hầu hết trong số đó là xác định và xác định các ký hiệu mà người dùng sẽ chuyển dưới dạng tham số cho X-marco thông qua bao gồm. Cách sử dụng là như thế này

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

Bản thử trực tiếp

Lưu ý rằng tôi đã không bao gồm việc chọn loại cơ bản chưa. Tôi không cần nó cho đến nay, nhưng nó nên được chuyển thẳng sang mã để kích hoạt điều đó.

Chỉ sau khi viết bài này, tôi mới nhận ra rằng nó khá giống với câu trả lời eferions . Có lẽ tôi đã đọc nó trước đây và có lẽ nó là nguồn cảm hứng chính. Tôi đã luôn thất bại trong việc hiểu các macro X cho đến khi tôi tự viết;).


1

Các giải pháp sử dụng enum trong lớp / struct (cấu trúc mặc định với các thành viên công cộng) và các toán tử quá tải:

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

Nhìn từ bên ngoài, nó trông gần giống hệt như một lớp enum:

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

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

Điều này sẽ tạo ra "đỏ ​​1 2". Bạn có thể có thể quá tải << để biến đầu ra màu xanh thành một chuỗi (mặc dù điều này có thể gây ra sự mơ hồ nên không thể thực hiện được), nhưng nó sẽ không hoạt động với Color :: GREEN vì nó không tự động chuyển thành Màu.

Mục đích của việc chuyển đổi ngầm thành Enum (mà ngầm chuyển đổi thành int hoặc kiểu đã cho) là để có thể thực hiện:

Color color;
switch (color) ...

Điều này hoạt động, nhưng nó cũng có nghĩa là công việc này quá:

int i = color;

Với một lớp enum, nó sẽ không biên dịch. Bạn nên cẩn thận nếu bạn quá tải hai hàm lấy enum và một số nguyên hoặc xóa chuyển đổi ẩn ...

Một giải pháp khác sẽ liên quan đến việc sử dụng một lớp enum thực tế và các thành viên tĩnh:

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

    //same as previous...
};

Nó có thể mất nhiều không gian hơn và lâu hơn để thực hiện, nhưng gây ra lỗi biên dịch cho các chuyển đổi int ẩn. Tôi sẽ sử dụng cái này vì điều đó!

Mặc dù chắc chắn có điều này, nhưng tôi nghĩ nó đơn giản và trông đẹp hơn các mã khác tôi từng thấy. Cũng có tiềm năng để thêm chức năng, tất cả có thể nằm trong phạm vi lớp.

Chỉnh sửa : điều này hoạt động và hầu hết có thể được biên dịch trước khi thực hiện:

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

Điều này rất thú vị :-) Tuy nhiên phiên bản hiện tại của bạn ngụ ý rằng bạn phải tự viết nội dung case Enum::RED: return "red";. Câu hỏi là về tự động hóa công cụ này bởi trình biên dịch (tại thời gian biên dịch). Ý tưởng của câu hỏi là chỉ thay đổi hoặc thêm giá trị enum mà không phải cập nhật nội dung toString(). Bạn có thấy? Cảm ơn
olibre

1

Giải pháp rất đơn giản với một ràng buộc lớn: bạn không thể gán giá trị tùy chỉnh cho enumcác giá trị, nhưng với biểu thức chính xác, bạn có thể. bạn cũng có thể thêm bản đồ để dịch chúng trở lại enumgiá trị mà không cần nỗ lực nhiều hơn:

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

Ví dụ sử dụng:

EnumToString(MyEnum, Red, Green, Blue);

Cảm ơn bạn Malem cho ý tưởng sáng tạo của bạn. Tôi đã chỉnh sửa câu trả lời của bạn để cải thiện khả năng đọc. Tôi hy vọng bạn thích những thay đổi của tôi. Vui lòng tiếp tục cải thiện câu trả lời của bạn: (1) mở rộng phần "Ví dụ sử dụng" với nội dung như auto name = MyEnumStrings["Red"];- (2) Tại sao bạn sử dụng enum class? - (3) Bạn có ủng hộ enum class MyEnum : char { Red, Green, Blue };không? - (4) Giải thích chức năng split()- (5) Bạn có cần tham số const std::regex& delimkhông? - (6) Điều gì về việc tạo ra MyEnumStringstại thời điểm biên dịch? => Bạn có thể sử dụng constexpr? ... Chúc mừng :-)
olibre

Tôi thực sự thích cách tiếp cận này. Thực sự ngắn gọn và dễ hiểu.
Anton Holmberg

1

EDIT: kiểm tra bên dưới để biết phiên bản mới hơn

Như đã đề cập ở trên, N4113 là giải pháp cuối cùng cho vấn đề này, nhưng chúng ta sẽ phải chờ hơn một năm để thấy nó ra mắt.

Trong khi đó, nếu bạn muốn tính năng như vậy, bạn sẽ cần phải sử dụng các mẫu "đơn giản" và một số phép thuật tiền xử lý.

ĐTVN

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

Sử dụng

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

Giải thích đơn giản

Enum<T>::m_counterđược đặt thành 0 bên trong mỗi khai báo không gian tên.
( Ai đó có thể chỉ cho tôi biết đâu ^^ hành vi này ^^ được đề cập trên tiêu chuẩn không? )
Phép thuật tiền xử lý tự động hóa việc khai báo các điều tra viên.

Nhược điểm

  • Nó không phải là sự thật enum loại , do đó không thể quảng bá cho int
  • Không thể được sử dụng trong trường hợp chuyển đổi

Giải pháp thay thế

Điều này hy sinh đánh số dòng (không thực sự) nhưng có thể được sử dụng trong các trường hợp chuyển đổi .

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

Errata

#line 0xung đột với -pedanticGCC và tiếng kêu.

Giải pháp thay thế

Bắt đầu từ #line 1và trừ 1 từ__LINE__ .
Hoặc, không sử dụng -pedantic.
Và trong khi chúng ta đang ở đó, hãy tránh VC ++ bằng mọi giá, nó luôn là một trò đùa của trình biên dịch.

Sử dụng

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

Thực hiện và sử dụng thực tế

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Tham khảo nhanh

#line lineno - cppreference.com


0

Tôi đã viết một thư viện để giải quyết vấn đề này, mọi thứ xảy ra trong thời gian biên dịch, ngoại trừ việc nhận được tin nhắn.

Sử dụng:

Sử dụng macro DEF_MSGđể xác định cặp macro và thông báo:

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

CODE_OKlà macro để sử dụng và "OK!"là thông điệp tương ứng.

Sử dụng get_message()hoặc chỉ gm()để nhận thông báo:

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

Sử dụng MSG_NUMđể tìm hiểu có bao nhiêu macro đã được xác định. Điều này sẽ tự động tăng, bạn không cần phải làm gì cả.

Tin nhắn được xác định trước:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Dự án: libcodemsg


Thư viện không tạo thêm dữ liệu. Mọi thứ xảy ra trong thời gian biên dịch. Trong message_def.h, nó tạo ra một cuộc enumgọi MSG_CODE; trong message_def.c, nó tạo ra một biến chứa tất cả các chuỗi trongstatic const char* _g_messages[] .

Trong trường hợp như vậy, thư viện bị giới hạn chỉ tạo một enum. Điều này là lý tưởng cho các giá trị trả về, ví dụ:

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

MSG_CODE ret = foo();

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

Một điều tôi thích thiết kế này là bạn có thể quản lý các định nghĩa tin nhắn trong các tệp khác nhau.


Tôi tìm thấy giải pháp cho câu hỏi này có vẻ tốt hơn nhiều.


Xin chào Madwyn. Cảm ơn ý kiến ​​của bạn. Nhưng làm thế nào nó hoạt động? Chi phí là gì? (không chi phí hoặc nó tạo thêm dữ liệu?). Đề xuất của bạn có vẻ ổn, nhưng thật không may, một tuyên bố DEF_MSGphải được sử dụng / cập nhật / duy trì cho mỗi enumgiá trị: - / Và đây là lý tưởng mà chúng tôi muốn ngừng thực hiện ... Chúc mừng
olibre

Cảm ơn bạn đã trả lời, @olibre. Vui lòng kiểm tra câu trả lời cập nhật. Tôi không thấy chi phí ở đây, ngoại trừ một cuộc gọi chức năng là cần thiết để truy cập chuỗi. DEF_MSGlàm cho enumkết hợp chặt chẽ với thông điệp, mặc dù nó có một số hạn chế.
Madwyn

Cảm ơn bạn đã giải thích thêm trong câu trả lời của bạn :-) lib của bạn vẫn ổn nhưng không thể được sử dụng cho nhiều enum: - / Điều gì về sự hỗ trợ của enum class(C ++ 11) ? Bạn có thể sử dụng constexprđể giới hạn _g_messagestại thời gian chạy. Hỗ trợ nhiều enumloại (tránh _g_messages) bằng cách sử dụng lập trình meta (loại truyền tải {enum-type, enum-value}) hoặc có thể là các biến mẫu (C ++ 14) . Tôi nghĩ rằng lib của bạn không (chưa?) Phù hợp với các yêu cầu C ++ 11/14/17. Bạn nghĩ sao? Chúc mừng ;-)
olibre

1
Cảm ơn đã theo dõi. Tôi đã học được một cái gì đó mới ngày hôm nay! Các lớp enum và các biến mẫu trông tốt. Tôi nghĩ rằng câu trả lời của tôi là một chút "lạc đề" vì nó là hương vị C.
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;
    }

thí dụ

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

tự động ENUM_MAKE macro tạo 'lớp enum' và lớp trình trợ giúp với 'hàm phản chiếu enum'.

Để giảm sai lầm, cùng một lúc Mọi thứ được xác định chỉ với một ENUM_MAKE.

Ưu điểm của mã này là tự động được tạo để phản ánh và xem xét kỹ mã macro, mã dễ hiểu. Hiệu suất 'enum to string', 'string to enum' là thuật toán O (1).

Nhược điểm là khi sử dụng lần đầu tiên, lớp trình trợ giúp cho vectơ và sơ đồ chuỗi của enum Relection được khởi tạo. nhưng nếu bạn muốn bạn cũng sẽ được khởi tạo trước. -


Mặc dù mã này có thể trả lời câu hỏi, tốt hơn là giải thích cách giải quyết vấn đề mà không cần giới thiệu người khác và tại sao nên sử dụng nó. Câu trả lời chỉ có mã là không hữu ích trong thời gian dài.
JAL

này các bạn, tôi xin lỗi tôi không nói tiếng anh tốt lắm
craado_98

tự động ENUM_MAKE macro tạo 'lớp enum' và lớp trình trợ giúp với 'hàm phản chiếu enum'. / Để giảm lỗi, cùng một lúc Mọi thứ được xác định chỉ với một ENUM_MAKE. Ưu điểm của mã này là tự động được tạo để phản ánh và xem xét kỹ mã macro, mã dễ hiểu. Hiệu suất 'enum to string', 'string to enum' là thuật toán O (1). Nhược điểm là khi sử dụng lần đầu tiên, lớp trình trợ giúp cho vectơ và sơ đồ chuỗi của enum Relection được khởi tạo. nhưng nếu bạn muốn bạn cũng sẽ được khởi tạo trước.
craado_98

Xin chào craado_98. Cảm ơn sự đóng góp của bạn. Vui lòng chỉnh sửa câu trả lời của bạn và chèn vào đó nội dung bình luận của bạn. Trình biên dịch có thể tính toán ví dụ của bạn tại thời gian biên dịch nếu bạn sử dụng một số thủ thuật lập trình meta và constexpr. Tôi có nghĩa là các chức năng toName()toType()có thể được đánh giá trong quá trình biên dịch chứ không phải trong quá trình thực thi (thời gian chạy). Vui lòng áp dụng kiểu C ++ 11/14/17 trong câu trả lời của bạn. Chúc mừng ;-)
olibre

Hơn nữa: macro của bạn có tương thích với enum class MyEnum : short { A, B, C };?
olibre

0

Giải pháp của tôi là không sử dụng macro.

ưu điểm:

  • bạn thấy chính xác những gì bạn làm
  • truy cập với bản đồ băm, rất tốt cho nhiều enum có giá trị
  • không cần xem xét thứ tự hoặc giá trị không liên tiếp
  • cả enum thành chuỗi và chuỗi sang enum dịch, trong khi chỉ thêm giá trị enum ở một nơi bổ sung

nhược điểm:

  • bạn cần sao chép tất cả các giá trị enums dưới dạng văn bản
  • truy cập trong bản đồ băm phải xem xét trường hợp chuỗi
  • bảo trì nếu thêm giá trị là đau đớn - phải thêm vào cả bản đồ enum và bản dịch trực tiếp

vì vậy ... cho đến ngày C ++ thực hiện chức năng C # Enum.Pude, tôi sẽ bị mắc kẹt với điều này:

            #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

Vâng, một lựa chọn khác. Một trường hợp sử dụng thông thường là nơi bạn cần các hằng số cho các động từ HTTP cũng như sử dụng các giá trị phiên bản chuỗi của nó.

Ví dụ:

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

}

Lớp ĐỘNG TỪ:

// -----------------------------------------------------------
// -----------------------------------------------------------
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
Để giảm sử dụng bộ nhớ, bạn có thể thay thế thành viên const std::string textbằng cách chỉ theStrings[v]. Tuy nhiên, câu hỏi là về các tính năng từ C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 để tránh phải viết lớp như vậy bằng tay: - /
olibre

0

Câu trả lời của tôi là ở đây.

Bạn có thể nhận được tên giá trị enum và các chỉ số này đồng thời dưới dạng deque của chuỗi.

Phương pháp này chỉ cần ít bản sao và dán và chỉnh sửa.

Kết quả thu được cần truyền kiểu từ size_t sang kiểu lớp enum khi bạn cần giá trị loại enum, nhưng tôi nghĩ đó là một cách rất cơ động và mạnh mẽ để xử lý lớp 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

Bạn có thể sử dụng thư viện phản chiếu, như 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

(Tương tự https://stackoverflow.com/a/54967187/2338477 , sửa đổi một chút).

Đây là giải pháp của riêng tôi với phép thuật xác định tối thiểu và hỗ trợ các bài tập enum riêng lẻ.

Đây là tập tin tiêu đề:

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

Và đây là ví dụ thử nghiệm ứng dụng:

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

Phiên bản cập nhật của cùng một tệp tiêu đề sẽ được lưu giữ tại đây:

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


-5

Điều gì về một quá tải trực tuyến đơn giản? Bạn vẫn phải duy trì ánh xạ nếu bạn không muốn thực hiện một số phép thuật vĩ mô, nhưng tôi thấy nó sạch hơn giải pháp ban đầu của bạn.

#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) nó tạo ra nhiều bản sao hơn nữa 2) nó buộc bạn phải sử dụng các luồng
Karoly Horvath

6
-1 Xin lỗi @dau_sama nhưng mục đích của tất cả các câu hỏi lặp lại chuỗi này là để tránh duy trì ánh xạ enum / chuỗi. Nếu bạn nghĩ rằng câu trả lời của bạn không phù hợp với mục đích, vui lòng xem xét xóa câu trả lời. Chúc may mắn cho câu trả lời tiếp theo của bạn;) Chúc mừng
olibre

-9

Cách dễ nhất?
Sử dụng Ada: Enumeration'Image( Value )làm chính xác những gì bạn muốn. Nếu bạn thực sự cần C ++, bạn có thể thử xuất hàm:

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
Làm thế nào điều này trả lời câu hỏi ở tất cả? Câu hỏi nêu rõ việc chuyển đổi một enum thành chuỗi trong C ++ hiện đại.
Michael Choi

1
@MichaelChoi - Nó cũng vậy, nhưng cũng có vấn đề sử dụng công cụ thích hợp cho công việc: chỉ vì C ++ hoàn thiện và do đó có thể giải quyết tất cả các vấn đề có thể giải quyết KHÔNG có nghĩa là giải pháp đó là: nhanh, duy trì hoặc hiệu quả. Sử dụng một ngôn ngữ có chức năng phù hợp / mong muốn và xuất nó một giải pháp hợp lệ.
Shark8

3
Trong câu đầu tiên của câu hỏi "câu hỏi này là về việc sử dụng các tính năng mới của C ++". sau đó "[Tôi chưa tìm thấy cách nào] thanh lịch bằng cách sử dụng các tính năng mới của C ++ 11, C ++ 14 hoặc C ++ 17". Tác giả rõ ràng đang tìm kiếm một giải pháp C ++; bạn đã đưa ra một giải pháp trong Ada, vì vậy bạn không trả lời câu hỏi. Bạn đang đề nghị kết hợp một sự phụ thuộc hoàn toàn khác để giải quyết một cái gì đó có lẽ không nằm trong phạm vi câu hỏi.
Michael Choi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.