enum to string dalam C ++ 11 / C ++ 14 / C ++ 17 modern dan C ++ 20 mendatang


354

Berlawanan dengan semua pertanyaan serupa lainnya, pertanyaan ini adalah tentang menggunakan fitur C ++ yang baru.

Setelah membaca banyak jawaban, saya belum menemukan:

Contoh

Contoh seringkali lebih baik daripada penjelasan panjang.
Anda dapat mengkompilasi dan menjalankan cuplikan ini di Coliru .
( Contoh lain sebelumnya juga tersedia)

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

Kendala

  • Harap tidak ada duplikasi yang tidak bernilai dari jawaban lain atau tautan dasar .
  • Harap hindari mengasapi jawaban berbasis makro, atau coba kurangi #definebiaya overhead seminimal mungkin.
  • Harap tidak ada manual enum-> stringpemetaan.

Senang bisa memiliki

  • enumNilai dukungan mulai dari angka yang berbeda dari nol
  • Mendukung enumnilai negatif
  • Mendukung enumnilai terfragmentasi
  • Dukungan class enum(C ++ 11)
  • Dukungan class enum : <type>memiliki izin apa pun <type>(C ++ 11)
  • Kompilasi waktu (bukan waktu berjalan) konversi ke string,
    atau paling tidak eksekusi cepat pada waktu berjalan (mis. std::mapBukan ide yang bagus ...)
  • constexpr (C ++ 11, lalu santai di C ++ 14/17/20)
  • noexcept (C ++ 11)
  • C ++ 17 / C ++ 20 cuplikan ramah

Satu ide yang mungkin bisa menggunakan kemampuan kompiler C ++ untuk menghasilkan kode C ++ pada waktu kompilasi menggunakan trik meta-pemrograman berdasarkan variadic template classdan constexprfungsi ...


4
(mungkin dari topik) lihat blog terkait Qt ini. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Menjelaskan kemungkinan mengganti Qt's moc (meta-object-compiler) dengan menggunakan refleksi C ++ (standar yang diusulkan).
ibre5041

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

1
Saya pribadi memecahkan masalah ini dengan menerapkan pustaka utilitas preprocessor kecil yang memungkinkan saya untuk mengulang argumen makro variadic dan melakukan fungsi atas semuanya. Saya melewatkan nilai enum sebagai argumen makro dan saya secara otomatis menghasilkan enum dan array string melalui preprocessor. Anda mungkin dapat melakukan ini menggunakan Boost Preprocessor juga.
Vittorio Romeo

2
apakah semuanya harus diselesaikan dengan C ++? Sangat mudah untuk secara otomatis menghasilkan kode untuk representasi string, hanya beberapa baris kode.
Karoly Horvath

2
"Tolong jangan berikan jawaban berbasis makro C jika mungkin" baik, kecuali jika Anda bersedia menunggu C ++ 17 hampir tidak ada yang dapat digunakan, dan tidak terlalu buruk untuk menyatakan enum DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))Anda kecuali Anda harus mempertahankan menghasilkan makro ... dan juga menempatkan kasus-kasus semacam itu ke dalam bahasa hanyalah jenis peretasan lain sebagai pengganti templat / hibrida makro yang lebih kuat. Kita seharusnya tidak menambahkan semua makro-makro yang berguna ke dalam bahasa hanya untuk dapat mengatakan bahwa makro tidak lagi digunakan.
PlasmaHH

Jawaban:


43

Magic EnumPerpustakaan hanya header menyediakan refleksi statis untuk enum (ke string, dari string, iterasi) untuk 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
};

Untuk lebih banyak contoh, periksa repositori rumah https://github.com/Neargye/magic_enum .

Di mana kelemahannya?

Pustaka ini menggunakan hack khusus-kompiler (berdasarkan __PRETTY_FUNCTION__/__FUNCSIG__ ), yang bekerja pada Dentang> = 5, MSVC> = 15.3 dan GCC> = 9.

