C ++: роздрукувати значення переліку як текст


89

Якщо у мене є така перерахування

enum Errors
{ErrorA=0, ErrorB, ErrorC};

Тоді я хочу роздрукувати на консолі

Errors anError = ErrorA;
cout<<anError;/// 0 will be printed

але що я хочу, це текст "ErrorA", чи можу я це зробити, не використовуючи if / switch?
І яке ваше рішення для цього?


Я думаю, що моя відповідь досить гарна, не могли б ви поглянути?
Сяо


Відповіді:


63

Використання карти:

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

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    static std::map<Errors, std::string> strings;
    if (strings.size() == 0){
#define INSERT_ELEMENT(p) strings[p] = #p
        INSERT_ELEMENT(ErrorA);     
        INSERT_ELEMENT(ErrorB);     
        INSERT_ELEMENT(ErrorC);             
#undef INSERT_ELEMENT
    }   

    return out << strings[value];
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Використання масиву структур з лінійним пошуком:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
#define MAPENTRY(p) {p, #p}
    const struct MapEntry{
        Errors value;
        const char* str;
    } entries[] = {
        MAPENTRY(ErrorA),
        MAPENTRY(ErrorB),
        MAPENTRY(ErrorC),
        {ErrorA, 0}//doesn't matter what is used instead of ErrorA here...
    };
#undef MAPENTRY
    const char* s = 0;
    for (const MapEntry* i = entries; i->str; i++){
        if (i->value == value){
            s = i->str;
            break;
        }
    }

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Використання перемикача / корпусу:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    const char* s = 0;
#define PROCESS_VAL(p) case(p): s = #p; break;
    switch(value){
        PROCESS_VAL(ErrorA);     
        PROCESS_VAL(ErrorB);     
        PROCESS_VAL(ErrorC);
    }
#undef PROCESS_VAL

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

12
-1. Просто зробіть комутатор замість того, щоб використовувати хеш-карту. Підвищена складність - це не дуже добре.
Саймон

8
Гарна думка. Наступного разу буду :) Але зараз я бачу, що ви вже відредагували свою публікацію, щоб додати ту функціональність, яку я шукав. Хороша робота!
Саймон

1
що таке #p? якщо в третьому прикладі замість enum я використовую клас enum, чи можна отримати лише рядок enum без імені класу?
rh0x

2
#pє препроцесором, що розшифровує p. Так викликаючи PROCESS_VAL(ErrorA)вихід буде: case(ErrorA): s = "ErrorA"; break;.
Нашенас

Я не вважаю це оптимальним рішенням: Причини: 1) Я повинен підтримувати вдвічі більше enumзначень, що, на мою думку, є NO-GO . 2) Коли я правильно розумію рішення, воно працює лише для одного enum.
Пітер ВАРГА

29

Використовуйте масив або вектор рядків із відповідними значеннями:

char *ErrorTypes[] =
{
    "errorA",
    "errorB",
    "errorC"
};

cout << ErrorTypes[anError];

EDIT: Рішення, наведене вище, застосовується, коли перелік є суміжним, тобто починається з 0 і немає призначених значень. Це буде чудово працювати з переліком у питанні.

Для подальшого підтвердження випадку, коли перелік не починається з 0, використовуйте:

cout << ErrorTypes[anError - ErrorA];

4
на жаль, enum дозволяє нам присвоювати значення елементам. Як ви підходите до роботи, якщо у вас є неперервані перелічення, рядок 'enum Status {OK = 0, Fail = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000}' (щоб ви могли згодом додати IOErrors до діапазону -1001-1999)
Nordic Mainframe

@Luther: Так, це буде працювати лише з суміжними переліками, якими є більшість перерахувань . Якщо перелік не суміжний, вам потрібно буде використовувати інший підхід, тобто карти. Але у випадку суміжних переліків я б запропонував використовувати цей підхід, а не надто ускладнювати.
Ігор Окс

2
Отже, якщо мій колега додає NewValue до переліку і не оновлює масив ErrorTypes, тоді ErrorTypes [NewValue] дає що? І як я обробляю негативні значення переліку?
Nordic Mainframe

2
@Luther: Вам доведеться постійно оновлювати ErrorTypes. Знову ж таки, існує компроміс між простотою та універсальністю, залежить те, що важливіше для користувача. У чому проблема негативних значень переліку?
Ігор Окс

1
Чи не повинен цей масив бути статичним для ефективності пам'яті? і переконання щодо безпеки?
Джонатан

