Cách dễ dàng để sử dụng các biến kiểu enum dưới dạng chuỗi trong C?


87

Đây là những gì tôi đang cố gắng làm:

typedef enum { ONE, TWO, THREE } Numbers;

Tôi đang cố gắng viết một hàm sẽ thực hiện một trường hợp chuyển đổi tương tự như sau:

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

Thay vì xác định mọi trường hợp, có cách nào để đặt nó bằng cách sử dụng biến enum như tôi đang cố gắng làm ở trên không?

Câu trả lời:


14

Không có giải pháp tích hợp sẵn. Cách đơn giản nhất là với một mảng trong char*đó giá trị int của enum lập chỉ mục cho một chuỗi chứa tên mô tả của enum đó. Nếu bạn có một enumkhoảng trống (không bắt đầu bằng 0 hoặc có khoảng trống trong việc đánh số) trong đó một số intánh xạ đủ cao để làm cho ánh xạ dựa trên mảng không thực tế thì bạn có thể sử dụng bảng băm để thay thế.


Mở rộng về điều này, Nếu thực sự đó là một danh sách tăng dần tuyến tính, bạn chỉ có thể sử dụng công cụ macro của trình soạn thảo của mình để ghi lại và phân giải từng tên thành một chuỗi. Cần thêm một chút đánh máy và bạn không cần phải định nghĩa ngay từ đầu. Tôi nhấp vào ghi trên macro cuối cùng được sao chép, thêm dấu ngoặc kép sau và tiếp tục đến cùng một vị trí trên dòng tiếp theo. Tôi dừng lại. Tôi nhấn chạy X lần và thực hiện nhiều lần (hoặc chỉ một bước). Sau đó, tôi có thể bọc nó trong một mảng chuỗi.
user2262111

70

Kỹ thuật từ Tạo một cái gì đó vừa là định danh C vừa là một chuỗi? có thể được sử dụng ở đây.

Như thường lệ với các công cụ tiền xử lý như vậy, việc viết và hiểu phần tiền xử lý có thể khó và bao gồm việc chuyển các macro sang các macro khác và liên quan đến việc sử dụng các toán tử # và ##, nhưng việc sử dụng nó rất dễ dàng. Tôi thấy phong cách này rất hữu ích cho enum dài, nơi duy trì cùng một danh sách hai lần có thể thực sự rắc rối.

Mã xuất xưởng - chỉ được nhập một lần, thường được ẩn trong tiêu đề:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Nhà máy đã sử dụng

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

Kỹ thuật này có thể dễ dàng mở rộng để macro XX chấp nhận nhiều đối số hơn và bạn cũng có thể chuẩn bị nhiều macro hơn để thay thế cho XX cho các nhu cầu khác nhau, tương tự như ba macro mà tôi đã cung cấp trong mẫu này.

So sánh với X-Macros bằng #include / #define / #undef

Mặc dù điều này tương tự như X-Macros mà những người khác đã đề cập, tôi nghĩ rằng giải pháp này thanh lịch hơn ở chỗ nó không yêu cầu #undefing bất cứ thứ gì, cho phép bạn ẩn nhiều thứ phức tạp hơn trong tệp tiêu đề - tệp tiêu đề là thứ mà bạn không đụng đến khi cần định nghĩa một enum mới, do đó, định nghĩa enum mới ngắn hơn và rõ ràng hơn rất nhiều.