Nilai enum harus dalam kisaran [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Secara default MAGIC_ENUM_RANGE_MIN = -128,MAGIC_ENUM_RANGE_MAX = 128 .

  • Jika perlu rentang lain untuk semua jenis enum secara default, definisikan ulang makro MAGIC_ENUM_RANGE_MINdan MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINharus lebih kecil atau sama dengan 0dan harus lebih besar dari INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXharus lebih besar dari 0dan harus lebih kecil dariINT16_MAX .

  • Jika perlu rentang lain untuk tipe enum tertentu, tambahkan spesialisasi enum_range untuk tipe enum yang diperlukan.

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

Mengapa batas jangkauan? Apakah ini untuk membatasi semacam kedalaman rekursi, atau karena semacam pencarian linear waktu kompilasi?
Emile Cormier

Ini luar biasa. Terima kasih! Mungkin bahkan lebih efisien jika kompiler cukup pintar untuk mengevaluasi std :: array constexpr satu kali saja. Sangat sangat bagus.
iestyn

87

(Pendekatan perpustakaan better_enums )

Ada cara untuk melakukan enum to string di C ++ saat ini yang terlihat seperti ini:

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

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

Pemakaian:

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

Semua operasi dapat dilakukan constexpr. Anda juga dapat mengimplementasikan proposal refleksi C ++ 17 yang disebutkan dalam jawaban oleh @ecatmur.

  • Hanya ada satu makro. Saya percaya ini adalah minimum yang mungkin, karena pengerasan preprocessor (# ) adalah satu-satunya cara untuk mengkonversi token ke string dalam C ++ saat ini.
  • Makro cukup tidak mengganggu - deklarasi konstan, termasuk inisialisasi, disisipkan ke dalam deklarasi enum built-in. Ini berarti mereka memiliki sintaks dan makna yang sama seperti pada enum bawaan.
  • Pengulangan dihilangkan.
  • Implementasi ini paling alami dan bermanfaat dalam setidaknya C ++ 11, karena constexpr. Itu juga bisa dibuat untuk bekerja dengan C ++ 98+ __VA_ARGS__. Sudah pasti C ++ modern.

Definisi makro agak terlibat, jadi saya menjawab ini dengan beberapa cara.

  • Sebagian besar jawaban ini adalah implementasi yang saya pikir cocok untuk kendala ruang pada StackOverflow.
  • Ada juga artikel CodeProject yang menjelaskan dasar-dasar implementasi dalam tutorial bentuk panjang. [ Haruskah aku memindahkannya ke sini? Saya pikir itu terlalu banyak untuk jawaban SO ].
  • Ada perpustakaan berfitur lengkap "Better Enums" yang mengimplementasikan makro dalam satu file header. Ini juga mengimplementasikan N4428 Type Property Queries , revisi saat ini dari proposal refleksi C ++ 17 N4113. Jadi, setidaknya untuk enum yang dideklarasikan melalui makro ini, Anda dapat memiliki refleksi C ++ 17 enum yang diusulkan sekarang, di C ++ 11 / C ++ 14.

Sangat mudah untuk memperluas jawaban ini ke fitur perpustakaan - tidak ada yang "penting" yang tertinggal di sini. Namun, hal ini cukup membosankan, dan ada masalah portabilitas kompiler.

Penolakan : Saya penulis artikel CodeProject dan perpustakaan.

Anda dapat mencoba kode dalam jawaban ini , perpustakaan , dan implementasi N4428 langsung online di Wandbox. Dokumentasi perpustakaan juga berisi ikhtisar tentang bagaimana menggunakannya sebagai N4428 , yang menjelaskan bagian enums dari proposal itu.


Penjelasan

Kode di bawah ini mengimplementasikan konversi antara enum dan string. Namun, dapat diperluas untuk melakukan hal-hal lain juga, seperti iterasi. Jawaban ini membungkus enum dalam astruct . Anda juga bisa membuat ciri di structsamping enum.

Strateginya adalah menghasilkan sesuatu seperti ini:

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

Masalahnya adalah:

  1. Kita akan berakhir dengan sesuatu seperti {Red = 1, Green, Blue}sebagai penginisialisasi untuk array nilai. Ini bukan C ++ yang valid, karena Redbukan ekspresi yang dapat ditentukan. Ini diselesaikan dengan melemparkan setiap konstanta ke tipe Tyang memiliki operator penugasan, tetapi akan membatalkan penugasan:{(T)Red = 1, (T)Green, (T)Blue} .
  2. Demikian pula, kita akan berakhir dengan {"Red = 1", "Green", "Blue"}sebagai inisialisasi untuk array nama. Kita harus memotong " = 1". Saya tidak mengetahui cara terbaik untuk melakukan ini pada waktu kompilasi, jadi kami akan menunda ini untuk menjalankan waktu. Akibatnya, _to_stringtidak akan constexpr, tetapi_from_string masih bisa constexpr, karena kita dapat memperlakukan spasi putih dan sama dengan tanda sebagai terminator ketika membandingkan dengan string yang tidak dipotong.
  3. Kedua hal di atas memerlukan makro "pemetaan" yang dapat menerapkan makro lain untuk setiap elemen di __VA_ARGS__ . Ini cukup standar. Jawaban ini mencakup versi sederhana yang dapat menangani hingga 8 elemen.
  4. Jika makro benar-benar mandiri, ia perlu menyatakan tidak ada data statis yang memerlukan definisi terpisah. Dalam praktiknya, ini berarti array perlu perawatan khusus. Ada dua solusi yang mungkin: constexpr(atau hanya const) array pada lingkup namespace, atau array reguler dalam constexprfungsi inline non- statis. Kode dalam jawaban ini adalah untuk C ++ 11 dan menggunakan pendekatan sebelumnya. Artikel CodeProject untuk C ++ 98 dan mengambil yang terakhir.

Kode

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

dan

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

Program di atas mencetak Red, seperti yang Anda harapkan. Ada tingkat keamanan jenis, karena Anda tidak dapat membuat enum tanpa menginisialisasi, dan menghapus salah satu kasing switchakan menghasilkan peringatan dari kompiler (tergantung pada kompiler dan bendera Anda). Juga, perhatikan bahwa "Red"telah dikonversi ke enum selama kompilasi.


Heya @mrhthepie, maaf suntingan Anda ditolak. Saya baru saja melihat email tentang hal itu. Saya akan memasukkannya ke dalam jawaban - terima kasih atas perbaikan bugnya!
antron

ini bagus. Apakah ini juga berfungsi jika saya ingin enum bit? Seperti saya ingin enum BitFlags, masing-masing 1Udigeser dengan jumlah tertentu?
user3240688

1
sepertinya ada kebocoran memori dalam _trimmed_names()kode yang Anda posting di sini ( new char[length + 1]tetapi Anda tidak disetel initializedke true). apakah saya melewatkan sesuatu? saya tidak melihat masalah yang sama dalam kode github Anda.
user3240688

1
Sudah diatur untuk true, tetapi di luar ifcabang (kebocoran memori awalnya ditangkap oleh @ mrhthepie). Harus memindahkannya ke dalam ... Mengedit. Terima kasih untuk melihat dari dekat ini dan kode GH.
antron

1
to_stringdapat mengembalikan a string_viewdari C ++ 17, yang tidak memerlukan pengakhiran nol, dan menjadi constexpr.
Yakk - Adam Nevraumont

74

Untuk C ++ 17 C ++ 20, Anda akan tertarik pada karya Kelompok Studi Refleksi (SG7). Ada seri paralel makalah yang meliputi kata - kata ( P0194 ) dan rasional, desain dan evolusi ( P0385 ). (Tautan menyelesaikan ke makalah terbaru di setiap seri.)

Pada P0194r2 (2016-10-15), sintaks akan menggunakan reflexprkata kunci yang diusulkan :

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

Sebagai contoh (diadaptasi dari cabang reflexpr dentang 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;
}

Refleksi statis gagal membuatnya menjadi C ++ 17 (lebih tepatnya, ke dalam draft yang mungkin final yang disajikan pada pertemuan standar November 2016 di Issaquah) tetapi ada keyakinan bahwa itu akan membuatnya menjadi C ++ 20; dari laporan perjalanan Herb Sutter :

Secara khusus, kelompok studi Refleksi meninjau proposal refleksi statis gabungan terbaru dan menemukan siap untuk memasuki kelompok Evolusi utama pada pertemuan berikutnya untuk mulai mempertimbangkan proposal refleksi statis terpadu untuk TS atau untuk standar berikutnya.


2
@ antron maaf hasil edit Anda ditolak; Saya akan menyetujuinya jika saya melihatnya tepat waktu. Saya belum melihat N4428 jadi terima kasih telah memberikan kepala.
ecatmur

3
Tidak masalah, terima kasih sudah memasukkannya. Saya agak heran mengapa itu ditolak. Saya melihat alasan boilerplate "tidak membuatnya lebih akurat", tetapi jelas lebih akurat untuk hari ini.
antron

1
Terima kasih :-) Saya telah membagi contoh terakhir untuk menghindari bilah gulir horizontal. Sayang sekali nilainya MyEnum::AAAtidak dapat dilewatkan sebagai argumen kedua dari std::meta::get_enumerators_m: - /
olibre

1
Fakta bahwa tugas yang begitu sederhana secara konseptual membutuhkan 3 level argumen templat bersarang sangat overengineered. Saya yakin ada alasan khusus dan teknis untuk itu. Tapi itu tidak berarti hasil akhirnya ramah pengguna. Saya suka C ++ dan kodenya masuk akal bagi saya. Tetapi 90% dari programmer lain yang bekerja dengan saya setiap hari menghindari C ++ karena kode seperti ini. Saya kecewa karena tidak melihat solusi yang lebih sederhana.
void.pointer

2
Tampaknya perkiraan saat ini untuk dimasukkannya TS Refleksi mendatang dalam standar adalah C ++ 23 : herbalutter.com/2018/04/02/…
Tim Rae

25

Ini mirip dengan Yuri Finkelstein; tetapi tidak diperlukan dorongan. Saya menggunakan peta sehingga Anda dapat memberikan nilai apa pun pada enum, pesanan apa pun.

Deklarasi kelas enum sebagai:

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

Kode berikut akan secara otomatis membuat kelas enum dan membebani:

  • '+' '+ + =' untuk std :: string
  • '<<' untuk streaming
  • '~' hanya untuk mengonversi ke string (Operator unary mana pun akan melakukannya, tapi saya pribadi tidak suka untuk kejelasan)
  • '*' untuk mendapatkan hitungan enum

Tidak perlu meningkatkan, semua fungsi yang diperlukan disediakan.

Kode:

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

Contoh:

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
Bisakah kita memiliki jeda baris di dalam definisi makro ini?
einpoklum

1
Saya menambahkan kelebihan untuk *mendapatkan hitungan enum ... Saya harap Anda tidak keberatan :-)
Peter VARGA