15

Ось приклад, заснований на Boost.Preprocessor:

#include <iostream>

#include <boost/preprocessor/punctuation/comma.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>


#define DEFINE_ENUM(name, values)                               \
  enum name {                                                   \
    BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)          \
  };                                                            \
  inline const char* format_##name(name val) {                  \
    switch (val) {                                              \
      BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)       \
    default:                                                    \
        return 0;                                               \
    }                                                           \
  }

#define DEFINE_ENUM_VALUE(r, data, elem)                        \
  BOOST_PP_SEQ_HEAD(elem)                                       \
  BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),      \
               = BOOST_PP_SEQ_TAIL(elem), )                     \
  BOOST_PP_COMMA()

#define DEFINE_ENUM_FORMAT(r, data, elem)             \
  case BOOST_PP_SEQ_HEAD(elem):                       \
  return BOOST_PP_STRINGIZE(BOOST_PP_SEQ_HEAD(elem));


DEFINE_ENUM(Errors,
            ((ErrorA)(0))
            ((ErrorB))
            ((ErrorC)))

int main() {
  std::cout << format_Errors(ErrorB) << std::endl;
}

2
+1, це рішення не покладається на зовнішній інструмент, як відповідь lua вище, але є чистим C ++, воно дотримується принципу СУХОСТІ, а синтаксис користувача читається (якщо правильно відформатовано. ДОТЕ, вам не потрібні зворотні скісні риски при використанні DEFINE_ENUM, що виглядає трохи більш природно, IMO)
Fabio Fracassi

3
@Fabio Fracassi: "Це рішення не покладається на зовнішній інструмент" Boost - це зовнішній інструмент - нестандартна бібліотека C ++. До того ж це трохи занадто довго. Рішення проблеми має бути якомога простішим. Цей не відповідає вимогам ...
SigTerm

2
Насправді це все, що ви можете розмістити більшу частину коду (насправді весь, крім власне визначення), можна помістити в один заголовок. отже, це насправді найкоротше рішення, представлене тут. І для того, щоб boost був зовнішнім, так, але менше, ніж позамовний скрипт для попередньої обробки частин джерела, як це є сценарій lua вище. До того ж boost настільки близький до стандарту, що він повинен бути в кожному наборі інструментів програмістів на C ++. Просто ІМХО, звичайно
Фабіо Фракассі

[Я видалив непотрібне екранування нових рядків у виклику макросу. Вони не потрібні: виклик макросу може охоплювати декілька рядків.]
Джеймс МакНелліс,

