C ++:列挙値をテキストとして出力します


88

このような列挙型がある場合

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。ハッシュマップを使用する代わりに、switch-caseを実行するだけです。複雑さが増すのは良いことではありません。
サイモン

8
いい視点ね。次回は:)しかし、あなたはすでに投稿を編集して、私が探していた種類の機能を追加していることがわかりました。よくやった!
サイモン

1
#pとは何ですか?3番目の例でenumの代わりにenumクラスを使用する場合、クラス名なしでenum文字列のみを取得することは可能ですか?
rh0x 2016年

2
#ppを文字列化するプリプロセッサです。したがって、呼び出しPROCESS_VAL(ErrorA)は次のように出力されますcase(ErrorA): s = "ErrorA"; break;
ナシェナス2016

私は、最適なソリューションとして、それを考慮していない:理由:1)私は維持する必要が二回enum、私はあると思い値NO-GOを。2)私が解決策を正しく理解しているとき、それは1つだけで機能しenumます。
Peter VARGA 2017年

28

値が一致する文字列の配列またはベクトルを使用します。

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

cout << ErrorTypes[anError];

編集:上記の解決策は、列挙型が連続している場合、つまり0から始まり、割り当てられた値がない場合に適用できます。問題の列挙型で完全に機能します。

列挙型が0から始まらない場合にそれをさらに証明するには、次を使用します。

cout << ErrorTypes[anError - ErrorA];

4
残念ながら、enumでは要素に値を割り当てることができます。非連続列挙型、行 'enum Status {OK = 0、Fail = -1、OutOfMemory = -2、IOError = -1000、ConversionError = -2000} `がある場合、どのように作業にアプローチしますか(後でIOErrorsを追加できます) -1001-1999の範囲まで)
北欧メインフレーム

@Luther:はい、これは隣接する列挙型でのみ機能しますほとんどの列挙型はです。列挙型が連続していない場合は、別のアプローチ、つまりマップを使用する必要があります。ただし、連続した列挙型の場合は、過度に複雑にしないで、このアプローチを使用することをお勧めします。
Igor Oks 2010

2
したがって、同僚がNewValueを列挙型に追加し、ErrorTypes配列を更新しない場合、ErrorTypes [NewValue]は何を生成しますか?また、負の列挙値を処理するにはどうすればよいですか?
北欧メインフレーム2010

2
@Luther:ErrorTypesを最新の状態に保つ必要があります。繰り返しますが、シンプルさと普遍性の間にはトレードオフがあり、ユーザーにとって何がより重要かによって異なります。負の列挙値の問題は何ですか?
Igor Oks 2010

1
この配列はメモリ効率のために静的であるべきではありませんか?安全のためにconst?
ジョナサン

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 ++であり、DRYの原則に従い、ユーザー構文は読み取り可能です(正しくフォーマットされている場合。ところで、バックスラッシュは必要ありません)。もう少し自然に見えるDEFINE_ENUMを使用する場合、IMO)
Fabio

3
@Fabio Fracassi:「このソリューションは外部ツールに依存していません」Boostは外部ツールであり、非標準のC ++ライブラリです。その上、それは少し長すぎます。問題の解決策はできるだけ簡単にする必要があります。この一つは...資格はありません
SIGTERM

2
実際には、ほとんどのコード(実際には、実際の定義を除くすべて)を単一のヘッダーに入れることができるのはそれだけです。したがって、これは実際にここに示されている最短のソリューションです。そして、ブーストが外部であるためには、そうですが、上記のluaスクリプトのように、ソースの一部を前処理するための言語外スクリプトほどではありません。さらに、ブーストは標準に非常に近いため、すべてのC ++プログラマーツールボックスに含まれているはずです。もちろん、私見だけです
Fabio Fracassi 2010

[マクロ呼び出しでの不要な改行のエスケープを削除しました。それらは必要ありません。マクロ呼び出しは複数行にまたがることができます。]
James McNellis 2011

マクロを使おうとするとDEFINE_ENUM、エラーが発生multiple definition of `format_ProgramStatus(ProgramStatus)'します。
ハローグッバイ

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 ++列挙型を文字列に変換する簡単な方法はありますか?

更新: LuaのHere#saスクリプトは、検出した名前付き列挙型ごとに演算子<<を作成します。これは、それほど単純ではないケースで機能させるために、いくつかの作業が必要になる場合があります[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
以下のBoostPPソリューション(Philip製)の方が優れていると思います。外部ツールを使用すると、メンテナンスに非常に費用がかかるためです。しかし、答えは他の点では有効であるため、-1はありません
Fabio Fracassi 2010

4
Boost PPメンテナンスの問題でもあります。これは、Boost PPメタ言語を全員が話す必要があるためです。これは、ひどく、壊れやすく(通常は使用できないエラーメッセージが表示されます)、使い勝手が限られています(lua / python / perlは任意のコードを生成できます)。外部データ)。それはあなたの依存関係リストにブーストを追加します、それはプロジェクトポリシーのために許可されないかもしれません。また、DSLで列挙型を定義する必要があるため、侵襲的です。お気に入りのソースコードツールまたはIDEで問題が発生する可能性があります。最後になりましたが、拡張にブレークポイントを設定することはできません。
北欧メインフレーム2010

4

列挙型を定義するときは常に文字列配列を使用します。

Profile.h

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

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マップコンテナを使用できます。

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)ルックアップがあり、初期化する必要がないのはどれですか?または、列挙型は実行時に何らかの形で変更されますか?
北欧メインフレーム2010

switchステートメント(または関数ポインターも)の使用について@Luther Blissettに同意します
KedarX 2010

1
「これは私の親愛なる友人のルーサーはエラーAです」または「これは私の親愛なる友人のエイドリアンはエラーBです」を出力したい場合があります。また、マップを使用すると、iostream署名への依存がなくなり、たとえば、string x = "Hello" + m [ErrorA]などの文字列連結を含むコード
Adrian Regan

std :: mapには多くのifとスイッチが含まれていると確信しています。私は「私はせずにこれを行うことができる方法として、これを読んでいました、私を持つのとスイッチあれば書いて」
北欧のメインフレーム

...私は確かにそれがないんだけど、それは確かに問題を解決するための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を見つけることはPITAになります。次のように、最初の列挙型の数値を手動で指定できます。

#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()で記述します。次に、コードレビューに合格してみてください:)。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.