1
Apakah ada alasan implementasi ini menggunakan std::mappengindeksan (O (log (n))) daripada pengindeksan std::unordered_map(O (1))?
River Tam

1
juga, saya pikir metode harus ditandai inlinesehingga Anda dapat mendeklarasikan enums dalam file header seperti biasa tanpa mendapatkan "definisi banyak" kesalahan dari linker. (tidak yakin apakah itu sebenarnya solusi terbersih / terbaik)
River Tam

1
(maaf karena spam tapi sepertinya saya tidak bisa mengedit komentar hari ini) ada masalah lain dengan ini dalam file header. Peta ( E##MapName) perlu dipindahkan ke unit kompilasi yang juga memiliki akses ke enum. Saya telah membuat solusi, tetapi tidak terlalu bersih dan saya harus mendapatkan izin untuk membaginya. Untuk saat ini, saya hanya berkomentar mengatakan tidak ada gunanya menandai metode inline tanpa fitur tambahan yang diperlukan untuk mendukung penggunaan dalam file header.
River Tam

19

Kembali pada tahun 2011, saya menghabiskan akhir pekan mencari solusi berbasis makro dan akhirnya tidak pernah menggunakannya.

Prosedur saya saat ini adalah memulai Vim, menyalin enumerator di badan saklar kosong, memulai makro baru, mengubah enumerator pertama menjadi pernyataan kasus, memindahkan kursor ke awal baris berikutnya, menghentikan makro dan menghasilkan kasing yang tersisa pernyataan dengan menjalankan makro pada enumerator lainnya.

Makro Vim lebih menyenangkan daripada makro C ++.

Contoh kehidupan nyata:

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

Saya akan membuat ini:

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

Dan itulah yang saya dapatkan.

Dukungan asli untuk pengencangan enum akan jauh lebih baik. Saya sangat tertarik untuk melihat hasil dari workgroup refleksi di C ++ 17.

Cara alternatif untuk melakukannya diposting oleh @sehe di komentar .


1
Saya melakukan ini dengan tepat. Meskipun saya biasanya menggunakan Surround vim dan memblokir pilihan di sepanjang jalan
lihat

@sehe Menarik. Saya harus melihat "surround" karena saya memerlukan cara untuk menekan tombol saat ini.
StackedCrooked

Ini dia dalam darah penuh, tidak ada makro (kecuali .dihitung): i.imgur.com/gY4ZhBE.gif
sehe

1
Gif animasi itu imut, tetapi sulit untuk mengetahui kapan itu dimulai dan berakhir, dan seberapa jauh kita berada. ... sebenarnya, menggaruknya, itu tidak lucu, itu mengganggu. Saya katakan bunuh itu.
einpoklum

Pendekatan pemilihan blok dalam vim ini bagus dan semuanya, tetapi mengapa tidak menggunakan sesuatu seperti :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan

14

Saya tidak tahu apakah Anda akan suka ini atau tidak, saya tidak cukup senang dengan solusi ini tetapi ini adalah pendekatan ramah C ++ 14 karena menggunakan variabel template dan menyalahgunakan spesialisasi template:

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

Yang terburuk tentang pendekatan ini adalah rasa sakit yang harus dipertahankan, tetapi juga rasa sakit untuk mempertahankan beberapa pendekatan serupa lainnya, bukan?

Poin bagus tentang pendekatan ini:

  • Menggunakan variabel tempates (fitur C ++ 14)
  • Dengan spesialisasi templat, kami dapat "mendeteksi" ketika nilai yang tidak valid digunakan (tapi saya tidak yakin apakah ini bisa berguna sama sekali).
  • Terlihat rapi.
  • Pencarian nama dilakukan pada waktu kompilasi.

Live example

Edit

Misterious user673679 Anda benar; pendekatan template variabel C ++ 14 tidak menangani kasus runtime, itu adalah kesalahan saya untuk melupakannya :(

Tapi kita masih bisa menggunakan beberapa fitur C ++ modern dan templat variabel plus tipuan templat variadic untuk mencapai terjemahan runtime dari nilai enum ke string ... sama menyebalkannya dengan yang lain tapi masih layak untuk disebutkan.

Mari mulai menggunakan alias templat untuk mempersingkat akses ke peta 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{};

Kemudian, tipuan template variadic:

template <typename ENUM>
void initialize() {}

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

" Trik terbaik " di sini adalah penggunaan templat variabel untuk peta yang berisi nilai dan nama dari setiap entri enum; peta ini akan sama di setiap unit terjemahan dan memiliki nama yang sama di mana-mana sehingga cukup mudah dan rapi, jika kita memanggil initializefungsi seperti ini:

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

Kami menetapkan nama untuk setiap MyEnumentri dan dapat digunakan dalam runtime:

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

Tetapi dapat ditingkatkan dengan SFINAE dan <<operator kelebihan beban :

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

Dengan benar operator <<sekarang kita dapat menggunakan enum dengan cara ini:

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

Ini juga merepotkan untuk dipertahankan dan dapat ditingkatkan, tetapi harap Anda mendapatkan idenya.

Live example


Ini terlihat cukup rapi (apakah mungkin untuk tidak mendefinisikan variabel yang tidak ditentukan?). Mungkin saya melewatkan sesuatu, meskipun karena saya tidak melihat bagaimana menangani kasing runtime sama sekali.
user673679

@Paula_plus_plus: Bukankah Anda seharusnya hanya menggunakan std::arraypeta yang lebih sulit? Itu hanya akan menjadi lebih baik untuk enum mulai dari ... apa, 2 ^ 10 nilai? Mungkin bahkan lebih.
einpoklum

@einpoklum itu akan luar biasa jika kita dapat memastikan pada saat runtime berapa banyak elemen yang enumdimiliki. Sayangnya, kita tidak bisa. Dan seluruh titik peta hanya untuk mengaitkan nama dengan nilai, yang std::mapmemang bagus untuknya.
PaperBirdMaster

@ Paula_plus_plus: Anda sudah memanggil initialize()fungsi yang jumlah argumennya adalah jumlah nilai enum, sehingga Anda tahu jumlah nilai pada waktu kompilasi. Ini hanya nilai spesifik yang Anda minta untuk cetak yang hanya dikenal saat run-time. Juga, bahkan jika Anda tidak tahu angka itu, std :: vector akan lebih cepat daripada std :: map, lagi-lagi, dalam hampir semua kasus realistis.
einpoklum

@einpoklum itu poin yang sangat bagus, saya akan memikirkannya, terima kasih! Satu-satunya hal yang membuat saya khawatir adalah itu std::arraybukan wadah nilai kunci dan karena itu kurangnya metode menemukan; lagian aku akan memikirkannya.
PaperBirdMaster

7

Jika Anda enumterlihat seperti

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

Anda dapat memindahkan konten enumke file baru:

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

Dan kemudian nilai bisa dikelilingi oleh makro:

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

Langkah selanjutnya mungkin termasuk item dalam enumlagi:

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

Dan akhirnya Anda dapat menghasilkan fungsi utilitas tentang ini 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");
}

Solusi ini dapat diterapkan pada standar C ++ yang lebih lama dan tidak menggunakan elemen C ++ modern tetapi dapat digunakan untuk menghasilkan banyak kode tanpa terlalu banyak usaha dan pemeliharaan.


3
Tidak perlu untuk file terpisah. Ini pada dasarnya adalah x-makro .
HolyBlackCat

@HolyBlackCat jika Anda membagi solusi dalam beberapa file, Anda dapat menggunakan kembali nilai enum untuk tujuan yang berbeda
eferion

Saya mencoba untuk mengatakan bahwa Anda dapat melakukan hal yang sama jika Anda memasukkan daftar nilai ke dalam makro tunggal di samping definisi enum di header.
HolyBlackCat

@HolyBlackCat ya saya mengerti Anda tetapi saya lebih suka solusi ini. di sisi lain solusi ini dapat ditemukan dalam kode sumber dentang jadi saya pikir ini adalah cara yang baik untuk menyelesaikan masalah
eferion

Cukup adil. Seharusnya tidak menurunkan ini saya kira, karena memang dapat memiliki beberapa kegunaan. (Maafkan suntingan tiruan, sistem mengunci suara saya sebaliknya.)
HolyBlackCat

6

Saya memiliki masalah yang sama beberapa hari yang lalu. Saya tidak dapat menemukan solusi C ++ tanpa sihir makro yang aneh, jadi saya memutuskan untuk menulis generator kode CMake untuk menghasilkan pernyataan kasus saklar sederhana.

Pemakaian:

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

Fungsi mencari file-file include di sistem file (menggunakan direktori include yang disediakan dengan perintah include_directories), membacanya dan melakukan beberapa regex untuk menghasilkan kelas dan fungsi.

CATATAN: constexpr menyiratkan inline dalam C ++, jadi menggunakan opsi USE_CONSTEXPR akan menghasilkan kelas header saja!

Contoh:

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

Menghasilkan:

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

Memperbarui:

Sekarang skrip juga mendukung pencacahan scoped (enum class | struct) dan saya memindahkannya ke repo terpisah dengan beberapa skrip lain yang sering saya gunakan: https://github.com/mensinda/cmakeBuildTools


Wow! Ide yang sangat orisinal dan inovatif :-) Saya harap Anda memiliki keberanian untuk memutakhirkan generator Anda untuk menyediakan constexprdan noexceptversi ;-) Saya juga baru saja menatap proyek GitHub Anda ;-) Cheers
olibre

