Cara mudah untuk menggunakan variabel jenis enum sebagai string di C?

89

Inilah yang saya coba lakukan:

Saya mencoba menulis fungsi yang akan melakukan kasus sakelar yang mirip dengan berikut ini:

Alih-alih mendefinisikan di setiap kasus, apakah ada cara untuk mengaturnya menggunakan variabel enum seperti yang saya coba lakukan di atas?

zxcv
sumber

Jawaban:

16

Tidak ada solusi bawaan. Cara termudah adalah dengan larik di char*mana nilai int enum diindeks ke string yang berisi nama deskriptif enum itu. Jika Anda memiliki sparse enum(yang tidak dimulai dari 0 atau memiliki celah dalam penomoran) di mana beberapa intpemetaan cukup tinggi untuk membuat pemetaan berbasis larik tidak praktis maka Anda dapat menggunakan tabel hash sebagai gantinya.

sk.
sumber
Memperluas ini, Jika memang itu adalah daftar yang bertambah secara linier, Anda bisa menggunakan alat makro editor Anda untuk merekam dan menyelesaikan setiap nama menjadi string. Sedikit pengetikan ekstra diperlukan dan Anda tidak perlu lagi menentukan definisi. Saya klik rekam di makro yang terakhir disalin, tambahkan kutipan setelahnya dan lanjutkan ke tempat yang sama di baris berikutnya. Saya mendorong berhenti. Saya menekan run X kali dan melakukan berapa pun yang ada (atau hanya satu langkah). Saya kemudian bisa membungkusnya dalam array string.
pengguna2262111
70

Teknik dari Membuat sesuatu baik pengenal C dan string? bisa digunakan disini.

Seperti biasa dengan hal-hal preprocessor seperti itu, menulis dan memahami bagian preprocessor bisa jadi sulit, dan termasuk meneruskan makro ke makro lain dan melibatkan penggunaan operator # dan ##, tetapi menggunakannya sangat mudah. Saya menemukan gaya ini sangat berguna untuk enum panjang, di mana mempertahankan daftar yang sama dua kali bisa sangat merepotkan.

Kode pabrik - hanya diketik sekali, biasanya disembunyikan di tajuk:

enumFactory.h:

Pabrik digunakan

someEnum.h:

someEnum.cpp:

Teknik ini dapat dengan mudah diperpanjang sehingga makro XX menerima lebih banyak argumen, dan Anda juga dapat menyiapkan lebih banyak makro untuk menggantikan XX untuk kebutuhan yang berbeda, serupa dengan tiga yang telah saya berikan dalam contoh ini.

Perbandingan dengan X-Macro menggunakan #include / #define / #undef

Meskipun ini mirip dengan X-Macro yang telah disebutkan orang lain, saya pikir solusi ini lebih elegan karena tidak memerlukan #undefing apa pun, yang memungkinkan Anda untuk menyembunyikan lebih banyak hal rumit yang ada di pabrik file header - file header adalah sesuatu yang tidak Anda sentuh sama sekali saat Anda perlu menentukan enum baru, oleh karena itu definisi enum baru jauh lebih pendek dan lebih rapi.

Suma
sumber
2
Saya tidak yakin bagaimana Anda dapat mengatakan ini lebih baik / lebih buruk daripada x-makro - ini adalah x-makro. The SOME_ENUM(XX)persis X-makro (tepatnya, "bentuk pengguna" yang melewati XXfungsi daripada menggunakan #def #undef) dan kemudian pada gilirannya seluruh X-MACRO kemudian diteruskan ke DEFINE_ENUM yang menggunakan itu. Tidak mengambil apa pun dari solusi - ini bekerja dengan baik. Hanya untuk memperjelas bahwa ini adalah penggunaan makro X.
BeeOnRope
1
@BeeOnRope Perbedaan yang Anda catat signifikan, dan membedakan solusi ini dari makro X idiomatik (seperti contoh Wikipedia ). Keuntungan dari passing XXover reing #defineadalah bahwa pola sebelumnya dapat digunakan dalam ekspansi makro. Perhatikan bahwa satu-satunya solusi lain sesingkat yang satu ini semuanya memerlukan pembuatan dan banyak penyertaan file terpisah untuk menentukan enum baru.
pmttavara
1
Trik lainnya adalah menggunakan nama enum sebagai nama makro. Anda cukup menulis #define DEFINE_ENUM(EnumType) ..., mengganti ENUM_DEF(...)dengan EnumType(...), dan meminta pengguna mengatakannya #define SomeEnum(XX) .... Praprosesor C secara kontekstual akan berkembang SomeEnummenjadi pemanggilan makro saat diikuti oleh tanda kurung dan sebaliknya menjadi token biasa. (Tentu saja, ini menyebabkan masalah jika pengguna lebih suka menggunakan SomeEnum(2)untuk mentransmisikan ke jenis enum daripada (SomeEnum)2atau static_cast<SomeEnum>(2).)
pmttavara
1
@pmttavara - tentu, jika pencarian cepat adalah indikasi penggunaan x-makro yang paling umum menggunakan nama makro dalam tetap bersama dengan #definedan #undef. Apakah Anda tidak setuju bahwa "formulir pengguna" (disarankan misalnya, di bagian bawah artikel ini ) adalah jenis x-makro? Saya pasti selalu menyebutnya x-makro juga dan dalam basis kode C yang saya kunjungi akhir-akhir ini, ini adalah bentuk yang paling umum (itu jelas pengamatan yang bias). Saya mungkin telah salah mengurai OP.
BeeOnRope
2
@BeeOnRope Kata-kata saat ini adalah hasil edit, karena Anda meyakinkan saya saat itu ini adalah x-makro, bahkan jika bentuknya mungkin kurang digunakan (atau setidaknya satu yang kurang disebutkan dalam artikel) saat itu.
Suma
62
Bill Forster
sumber
3
Untuk itulah cpp dibuat. +1.
Derrick Turk
6
Ini adalah jawaban yang bagus, sepertinya ini adalah jawaban terbaik yang dapat dilakukan tanpa menggunakan alat khusus, dan saya telah melakukan hal semacam ini sebelumnya; tapi masih tidak pernah benar-benar terasa 'benar' dan saya tidak pernah benar-benar suka melakukannya ...
Michael Burr
Perubahan kecil: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];dalam defs.hfile - ini akan mendeklarasikan tabel nama Anda di file yang Anda gunakan. (Namun, tidak dapat menemukan cara yang baik untuk menyatakan ukuran tabel.) Selain itu, secara pribadi saya akan meninggalkan titik koma terakhir, tetapi manfaatnya masih bisa diperdebatkan.
Chris Lutz
1
@Bill, Mengapa repot-repot typmengantre #define ENUM_END(typ) };?
Pacerier
Ini tidak berfungsi jika saya ingin makro saya ditetapkan sebagai "ONE = 5"
UKMonkey
13