2
Tôi không chắc làm thế nào bạn có thể nói điều này tốt hơn / tệ hơn x-macro - đây x-macro. Các SOME_ENUM(XX)chính là một X-vĩ mô (để được chính xác, "người dùng hình thức" đã vượt qua XXchức năng hơn là sử dụng #def #undef) và sau đó lần lượt sau đó toàn bộ X-MACRO được chuyển cho DEFINE_ENUM trong đó sử dụng nó. Không loại bỏ bất cứ thứ gì khỏi giải pháp - nó hoạt động tốt. Chỉ để làm rõ rằng đó là sử dụng macro X.
BeeOnRope 14/02/17

1
@BeeOnRope Sự khác biệt bạn lưu ý là đáng kể và phân biệt giải pháp này với các macro X thành ngữ (chẳng hạn như các ví dụ của Wikipedia ). Ưu điểm của việc chuyển XXqua sử dụng lại #definelà mẫu cũ có thể được sử dụng trong các mở rộng vĩ mô. Lưu ý rằng các giải pháp duy nhất khác ngắn gọn như giải pháp này đều đòi hỏi phải tạo và đưa vào nhiều tệp riêng biệt để xác định một enum mới.
pmttavara

1
Lừa khác là sử dụng tên enum như tên macro. Bạn chỉ cần viết #define DEFINE_ENUM(EnumType) ..., thay thế ENUM_DEF(...)bằng EnumType(...)và yêu cầu người dùng nói #define SomeEnum(XX) .... Bộ tiền xử lý C sẽ mở rộng theo ngữ cảnh SomeEnumthành lệnh gọi macro khi được theo sau bởi dấu ngoặc đơn và thành một mã thông báo nếu không. (Tất nhiên, điều này gây ra vấn đề nếu người dùng thích sử dụng SomeEnum(2)để đúc các loại enum hơn (SomeEnum)2hoặc static_cast<SomeEnum>(2).)
pmttavara

1
@pmttavara - chắc chắn, nếu tìm kiếm nhanh là bất kỳ dấu hiệu nào thì việc sử dụng macro x phổ biến nhất sử dụng tên macro bên trong cố định cùng với #define#undef. Bạn có không đồng ý rằng "biểu mẫu người dùng" (ví dụ: được đề xuất ở cuối bài viết này ) là một loại macro x không? Tôi chắc chắn đã luôn gọi nó là macro x và trong các cơ sở mã C mà tôi đã sử dụng gần đây, nó là dạng phổ biến nhất (đó rõ ràng là một quan sát thiên vị). Tôi có thể đã phân tích cú pháp OP sai mặc dù.
BeeOnRope

2
@BeeOnRope Từ ngữ hiện tại là kết quả của việc chỉnh sửa, vì bạn đã thuyết phục tôi hồi đó đây là x-macro, ngay cả khi nó có lẽ là dạng ít được sử dụng hơn (hoặc ít nhất là một dạng ít được đề cập trong các bài báo) hồi đó.
Suma

62
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever

3
Đây là thứ mà cpp được tạo ra. +1.
Derrick Turk

6
Đây là một câu trả lời hay, nó có vẻ là câu trả lời tốt nhất có thể làm mà không cần sử dụng các công cụ đặc biệt, và tôi đã từng làm kiểu này trước đây; nhưng nó vẫn không bao giờ thực sự cảm thấy 'đúng' và tôi không bao giờ thực sự thích làm điều đó ...
Michael Burr

Thay đổi nhỏ: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];trong defs.htệp - điều này sẽ khai báo bảng tên của bạn trong các tệp bạn sử dụng nó. (Mặc dù vậy, không thể tìm ra cách hay để khai báo kích thước bảng.) Ngoài ra, cá nhân tôi muốn bỏ qua dấu chấm phẩy cuối cùng, nhưng giá trị của cả hai cách đều được tranh luận.
Chris Lutz

1
@Bill, Tại sao phải bận tâm với typtrong dòng #define ENUM_END(typ) };?
Pacerier

Điều này không hoạt động khi tôi muốn macro của mình được xác định là "ONE = 5"
UKMonkey 13/09/17

13

Chắc chắn có một cách để làm điều này - sử dụng macro X () . Các macro này sử dụng bộ tiền xử lý C để tạo các vùng, mảng và khối mã từ danh sách dữ liệu nguồn. Bạn chỉ cần thêm các mục mới vào #define chứa macro X (). Câu lệnh switch sẽ tự động mở rộng.

Ví dụ của bạn có thể được viết như sau:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

Có nhiều cách hiệu quả hơn (tức là sử dụng Macro X để tạo mảng chuỗi và chỉ mục enum), nhưng đây là cách trình diễn đơn giản nhất.


8

Tôi biết bạn có một vài câu trả lời hay, nhưng bạn có biết về toán tử # trong bộ tiền xử lý C không?