1
Pembaruan generator. Fungsi sekarang akan selalu constexpr dan enum: <type> sekarang didukung. Terima kasih untuk bintangnya :)
Mense

Tautannya rusak ... -.-
yeoman

Tautan sekarang diperbaiki.
Mense

4

Cukup hasilkan enum Anda. Menulis generator untuk tujuan itu adalah pekerjaan sekitar lima menit.

Kode generator dalam java dan python, super mudah untuk port ke bahasa apa pun yang Anda suka, termasuk C ++.

Juga super mudah diperluas dengan fungsionalitas apa pun yang Anda inginkan.

contoh input:

First = 5
Second
Third = 7
Fourth
Fifth=11

header yang dihasilkan:

#include <iosfwd>

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

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

file cpp yang dihasilkan

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

Dan generator, dalam bentuk yang sangat singkat sebagai template untuk porting dan ekstensi. Contoh kode ini benar-benar mencoba untuk menghindari menimpa file apa pun tetapi tetap menggunakannya dengan risiko Anda sendiri.

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

Dan port ke Python 3.5 karena cukup berbeda sehingga berpotensi membantu

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
Terima kasih banyak telah berbagi generator Anda dalam dua bahasa :-) Tapi apakah Anda punya ide bagaimana menghasilkan pada waktu kompilasi? Misalnya, dapatkah kita membayangkan menerjemahkan generator Anda menggunakan pernyataan CMake untuk menyegarkan kembali kode yang dihasilkan C ++ ketika input data diubah? Impian saya adalah untuk memaksa kompiler C ++ untuk menghasilkan enum di kompilasi menggunakan meta-programming ( variadic template classdan constexprfungsi).
olibre

Otoh, kalau-kalau terlalu rumit untuk menambahkan perintah cmake kustom, Anda dapat mengotomatiskan IDE Anda atau memanggil gererator secara manual dan memiliki output dalam kontrol sumber. Kadang-kadang ide yang baik untuk membuat kode dalam kontrol sumber, asalkan tidak terlalu banyak, dan orang-orang mengerti bahwa mereka tidak seharusnya membuat perubahan manual, karena kadang-kadang menarik untuk melihat sejarah file yang dihasilkan ketika Anda Sedang men-debug sesuatu yang aneh dan curiga bahwa perubahan baru-baru ini ke generator mungkin telah merusak sesuatu :)
yeoman

Tentang menghasilkan sesuatu pada waktu kompilasi, itu sangat mudah di LISP karena sintaksnya sangat bersih dan mudah. Itu dibantu oleh fakta bahwa itu diketik secara dinamis, yang memungkinkannya menjadi singkat dan dapat dibaca tanpa banyak sintaks. Setara dengan LISP macro di C ++ akan membutuhkan cara yang sangat rumit untuk menggambarkan AST dari apa yang Anda coba hasilkan. Dan AST untuk C ++ tidak pernah cantik :(
yeoman

Langsung di Make bukan cmake, ini sangat mudah. Cukup buat target .h dan .cpp untuk setiap file .enum melalui find, dan minta target ini bergantung pada kata enum defs, sehingga target tersebut secara otomatis dihasilkan kembali setelah file .enum def berubah. Ini mungkin jauh lebih mudah di cmake karena penuh dengan sihir untuk hal-hal semacam ini tetapi saya secara teratur menggunakan Make, semut, dan gradle, tetapi hanya memiliki pengetahuan yang terbatas tentang Maven, cmake, dan menggerutu :)
yeoman

Terima kasih atas jawaban Anda :-) Saya pikir sebagian besar pengembang C ++ akan menghargai jika generator Anda dapat mendeteksi enum langsung dalam kode C ++ seperti enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};atau dalam beberapa baris :-D Apakah Anda pikir Anda dapat mengadaptasi generator Anda untuk mendeteksi suatu enumdalam C ++ mengajukan? Yang terbaik adalah menghasilkan kode hanya pada deteksi tag seperti /*<Generate enum to string here>*/. Kemudian generator Anda menulis di tempat kode yang dihasilkan C ++ yang sesuai (mengganti kode yang dihasilkan sebelumnya). ^ _ ^ Apa generator yang luar biasa bukan? Cheers :-)
olibre