Pasti ada cara untuk melakukan ini - gunakan makro X () . Makro ini menggunakan praprosesor C untuk membuat enum, array, dan blok kode dari daftar data sumber. Anda hanya perlu menambahkan item baru ke #define yang berisi makro X (). Pernyataan switch akan meluas secara otomatis.

Contoh Anda dapat ditulis sebagai berikut:

Ada cara yang lebih efisien (yaitu menggunakan X Macro untuk membuat larik string dan indeks enum), tetapi ini adalah demo yang paling sederhana.

JayG
sumber
8

Saya tahu Anda memiliki beberapa jawaban yang bagus dan solid, tetapi tahukah Anda tentang operator # dalam C preprocessor?

Ini memungkinkan Anda melakukan ini:

alas tiang
sumber
char const *kConstStr[]
Anne van Rossum
6

C atau C ++ tidak menyediakan fungsionalitas ini, meskipun saya sering membutuhkannya.

Kode berikut berfungsi, meskipun paling cocok untuk enum non-sparse.

Dengan non-jarang, maksud saya bukan dari bentuk

karena ada celah besar di dalamnya.

Keuntungan dari metode ini adalah ia menempatkan definisi enum dan string di dekat satu sama lain; memiliki pernyataan switch dalam suatu fungsi mempelopori mereka. Ini berarti Anda cenderung tidak mengubah yang satu tanpa yang lain.

paxdiablo
sumber
6

CIUMAN. Anda akan melakukan segala macam hal switch / case lain dengan enum Anda jadi mengapa pencetakan harus berbeda? Melupakan tas dalam rutinitas pencetakan Anda bukanlah masalah besar ketika Anda mempertimbangkan ada sekitar 100 tempat lain di mana Anda dapat melupakan tas. Cukup kompilasi -Wall, yang akan memperingatkan kecocokan kasus yang tidak lengkap. Jangan gunakan "default" karena itu akan membuat peralihan menjadi lengkap dan Anda tidak akan mendapatkan peringatan. Sebaliknya, biarkan saklar keluar dan tangani kasus default seperti ...

Samuel Danielson
sumber
4

Penggunaan boost :: preprocessor memungkinkan solusi elegan seperti berikut:

Langkah 1: Sertakan file header:

Langkah 2: Deklarasikan objek enumerasi dengan sintaks berikut:

Langkah 3: Gunakan data Anda:

Mendapatkan jumlah elemen:

Mendapatkan string terkait:

Mendapatkan nilai enum dari string terkait:

Ini terlihat bersih dan kompak, tanpa file tambahan untuk disertakan. Kode yang saya tulis dalam EnumUtilities.h adalah sebagai berikut:

Ada beberapa batasan, yaitu salah satunya boost :: preprocessor. Dalam kasus ini, daftar konstanta tidak boleh lebih dari 64 elemen.

Mengikuti logika yang sama, Anda juga bisa berpikir untuk membuat enum renggang:

Dalam hal ini, sintaksnya adalah:

Penggunaannya mirip seperti di atas (tanpa fungsi eName ## 2Enum, yang dapat Anda coba ekstrapolasi dari sintaks sebelumnya).

Saya mengujinya di mac dan linux, tetapi perlu diketahui bahwa boost :: preprocessor mungkin tidak sepenuhnya portabel.

Giacomo M.
sumber
3

Dengan menggabungkan beberapa teknik di sini, saya mendapatkan bentuk yang paling sederhana:

Juan Gonzalez Burgos
sumber
2

Jika Anda menggunakan gcc, Anda dapat menggunakan:

Kemudian panggil saja misalnya

janisj
sumber
1

Lihat ide-ide di Mu Dynamics Research Labs - Arsip Blog . Saya menemukan ini awal tahun ini - saya lupa konteks persis di mana saya menemukannya - dan telah menyesuaikannya ke dalam kode ini. Kita bisa memperdebatkan manfaat menambahkan E di depan; ini berlaku untuk masalah spesifik yang ditangani, tetapi bukan bagian dari solusi umum. Saya menyimpannya di folder 'sketsa' saya - tempat saya menyimpan potongan kode yang menarik jika saya menginginkannya nanti. Saya malu untuk mengatakan bahwa saya tidak mencatat dari mana ide ini berasal pada saat itu.

Header: paste1.h

Contoh sumber:

Tidak selalu merupakan penggunaan pra-prosesor C terbersih di dunia - tetapi itu mencegah penulisan materi beberapa kali.

Jonathan Leffler
sumber
0

Jika indeks enum berbasis 0, Anda dapat meletakkan nama-nama dalam array char *, dan mengindeksnya dengan nilai enum.

Colen
sumber
0

Saya telah menciptakan sebuah kelas sederhana templated streamable_enumyang menggunakan streaming operator <<dan >>dan didasarkan pada std::map<Enum, std::string>:

Pemakaian:

Robert Husák
sumber
0

Berikut adalah solusi menggunakan makro dengan fitur-fitur berikut:

  1. hanya menulis setiap nilai enum satu kali, jadi tidak ada daftar ganda yang harus dipertahankan

  2. jangan menyimpan nilai enum dalam file terpisah yang #included, jadi saya bisa menuliskannya di mana pun saya mau

  3. jangan mengganti enum itu sendiri, saya masih ingin memiliki jenis enum yang ditentukan, tetapi selain itu saya ingin dapat memetakan setiap nama enum ke string yang sesuai (untuk tidak memengaruhi kode lama)

  4. pencarian harus cepat, jadi sebaiknya tidak ada switch-case, untuk enum besar itu

https://stackoverflow.com/a/20134475/1812866

muqker
sumber
0

Saya pikir solusi seperti Boost.Fusion satu untuk mengadaptasi struct dan class akan bagus, mereka bahkan memilikinya di beberapa titik, untuk menggunakan enum sebagai urutan fusi.

Jadi saya membuat hanya beberapa makro kecil untuk menghasilkan kode untuk mencetak enum. Ini tidak sempurna dan tidak ada yang bisa dilihat dengan kode boilerplate yang dihasilkan Boost.Fusion, tetapi dapat digunakan seperti makro Boost Fusion. Saya ingin benar-benar menghasilkan jenis yang dibutuhkan oleh Boost.Fusion untuk berintegrasi dalam infrastruktur ini yang memungkinkan untuk mencetak nama anggota struct, tetapi ini akan terjadi nanti, untuk saat ini ini hanya makro:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

Jawaban lama di bawah ini sangat buruk, tolong jangan gunakan itu. :)

Jawaban lama:

Saya telah mencari cara untuk memecahkan masalah ini tanpa terlalu banyak mengubah sintaks deklarasi enums. Saya sampai pada solusi yang menggunakan preprocessor untuk mengambil string dari deklarasi enum bersenar.

Saya dapat mendefinisikan enum non-sparse seperti ini:

Dan saya dapat berinteraksi dengan mereka dengan berbagai cara:

Berdasarkan definisi berikut:

Ketika saya akan membutuhkan dukungan untuk jarang enum dan ketika saya akan memiliki lebih banyak waktu saya akan meningkatkan to_string dan from_string implementasi dengan meningkatkan :: XPRESSIVE, tapi akan ini biaya dalam waktu kompilasi karena template penting dilakukan dan executable yang dihasilkan yaitu kemungkinan besar akan sangat besar. Tetapi ini memiliki keuntungan karena akan lebih mudah dibaca dan dipelihara daripada kode manipulasi string manual yang jelek ini.: D

Jika tidak, saya selalu menggunakan boost :: bimap untuk melakukan pemetaan seperti itu antara nilai enum dan string, tetapi harus dijaga secara manual.

daminetreg.dll
sumber
0

Karena saya memilih untuk tidak menggunakan makro untuk semua alasan yang biasa, saya menggunakan solusi makro yang lebih terbatas yang memiliki keuntungan menjaga makro deklarasi enum tetap bebas. Kekurangannya termasuk harus menyalin dan menempel definisi makro untuk setiap enum, dan harus menambahkan pemanggilan makro secara eksplisit saat menambahkan nilai ke enum.

gerardw
sumber