Nó cho phép bạn làm điều này:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

char const *kConstStr[]
Anne van Rossum

6

C hoặc C ++ không cung cấp chức năng này, mặc dù tôi cần nó thường xuyên.

Đoạn mã sau đây hoạt động, mặc dù nó phù hợp nhất cho enums không thưa thớt.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Không thưa thớt, ý tôi là không phải dạng

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

vì điều đó có những khoảng trống lớn trong đó.

Ưu điểm của phương pháp này là nó đặt các định nghĩa của enum và string gần nhau; có một câu lệnh chuyển đổi trong một hàm sẽ giải thích chúng. Điều này có nghĩa là bạn ít có khả năng thay đổi cái này mà không thay cái kia.


6

HÔN. Bạn sẽ làm tất cả các loại công tắc / trường hợp khác với enums của mình, vậy tại sao việc in lại phải khác? Quên một hộp đựng trong thói quen in ấn của bạn không phải là một vấn đề lớn khi bạn xem xét có khoảng 100 nơi khác mà bạn có thể quên một hộp đựng. Chỉ cần biên dịch -Wall, sẽ cảnh báo về các trường hợp trùng khớp không đầy đủ. Không sử dụng "mặc định" vì điều đó sẽ làm cho quá trình chuyển đổi hoạt động và bạn sẽ không nhận được cảnh báo. Thay vào đó, hãy để nút chuyển thoát và xử lý trường hợp mặc định như vậy ...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}


4

Việc sử dụng boost :: preprocessor có thể trở thành một giải pháp thanh lịch như sau:

Bước 1: bao gồm tệp tiêu đề:

#include "EnumUtilities.h"

Bước 2: Khai báo đối tượng liệt kê theo cú pháp sau:

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

Bước 3: Sử dụng dữ liệu của bạn:

Nhận số phần tử:

td::cout << "Number of Elements: " << TestDataCount << std::endl;

Lấy chuỗi liên kết:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

Nhận giá trị enum từ chuỗi được liên kết:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

Điều này trông gọn gàng và nhỏ gọn, không có thêm tệp nào để bao gồm. Đoạn mã tôi đã viết trong EnumUtilities.h như sau:

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

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

Có một số hạn chế, tức là những hạn chế của boost :: tiền xử lý. Trong trường hợp này, danh sách các hằng số không được lớn hơn 64 phần tử.

Theo cùng một logic, bạn cũng có thể nghĩ để tạo enum thưa thớt:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

Trong trường hợp này, cú pháp là:

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

Cách sử dụng tương tự như trên (trừ hàm eName ## 2Enum mà bạn có thể thử ngoại suy từ cú pháp trước đó).

Tôi đã thử nghiệm nó trên mac và linux, nhưng lưu ý rằng boost :: tiền xử lý có thể không hoàn toàn di động.


3

Bằng cách kết hợp một số kỹ thuật ở đây, tôi đã đưa ra hình thức đơn giản nhất:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

2

Nếu bạn đang sử dụng gcc, bạn có thể sử dụng:

const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};

Sau đó, chỉ cần gọi ví dụ

enum_to_string_map[enum1]

1

Xem các ý tưởng tại Mu Dynamics Research Labs - Blog Archive . Tôi đã tìm thấy nó vào đầu năm nay - tôi quên bối cảnh chính xác nơi tôi bắt gặp nó - và đã điều chỉnh nó thành mã này. Chúng ta có thể tranh luận về giá trị của việc thêm chữ E ở phía trước; nó có thể áp dụng cho vấn đề cụ thể được giải quyết, nhưng không phải là một phần của giải pháp chung. Tôi đã cất nó đi trong thư mục 'họa tiết' của mình - nơi tôi lưu giữ những đoạn mã thú vị trong trường hợp tôi muốn chúng sau này. Tôi xấu hổ khi nói rằng tôi đã không ghi chú ý tưởng này đến từ đâu vào thời điểm đó.

Tiêu đề: paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

Nguồn ví dụ:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