3

Sesuai permintaan dari OP, berikut ini adalah versi sederhana dari solusi makro jelek berdasarkan Boost Preprosessor dan Variadic Macros .

Hal ini memungkinkan untuk daftar sederhana seperti sintaks elemen enumerator bersama dengan pengaturan nilai untuk elemen tertentu

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

meluas ke

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

Bersamaan dengan fungsi yang diperlukan untuk output dan melakukan konversi kembali. Makro ini telah ada di sini selama berabad-abad, dan saya tidak sepenuhnya yakin bahwa ini adalah cara yang paling efisien, atau itu adalah cara yang sesuai, tetapi sejak saat itu telah berfungsi

Kode lengkap dapat dilihat beraksi di Ideone dan Coliru .

Kejahatan raksasa di atas; Saya akan meletakkannya di belakang spoiler untuk melindungi mata Anda, jika saya tahu caranya, tetapi penurunan harga tidak menyukai saya.

Perpustakaan (digabung dalam satu file tajuk tunggal)

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

Pemakaian

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

Kompilasi (salin tajuk tempel di dalam 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)));
                               ^

Keluaran

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

5
Blok kode ini adalah perjalanan gila melalui pemandangan menakjubkan dari metaprogramming black magic. Saya benar-benar merasa lega saat tiba di main- Rumah, rumah yang manis!
Quentin

Baru saja menambahkan tautan ke coliru untuk memeriksa keluaran (ada beberapa peringatan, klik tautan di dalam jawaban Anda). Saya juga telah memecah menjadi Lib / Penggunaan. Apakah barang namespace xxxdapat dipindahkan ke tempat tajuk? Anda dapat mengatakan dalam intro penggunaan Anda boost/preprocessor.hppdan oleh karena itu jawabannya sesuai dengan C ++ modern . Harap perbaiki peringatan dan bersihkan sedikit kode sumber untuk kualitas yang lebih baik.
olibre

@ Colibre: Ini copypastad dari saya pikir 5 header berbeda di perpustakaan kami. Enum_cast berasal dari bagian lain yang lebih umum, tetapi saya pikir untuk menambahkannya juga untuk melihat untuk apa do_enum_cast di makro .. Peringatan itu hanya dari main<tab>vim saya termasuk args yang tidak saya gunakan. Saya tidak berpikir kode ini dapat benar-benar dibersihkan, itu hanya untuk menunjukkan apa yang bisa dilakukan dan tidak boleh;) dan jika saya mengubahnya di sini bukan kode yang saya gunakan dalam produksi lagi ... itu adalah salah satu hal yang rapuh bahwa begitu itu berfungsi Anda lebih baik tidak menyentuh karena mungkin runtuh dengan cara yang tidak ada yang bisa memprediksi.
PlasmaHH

Baiklah, Plasma, saya melihat ini bisa dilihat sebagai Bukti Konsep . Tetapi ada terlalu banyak overhead makro untuk dipilih. Meskipun demikian terima kasih telah berbagi. Cheers
olibre

Hai Plasma. Saya telah melakukan pembersihan kode sumber yang dalam + selesai dengan kompilasi dan menjalankan output. Silakan periksa edit saya . Saya harap ini tidak masalah bagi Anda. Apakah jawabannya lebih berharga? Namun, overhead makro masih mengerikan! Semoga harimu menyenangkan :-) Cheers
olibre

2

Solusi berikut ini didasarkan pada std::array<std::string,N>untuk enum yang diberikan.

Untuk enumke std::stringkonversi kita hanya dapat melemparkan enum untuk size_tdan lookup string dari array. Operasi adalah O (1) dan tidak memerlukan alokasi tumpukan.

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

Untuk std::stringke enumkonversi kita harus melakukan pencarian linear atas array dan melemparkan indeks array untuk enum.

Coba di sini dengan contoh penggunaan: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Sunting: Mengolah solusi saya sehingga Enum kustom dapat digunakan di dalam kelas.


Terima kasih atas jawaban Anda yang menarik. Silakan ulang proposal Anda untuk menggunakan makro Anda di dalam kelas. Lihat coliru.stacked-crooked.com/a/00d362eba836d04b Selain itu coba gunakan constexprdan noexeptkata kunci bila memungkinkan. Cheers :-)
olibre

Pertanyaannya tidak menentukan persyaratan ini.
FKaria

Pertanyaan diperbarui (lihat contoh). Dua persyaratan lain: (1) jenis dukungan enum dan (2) nilai dapat berbeda dari urutan 0, 1, 2 ...
olibre

Saya mengerjakan ulang solusi saya agar bisa digunakan di dalam kelas. Saya belum menemukan cara untuk membuat nilai-nilai berbeda dari 0,1,2, .. meskipun.
FKaria

Hai FKaria. Terima kasih banyak atas pengerjaan ulang Anda. Saya melakukan beberapa perubahan untuk mendukung beberapa enum dalam kelas yang sama, dan juga untuk mendukung enum class X : Typeformat. Harap tinjau kontribusi saya: coliru.stacked-crooked.com/a/b02db9190d3491a3 Apa pendapat Anda tentang perubahan saya? Apakah Anda punya ide untuk mendukung nilai yang ditetapkan dalam enum? Contoh enum E{A=3, B=6, C=A-B};Ceria
olibre

2

Inti ini memberikan pemetaan sederhana berdasarkan templat variadic C ++.

Ini adalah versi sederhana peta berbasis tipe C ++ 17 dari intisari :

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

Contoh penggunaan:

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;

The map<KeyValues...>dapat digunakan di kedua arah:

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

Contoh ini tersedia di godbolt.org

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

Hasil dari gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Cara meta-pemrograman yang sangat menarik. Saya telah mencoba menyederhanakan sedikit jawaban untuk menjadi otonom (tanpa ketergantungan pada tautan Gist). Agar ringkas dan dapat dimengerti akhirnya saya banyak mengedit jawaban Anda. Apakah Anda masih setuju dengan perubahan saya? Cheers ;-)
olibre

2

Saya telah frustrasi dengan masalah ini untuk waktu yang lama juga, bersama dengan masalah mendapatkan tipe yang dikonversi menjadi string dengan cara yang benar. Namun, untuk masalah terakhir, saya terkejut dengan solusi yang dijelaskan dalam Apakah mungkin untuk mencetak tipe variabel dalam standar C ++? , menggunakan ide dari Bisakah saya mendapatkan nama tipe C ++ dengan cara constexpr? . Menggunakan teknik ini, fungsi analog dapat dibangun untuk mendapatkan nilai enum sebagai string:

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