Макрос DEFINE_ENUMвидає мені помилку, multiple definition of `format_ProgramStatus(ProgramStatus)'коли я намагаюся її використовувати.
HelloGoodbye

6

Ви можете скористатися більш простим трюком перед процесором, якщо бажаєте перерахувати свої enumзаписи у зовнішньому файлі.

/* file: errors.def */
/* syntax: ERROR_DEF(name, value) */
ERROR_DEF(ErrorA, 0x1)
ERROR_DEF(ErrorB, 0x2)
ERROR_DEF(ErrorC, 0x4)

Потім у вихідному файлі ви обробляєте файл як файл включення, але визначаєте, що потрібно ERROR_DEFзробити.

enum Errors {
#define ERROR_DEF(x,y) x = y,
#include "errors.def"
#undef ERROR_DEF
};

static inline std::ostream & operator << (std::ostream &o, Errors e) {
    switch (e) {
    #define ERROR_DEF(x,y) case y: return o << #x"[" << y << "]";
    #include "errors.def"
    #undef ERROR_DEF
    default: return o << "unknown[" << e << "]";
    }
}

Якщо ви використовуєте якийсь інструмент перегляду джерел (наприклад, cscope), вам доведеться повідомити його про зовнішній файл.


4

Тут була дискусія, яка могла б допомогти: чи існує простий спосіб перетворити перерахування C ++ на рядок?

ОНОВЛЕННЯ: Тут # sa скрипт для Lua, який створює оператор << для кожного іменованого переліку, з яким він стикається. Для цього може знадобитися певна робота, щоб вона працювала у менш простих випадках [1]:

function make_enum_printers(s)
    for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do
    print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){') 
    for k in string.gmatch(body,"([%w_]+)[^,]*") do
    print('  case '..k..': return o<<"'..k..'";')
    end
    print('  default: return o<<"(invalid value)"; }}')
    end
end

local f=io.open(arg[1],"r")
local s=f:read('*a')
make_enum_printers(s)

Враховуючи цей вхід:

enum Errors
{ErrorA=0, ErrorB, ErrorC};

enum Sec {
    X=1,Y=X,foo_bar=X+1,Z
};

Він виробляє:

ostream& operator<<(ostream &o,Errors n) { switch(n){
  case ErrorA: return o<<"ErrorA";
  case ErrorB: return o<<"ErrorB";
  case ErrorC: return o<<"ErrorC";
  default: return o<<"(invalid value)"; }}
ostream& operator<<(ostream &o,Sec n) { switch(n){
  case X: return o<<"X";
  case Y: return o<<"Y";
  case foo_bar: return o<<"foo_bar";
  case Z: return o<<"Z";
  default: return o<<"(invalid value)"; }}

Тож це, мабуть, початок для вас.

[1] перелічення в різних сферах дії або просторах імен, перелічення з виразами ініціалізатора, що містять комму тощо.


Хіба тут не прийнято коментувати "-1", щоб дати плакату можливість виправити свою відповідь? Просто запитую ..
Північний мейнфрейм

2
Я думаю, що рішення Boost PP нижче (від Філіпа) є кращим, оскільки використання зовнішніх інструментів є дуже дорогим технічним обслуговуванням. але ні -1, бо відповідь інакше дійсна
Fabio Fracassi

4
Boost PP також є проблемою технічного обслуговування, оскільки вам потрібні всі, хто розмовляє з метамовою Boost PP, яка є жахливою , простою для розбиття (видаючи зазвичай непридатні повідомлення про помилки) і лише обмеженою зручністю використання (lua / python / perl може генерувати код з довільної зовнішні дані). Це додає примноження вам списку залежностей, що може навіть не дозволятися через політику проекту. Крім того, він є інвазивним, оскільки вимагає від вас визначення переліків у DSL. Ваш улюблений інструмент вихідного коду або IDE може мати проблеми з цим. І останнє, але не менш важливе: ви не можете встановити точку зупинки в розширенні.
Nordic Mainframe

4

Я використовую масив рядків щоразу, коли визначаю перелік:

Профіль. Ч

#pragma once

struct Profile
{
    enum Value
    {
        Profile1,
        Profile2,
    };

    struct StringValueImplementation
    {
        const wchar_t* operator[](const Profile::Value profile)
        {
            switch (profile)
            {
            case Profile::Profile1: return L"Profile1";
            case Profile::Profile2: return L"Profile2";
            default: ASSERT(false); return NULL;
            }
        }
    };

    static StringValueImplementation StringValue;
};

Profile.cpp

#include "Profile.h"

Profile::StringValueImplementation Profile::StringValue;

4

Це хороший спосіб,

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING };

Роздрукуйте його з масивом символьних масивів

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ;

Подобається це

std::cout << rank_txt[m_rank - 1]

2
Що робити, якщо мій перелік починається з 2000 року? Це рішення не допоможе.
Sitesh

3
#include <iostream>
using std::cout;
using std::endl;

enum TEnum
{ 
  EOne,
  ETwo,
  EThree,
  ELast
};

#define VAR_NAME_HELPER(name) #name
#define VAR_NAME(x) VAR_NAME_HELPER(x)

#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x);

const char *State2Str(const TEnum state)
{
  switch(state)
  {
    CHECK_STATE_STR(EOne);
    CHECK_STATE_STR(ETwo);
    CHECK_STATE_STR(EThree);
    CHECK_STATE_STR(ELast);
    default:
      return "Invalid";
  }
}

int main()
{
  int myInt=12345;
  cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl;

  for(int i = -1; i < 5;   i)
    cout << i << " " << State2Str((TEnum)i) << endl;
  return 0;
}

2

Ви можете використовувати контейнер stl map ....

typedef map<Errors, string> ErrorMap;

ErrorMap m;
m.insert(ErrorMap::value_type(ErrorA, "ErrorA"));
m.insert(ErrorMap::value_type(ErrorB, "ErrorB"));
m.insert(ErrorMap::value_type(ErrorC, "ErrorC"));

Errors error = ErrorA;

cout << m[error] << endl;

4
Як це карта краще, ніж switch(n) { case XXX: return "XXX"; ... }? Який має O (1) пошук і не потребує ініціалізації? Або переліки якимось чином змінюються під час виконання?
Nordic Mainframe

Я погоджуюсь з @Luther Blissett щодо використання оператора switch (або також покажчика функції)
KedarX

1
Що ж, він може захотіти вивести "Цей мій дорогий друг Лютер - помилка А або" Цей мій дорогий друг Адріан - помилка Б. "Крім того, використання map усуває залежність від підписів iostream, так що він може вільно використовувати її де-небудь в код із конкатенацією рядків, наприклад, рядок x = "Hello" + m [ErrorA] тощо
Адріан Реган

Я впевнений, що std :: map містить багато if і перемикачів. Я читав би це як "як я можу це зробити, не маючи при цьому записів if і switch"
Nordic Mainframe

Я впевнений, що так, але це точно не вимагає від вас написання сценарію на Lua для вирішення проблеми ...
Адріан Реган

1

Для цієї проблеми я роблю функцію довідки, як це:

const char* name(Id id) {
    struct Entry {
        Id id;
        const char* name;
    };
    static const Entry entries[] = {
        { ErrorA, "ErrorA" },
        { ErrorB, "ErrorB" },
        { 0, 0 }
    }
    for (int it = 0; it < gui::SiCount; ++it) {
        if (entries[it].id == id) {
            return entries[it].name;
        }
    }
   return 0;
}

Лінійний пошук, як правило, ефективніший, ніж std::mapдля невеликих колекцій, подібних до цієї.


1

Це рішення не вимагає використання будь-яких структур даних або створення іншого файлу.

В основному, ви визначаєте всі свої значення перерахування в #define, а потім використовуєте їх в операторі <<. Дуже схожий на відповідь @ jxh.

посилання ideone для остаточної ітерації: http://ideone.com/hQTKQp

Повний код:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\
ERROR_VALUE(FILE_NOT_FOUND)\
ERROR_VALUE(LABEL_UNINITIALISED)

enum class Error
{
#define ERROR_VALUE(NAME) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME) case Error::NAME: return os << "[" << errVal << "]" #NAME;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Вихід:

Error: [0]NO_ERROR
Error: [1]FILE_NOT_FOUND
Error: [2]LABEL_UNINITIALISED

Приємна річ у тому, що ви робите це таким чином, що ви також можете вказати власні повідомлення для кожної помилки, якщо вважаєте, що вони вам потрібні:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised")

enum class Error
{
#define ERROR_VALUE(NAME,DESCR) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Вихід:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised

Якщо вам подобається робити свої коди помилок / описи дуже описовими, можливо, ви не хочете, щоб вони були у виробничих збірках. Вимкнути їх, щоб надрукувати лише значення, легко:

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
    #ifndef PRODUCTION_BUILD // Don't print out names in production builds
    #define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
        ERROR_VALUES
    #undef ERROR_VALUE
    #endif
    default:
        return os << errVal;
    }
}

Вихід:

Error: 0
Error: 1
Error: 2

Якщо це так, знаходження помилки номер 525 було б ПДФО. Ми можемо вручну вказати числа в початковому переліку, як це:

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh")

enum class Error
{
#define ERROR_VALUE(NAME,VALUE,DESCR) NAME=VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE  "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
        return os <<errVal;
    }
}
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
    {
        // If the error value isn't found (shouldn't happen)
        return os << static_cast<int>(err);
        break;
    }
    }
}

Вихід:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised
Error: [-1]UKNOWN_ERROR; Uh oh

0

Як щодо цього?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

    std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl;

і т.д. ... Я знаю, що це надуманий приклад, але я думаю, що він має застосування там, де це можливо і потрібно, і, звичайно, коротший, ніж написання сценарію для нього.


0

Використовуйте препроцесор:

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \
    FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC)

enum Errors
{
    #define ENUMFIRST_ERROR(E)  E=0,
    #define ENUMMIDDLE_ERROR(E) E,
    #define ENUMLAST_ERROR(E)   E
    VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR)
    // you might undefine the 3 macros defined above
};

std::string toString(Error e)
{
    switch(e)
    {
    #define CASERETURN_ERROR(E)  case E: return #E;
    VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR)
    // you might undefine the above macro.
    // note that this will produce compile-time error for synonyms in enum;
    // handle those, if you have any, in a distinct macro

    default:
        throw my_favourite_exception();
    }
}

Перевага цього підходу полягає в тому, що: - його все ще просто зрозуміти, але - він дозволяє проводити різні відвідування (не лише рядок)

Якщо ви готові кинути перше, створіть собі макрос FOREACH (), а потім #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)напишіть відвідувачам терміни FOREACH (). Тоді спробуйте пройти огляд коду :).


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.