Không hẳn là cách sử dụng bộ xử lý trước C sạch nhất thế giới - nhưng nó ngăn cản việc ghi tài liệu ra ngoài nhiều lần.



0

Nếu chỉ mục enum dựa trên 0, bạn có thể đặt tên trong một mảng char * và lập chỉ mục chúng với giá trị enum.



0

Tôi đã tạo ra một lớp đơn giản templated streamable_enumrằng sử dụng dòng khai thác <<>>và được dựa trên std::map<Enum, std::string>:

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

#include <iostream>
#include <string>
#include <map>

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

Sử dụng:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}

0

Đây là một giải pháp sử dụng macro với các tính năng sau:

  1. chỉ viết mỗi giá trị của enum một lần, vì vậy không có danh sách kép để duy trì

  2. không giữ các giá trị enum trong một tệp riêng biệt mà sau này có #included, vì vậy tôi có thể viết nó ở bất cứ đâu tôi muốn

  3. không thay thế chính enum, tôi vẫn muốn định nghĩa kiểu enum, nhưng thêm vào đó, tôi muốn có thể ánh xạ mọi tên enum thành chuỗi tương ứng (để không ảnh hưởng đến mã kế thừa)

  4. việc tìm kiếm phải nhanh chóng, vì vậy tốt nhất là không có trường hợp chuyển mạch, đối với những môi trường khổng lồ đó

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


0

Tôi nghĩ rằng một giải pháp như Boost.Fusion để điều chỉnh cấu trúc và lớp sẽ rất hay, họ thậm chí đã có nó tại một số thời điểm, để sử dụng enum như một chuỗi hợp nhất.

Vì vậy, tôi chỉ tạo một số macro nhỏ để tạo mã để in enum. Điều này không hoàn hảo và không có gì đáng chú ý với mã bảng soạn sẵn do Boost.Fusion tạo ra, nhưng có thể được sử dụng như macro Boost Fusion. Tôi muốn thực sự tạo ra các loại cần thiết của Boost.Fusion để tích hợp trong cơ sở hạ tầng này, cho phép in tên của các thành viên cấu trúc, nhưng điều này sẽ xảy ra sau đó, hiện tại đây chỉ là các macro:

#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

Câu trả lời cũ dưới đây là khá tệ, vui lòng không sử dụng nó. :)

Câu trả lời cũ:

Tôi đã tìm kiếm một cách giải quyết vấn đề này mà không thay đổi quá nhiều cú pháp khai báo enums. Tôi đã đến một giải pháp sử dụng bộ tiền xử lý để truy xuất một chuỗi từ một khai báo enum được xâu chuỗi.

Tôi có thể xác định các enums không thưa thớt như thế này:

SMART_ENUM(State, 
    enum State {
        RUNNING,
        SLEEPING, 
        FAULT, 
        UNKNOWN
    })

Và tôi có thể tương tác với họ theo nhiều cách khác nhau:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

Dựa trên các định nghĩa sau:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

Khi nào tôi cần hỗ trợ cho enum thưa thớt và khi có thêm thời gian, tôi sẽ cải thiện to_string triển khai from_string với boost :: xpressive, nhưng điều này sẽ tốn thời gian biên dịch vì quá trình tạo khuôn mẫu quan trọng được thực hiện và tệp thực thi được tạo là có khả năng thực sự lớn hơn. Nhưng điều này có lợi thế là nó sẽ dễ đọc và dễ bảo trì hơn so với mã thao tác chuỗi thủ công xấu xí này. : D

Nếu không, tôi luôn sử dụng boost :: bimap để thực hiện các ánh xạ như vậy giữa giá trị enums và chuỗi, nhưng nó phải được duy trì theo cách thủ công.


0

Bởi vì tôi không muốn sử dụng macro vì tất cả các lý do thông thường, tôi đã sử dụng một giải pháp macro hạn chế hơn có ưu điểm là giữ cho macro khai báo enum miễn phí. Các nhược điểm bao gồm việc phải sao chép, dán định hướng macro cho mỗi enum và phải thêm lệnh gọi macro một cách rõ ràng khi thêm giá trị vào enum.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.