Kode di atas hanya diuji pada Dentang (lihat https://ideone.com/je5Quv ) dan VS2015, tetapi harus dapat disesuaikan dengan kompiler lain dengan mengutak-atik konstanta integer. Tentu saja, masih menggunakan makro di bawah tenda, tetapi setidaknya satu tidak perlu akses ke implementasi enum.


Ini gagal dengan g ++ 6.3.0 dan C ++ 14.
einpoklum

Menarik karena enum dapat dideklarasikan secara normal tanpa harus membungkusnya dalam makro. Meskipun saya tidak suka kompiler-dependensi dan konstanta sihir.
zett42

2

Saya mengambil ide dari @antron dan mengimplementasikannya secara berbeda: menghasilkan kelas enum yang sebenarnya .

Implementasi ini memenuhi semua persyaratan yang tercantum dalam pertanyaan asli tetapi saat ini hanya memiliki satu batasan nyata : mengasumsikan nilai enum tidak disediakan atau, jika disediakan, harus dimulai dengan 0 dan naik secara berurutan tanpa kesenjangan.

Ini bukan batasan intrinsik - hanya karena saya tidak menggunakan nilai ad-hoc enum. Jika ini diperlukan, seseorang dapat mengganti pencarian vektor dengan implementasi switch / case tradisional.

Solusinya menggunakan beberapa c ++ 17 untuk variabel inline tetapi ini dapat dengan mudah dihindari jika diperlukan. Ini juga menggunakan boost: trim karena kesederhanaan.

Yang paling penting, hanya membutuhkan 30 baris kode dan tidak ada makro ilmu hitam. Kode di bawah. Ini dimaksudkan untuk dimasukkan ke dalam header dan dimasukkan dalam beberapa modul kompilasi.

Itu dapat digunakan dengan cara yang sama seperti yang disarankan sebelumnya di utas ini:

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

Tolong beritahu saya jika ini berguna dan bagaimana hal itu dapat ditingkatkan lebih lanjut.


#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

Selama Anda baik-baik saja dengan menulis .h/.cpppasangan terpisah untuk setiap enum yang dapat diminta, solusi ini bekerja dengan sintaks dan kemampuan yang hampir sama dengan cumum en ++ biasa:

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

The .cppfile 3 baris boilerplate:

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

Contoh penggunaan:

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

Kode

Solusi ini membutuhkan 2 file sumber:

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

...dan

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

Penjelasan

Implementasi ini mengeksploitasi fakta bahwa daftar elemen-elemen dari definisi enum yang diperkuat juga dapat digunakan sebagai daftar penginisialisasi yang diperkuat untuk inisialisasi anggota kelas.

Ketika ETRAITSdievaluasi dalam konteks EnumTraits.inl, itu diperluas ke definisi anggota statis untukEnumTraits<> kelas.

The EDECLmakro transformasi masing-masing anggota enum ke dalam daftar nilai-nilai initializer yang kemudian bisa dilewatkan ke konstruktor anggota untuk mengisi info enum.

The EnumInitGuardkelas dirancang untuk mengkonsumsi enum initializer nilai-nilai dan kemudian runtuh - meninggalkan daftar murni data enum.

Manfaat

  • c++sintaks -seperti
  • Bekerja secara identik untuk keduanya enumdan enum class(* hampir)
  • Bekerja untuk enumtipe dengan tipe numerik yang mendasarinya
  • Bekerja untuk enumjenis dengan nilai penginisialisasi otomatis, eksplisit, dan terfragmentasi
  • Berfungsi untuk penggantian nama massal (intellisense linking preserved)
  • Hanya 5 simbol preprosesor (3 global)

* Berbeda dengan enums, inisialisasi dalam enum classtipe yang mereferensikan nilai-nilai lain dari enum yang sama harus memiliki nilai-nilai yang sepenuhnya memenuhi syarat

Kerugian

  • Membutuhkan .h/.cpppasangan yang terpisah untuk setiap permintaanenum
  • Tergantung pada berbelit macro- belit dan includesihir
  • Kesalahan sintaks kecil meledak menjadi kesalahan yang jauh lebih besar
  • Mendefinisikan classatau namespacemelingkari enum adalah nontrivial
  • Tidak ada inisialisasi waktu kompilasi

Komentar

Intellisense akan mengeluh sedikit tentang akses anggota pribadi saat membuka EnumTraits.inl, tetapi karena makro diperluas sebenarnya mendefinisikan anggota kelas, itu sebenarnya bukan masalah.

Itu #ifndef ENUM_INCLUDE_MULTI blok di bagian atas file header adalah gangguan kecil yang mungkin bisa menyusut ke dalam makro atau sesuatu, tapi itu cukup kecil untuk hidup dengan pada ukuran saat ini.

Mendeklarasikan ruang lingkup enum namespace mensyaratkan bahwa enum terlebih dahulu dideklarasikan di dalam lingkup namespace-nya, kemudian didefinisikan dalam namespace global. Selain itu, setiap inisialisasi enum yang menggunakan nilai-nilai dari enum yang sama harus memiliki nilai-nilai tersebut sepenuhnya memenuhi syarat.

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

Saya tidak yakin apakah pendekatan ini sudah tercakup dalam salah satu jawaban lain (sebenarnya itu, lihat di bawah). Saya mengalami masalah berkali-kali dan tidak menemukan solusi yang tidak menggunakan makro yang dikaburkan atau perpustakaan pihak ketiga. Oleh karena itu saya memutuskan untuk menulis versi makro saya sendiri yang dikaburkan.

Yang ingin saya aktifkan adalah setara dengan

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
}

yang harus dicetak

ONE
TWO
13

Saya bukan penggemar makro. Namun, kecuali c ++ secara native mendukung konversi enums ke string, seseorang harus menggunakan semacam pembuatan kode dan / atau makro (dan saya ragu ini akan terjadi terlalu cepat). Saya menggunakan X-makro :

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

Sebagian besar mendefinisikan dan mendefinisikan simbol bahwa pengguna akan lulus sebagai parameter ke X-marco melalui sebuah include. Penggunaannya seperti ini

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

Demo Langsung

Perhatikan bahwa saya belum memasukkan jenis yang mendasarinya. Saya tidak membutuhkannya sejauh ini, tetapi harus langsung diubah ke kode untuk mengaktifkannya.

Hanya setelah menulis ini saya menyadari bahwa ini agak mirip dengan jawaban eferion . Mungkin saya membacanya sebelumnya dan mungkin itu adalah sumber utama inspirasi. Saya selalu gagal dalam memahami X-macro sampai saya menulis sendiri;).


1

Solusi menggunakan enum di dalam kelas / struct (default bawaan dengan anggota publik) dan operator kelebihan beban:

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

Dari luar terlihat hampir persis seperti enum kelas:

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

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

Ini akan menghasilkan "red 1 2". Anda mungkin bisa membebani << untuk membuat output biru menjadi string (meskipun mungkin menyebabkan ambiguitas jadi tidak mungkin), tetapi itu tidak akan berfungsi dengan Color :: GREEN karena tidak secara otomatis dikonversi ke Warna.

Tujuan memiliki konversi implisit ke Enum (yang secara implisit dikonversi ke int atau tipe yang diberikan) adalah untuk dapat melakukan:

Color color;
switch (color) ...

Ini berfungsi, tetapi ini juga berarti bahwa ini bekerja juga:

int i = color;

Dengan kelas enum tidak akan dikompilasi. Anda harus berhati-hati jika Anda kelebihan dua fungsi mengambil enum dan integer, atau menghapus konversi implisit ...

Solusi lain akan melibatkan penggunaan kelas enum aktual dan anggota statis:

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

    //same as previous...
};

Mungkin membutuhkan lebih banyak ruang, dan lebih lama untuk dibuat, tetapi menyebabkan kesalahan kompilasi untuk konversi int implisit. Saya menggunakan yang ini karena itu!

Pasti ada overhead dengan ini, tapi saya pikir itu lebih sederhana dan terlihat lebih baik daripada kode lain yang pernah saya lihat. Ada juga potensi untuk menambahkan fungsionalitas, yang semuanya bisa dicakup dalam kelas.

Sunting : ini berfungsi dan sebagian besar dapat dikompilasi sebelum eksekusi:

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

Ini sangat menarik :-) Namun versi Anda saat ini menyiratkan bahwa Anda harus menulisnya secara manual case Enum::RED: return "red";. Pertanyaannya adalah tentang mengotomatisasi hal ini oleh kompiler (pada waktu kompilasi). Ide dari pertanyaan ini adalah hanya mengubah atau menambahkan nilai enum tanpa harus memperbarui barang toString(). Apakah kamu lihat? Terima kasih
olibre

1

Solusi yang sangat sederhana dengan satu kendala besar: Anda tidak bisa menetapkan nilai khusus ke enumnilai, tetapi dengan regex yang tepat, Anda bisa. Anda juga dapat menambahkan peta untuk menerjemahkannya kembali ke enumnilai tanpa lebih banyak usaha:

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

Contoh penggunaan:

EnumToString(MyEnum, Red, Green, Blue);

Terima kasih Malem untuk ide inovatif Anda. Saya telah mengedit jawaban Anda untuk meningkatkan keterbacaan. Saya harap Anda menyukai perubahan saya. Silakan lanjutkan untuk meningkatkan jawaban Anda: (1) lanjutkan bagian "Contoh penggunaan" dengan sesuatu seperti auto name = MyEnumStrings["Red"];- (2) Mengapa Anda menggunakan enum class? - (3) Apakah Anda mendukung enum class MyEnum : char { Red, Green, Blue };? - (4) Menjelaskan fungsi split()- (5) Apakah Anda memerlukan parameter const std::regex& delim? - (6) Bagaimana dengan menghasilkan MyEnumStringspada waktu kompilasi? => Bisakah Anda menggunakan constexpr? ... Ceria :-)
olibre

Saya sangat suka pendekatan ini. Sangat singkat dan mudah dimengerti.
Anton Holmberg

1

EDIT: periksa di bawah untuk versi yang lebih baru

Seperti yang disebutkan di atas, N4113 adalah solusi akhir untuk masalah ini, tetapi kita harus menunggu lebih dari setahun untuk melihatnya keluar.

Sementara itu, jika Anda menginginkan fitur tersebut, Anda harus menggunakan templat "sederhana" dan beberapa sihir preprosesor.

Pencacah

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

Pemakaian

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

Penjelasan sederhana

Enum<T>::m_counterdiatur ke 0 di dalam setiap deklarasi namespace.
( Bisakah seseorang menunjukkan saya di mana ^^ perilaku ini ^^ disebutkan pada standar? )
Sihir preprosesor mengotomatiskan pernyataan enumerator.

Kekurangan

  • Ini bukan enumtipe yang benar , oleh karena itu tidak dapat dipromosikan ke int
  • Tidak dapat digunakan dalam sakelar kasus

Solusi alternatif

Yang satu ini mengorbankan penomoran baris (tidak benar-benar) tetapi dapat digunakan pada kasus saklar .

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

Kesalahan

#line 0konflik dengan -pedanticpada GCC dan dentang.

Penanganan masalah

Mulai dari #line 1dan kurangi 1 dari __LINE__.
Atau, jangan gunakan -pedantic.
Dan sementara kita melakukannya, hindari VC ++ dengan cara apa pun, itu selalu menjadi lelucon seorang kompiler.

Pemakaian

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

Implementasi dan penggunaan kehidupan nyata

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Referensi cepat

#line lineno - cppreference.com


0

Saya menulis perpustakaan untuk menyelesaikan masalah ini, semuanya terjadi dalam kompilasi waktu, kecuali untuk mendapatkan pesan.

Pemakaian:

Gunakan makro DEF_MSGuntuk menentukan pasangan makro dan pesan:

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

CODE_OKadalah makro untuk digunakan, dan "OK!"merupakan pesan yang sesuai.

Gunakan get_message()atau hanya gm()untuk mendapatkan pesan:

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

Gunakan MSG_NUMuntuk mengetahui berapa banyak makro yang telah ditentukan. Ini secara otomatis akan meningkat, Anda tidak perlu melakukan apa pun.

Pesan yang ditentukan sebelumnya:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Proyek: libcodemsg


Perpustakaan tidak membuat data tambahan. Semuanya terjadi dalam menyusun waktu. Di message_def.h, itu menghasilkan enumdisebut MSG_CODE; di message_def.c, ia menghasilkan variabel yang menampung semua string static const char* _g_messages[].

Dalam kasus seperti itu, perpustakaan dibatasi untuk membuat satu enumsaja. Ini sangat ideal untuk nilai pengembalian, misalnya:

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

MSG_CODE ret = foo();

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

Hal lain yang saya suka dari desain ini adalah Anda dapat mengatur definisi pesan di berbagai file.


Saya menemukan solusi untuk pertanyaan ini terlihat jauh lebih baik.


Hai Madwyn. Terima kasih atas idemu. Tetapi bagaimana cara kerjanya? Apa overhead-nya? (nol overhead atau apakah itu menciptakan data tambahan?). Proposisi Anda tampaknya baik-baik saja, tetapi sayangnya, satu pernyataan DEF_MSGharus digunakan / diperbarui / dipertahankan untuk setiap enumnilai: - / Dan inilah idealnya kami ingin berhenti melakukan ... Cheers
olibre

Terima kasih atas jawabannya, @ibibre. Silakan periksa jawaban yang diperbarui. Saya tidak melihat overhead di sini, kecuali panggilan fungsi diperlukan untuk mengakses string. DEF_MSGmembuat enumpesan tersebut dipasangkan dengan erat, meskipun memiliki beberapa keterbatasan.
Madwyn

Terima kasih atas penjelasan terlampir dalam jawaban Anda :-) Lib Anda baik-baik saja tetapi tidak dapat digunakan untuk beberapa enum: - / Bagaimana dengan dukungan enum class(C ++ 11) ? Anda dapat menggunakan constexpruntuk membatasi _g_messagespada saat run-time. Mendukung beberapa enumtipe (menghindari _g_messages) menggunakan meta-programming (tipe menyampaikan {enum-type, enum-value}) atau mungkin variabel templat (C ++ 14) . Saya pikir lib Anda belum cocok dengan persyaratan C ++ 11/14/17. Bagaimana menurut anda? Cheers ;-)
olibre

1
Terima kasih atas tindak lanjutnya. Saya belajar sesuatu yang baru hari ini! Kelas enum dan variabel templat terlihat bagus. Saya pikir jawaban saya sedikit "di luar topik" karena C rasanya.
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;
    }

contoh

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

secara otomatis ENUM_MAKE makro menghasilkan 'kelas enum' dan kelas pembantu dengan 'fungsi refleksi enum'.

Untuk mengurangi kesalahan, semuanya sekaligus didefinisikan hanya dengan satu ENUM_MAKE.

Keuntungan dari kode ini secara otomatis dibuat untuk refleksi dan melihat lebih dekat pada kode makro, kode yang mudah dipahami. 'enum to string', 'string to enum' kinerja keduanya adalah algoritma O (1).

Kekurangannya adalah ketika pertama kali digunakan, kelas helper untuk vektor string enum relection dan peta diinisialisasi. tetapi jika Anda mau, Anda juga akan diinisialisasi. -


Meskipun kode ini dapat menjawab pertanyaan, akan lebih baik untuk menjelaskan bagaimana cara memecahkan masalah tanpa memperkenalkan orang lain dan mengapa harus menggunakannya. Jawaban kode saja tidak berguna dalam jangka panjang.
JAL

hai teman-teman, maaf saya tidak bisa berbahasa Inggris dengan baik.
desperado_98

secara otomatis ENUM_MAKE makro menghasilkan 'kelas enum' dan kelas pembantu dengan 'fungsi refleksi enum'. / Untuk mengurangi kesalahan, semuanya sekaligus didefinisikan hanya dengan satu ENUM_MAKE. Keuntungan dari kode ini secara otomatis dibuat untuk refleksi dan melihat lebih dekat pada kode makro, kode yang mudah dipahami. 'enum to string', 'string to enum' kinerja keduanya adalah algoritma O (1). Kekurangannya adalah ketika pertama kali digunakan, kelas helper untuk vektor string enum relection dan peta diinisialisasi. tetapi jika Anda mau, Anda juga akan diinisialisasi.
desperado_98

Hai putus asa_98. Terima kasih atas kontribusi anda. Harap edit jawaban Anda dan masukkan di dalamnya konten komentar Anda. Kompilator dapat menghitung contoh Anda pada waktu kompilasi jika Anda menggunakan beberapa trik meta-pemrograman dan constexpr. Maksud saya fungsi toName()dan toType()dapat dievaluasi selama kompilasi dan tidak selama eksekusi (run-time). Silakan mengadopsi gaya C ++ 11/14/17 dalam jawaban Anda. Cheers ;-)
olibre

Selain itu: Apakah makro Anda kompatibel dengan enum class MyEnum : short { A, B, C };?
olibre

0

solusi saya adalah tanpa penggunaan makro.

keuntungan:

  • Anda melihat persis apa yang Anda lakukan
  • akses dengan peta hash, sangat bagus untuk banyak enum yang berharga
  • tidak perlu mempertimbangkan nilai pesanan atau non-berturut-turut
  • baik enum ke string dan string ke enum terjemahan, sementara nilai enum yang ditambahkan harus ditambahkan di satu tempat tambahan saja

kerugian:

  • Anda perlu mereplikasi semua nilai enums sebagai teks
  • akses di peta hash harus mempertimbangkan string case
  • pemeliharaan jika menambahkan nilai menyakitkan - harus menambahkan enum dan langsung menerjemahkan peta

jadi ... sampai hari C ++ mengimplementasikan fungsionalitas C # Enum.Parse, saya akan terjebak dengan ini:

            #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

Nah, opsi lain lagi. Kasus penggunaan yang umum adalah di mana Anda memerlukan konstanta untuk kata kerja HTTP serta menggunakan nilai versi stringnya.

Contoh:

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

}

Kelas VERB:

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

private:

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

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

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

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

public:

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

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

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

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

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

  // ---

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

};

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

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

1
Untuk mengurangi penggunaan memori, Anda dapat mengganti anggota const std::string texthanya dengan theStrings[v]. Namun pertanyaannya adalah tentang fitur-fitur dari C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 untuk menghindari keharusan menulis kelas seperti itu dengan tangan: - /
olibre

0

Jawaban saya ada di sini.

Anda bisa mendapatkan nama nilai enum dan indeks ini secara bersamaan sebagai deque of string.

Metode ini hanya perlu sedikit salin dan tempel dan edit.

Hasil yang diperoleh membutuhkan tipe-casting dari size_t ke tipe kelas enum ketika Anda membutuhkan nilai tipe kelas enum, tapi saya pikir ini adalah cara yang sangat portabel dan kuat untuk memperlakukan kelas 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

Anda bisa menggunakan perpustakaan refleksi, seperti 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

(Analogi https://stackoverflow.com/a/54967187/2338477 , sedikit dimodifikasi).

Ini adalah solusi saya sendiri dengan sihir definisi minimum dan dukungan penugasan enum individu.

Ini file header:

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

Dan berikut ini adalah contoh aplikasi tes:

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

Versi terbaru dari file header yang sama akan disimpan di sini:

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


-5

Bagaimana dengan kelebihan streaming yang sederhana? Anda masih harus mempertahankan pemetaan jika Anda tidak ingin melakukan sihir makro, tetapi saya merasa lebih bersih daripada solusi asli Anda.

#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) itu menciptakan duplikasi lebih banyak lagi 2) itu memaksa Anda untuk menggunakan stream
Karoly Horvath

6
-1 Maaf @dau_sama tetapi tujuan dari semua enum ini untuk pertanyaan berulang string adalah untuk menghindari pemeliharaan pemetaan enum / string. Jika menurut Anda jawaban Anda tidak sesuai dengan tujuannya, harap pertimbangkan untuk menghapus jawabannya. Selamat mencoba jawaban Anda berikutnya;) Cheers
olibre

-9

Cara termudah?
Gunakan Ada: Enumeration'Image( Value )melakukan persis apa yang Anda inginkan. Jika Anda benar - benar membutuhkan C ++, Anda dapat mencoba mengekspor fungsi:

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
Bagaimana ini menjawab pertanyaan sama sekali? Pertanyaannya dengan jelas menyatakan konversi enum ke string dalam C ++ modern.
Michael Choi

1
@MichaelChoi - Ya, tapi ada juga masalah menggunakan alat yang tepat untuk pekerjaan itu: hanya karena C ++ sedang menyelesaikan-turing dan karena itu dapat menyelesaikan semua masalah yang dipecahkan TIDAK berarti bahwa solusinya adalah: cepat, dapat dipertahankan, atau efisien. Menggunakan bahasa yang memiliki fungsi yang sesuai / diinginkan dan mengekspornya adalah solusi yang valid.
Shark8

3
Dalam kalimat pertama dari pertanyaan "pertanyaan ini adalah tentang menggunakan fitur C ++ baru". lalu "[saya belum menemukan] cara elegan menggunakan C ++ 11, C ++ 14 atau C ++ 17 fitur baru". Penulis jelas mencari solusi C ++; Anda memberi solusi di Ada, jadi Anda tidak menjawab pertanyaan. Anda menyarankan untuk memasukkan ketergantungan yang sama sekali berbeda untuk menyelesaikan sesuatu yang mungkin tidak ada dalam cakupan pertanyaan.
Michael Choi
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.