列挙型変数を文字列に変換する方法は?


128

enum型の変数の値を表示するようにprintfを作成するにはどうすればよいですか?例えば:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

そして私が必要なのは

printenum(OS_type, "My OS is %s", myOS);

整数ではなく文字列「Linux」を表示する必要があります。

まず、値のインデックスが付けられた文字列の配列を作成する必要があると思います。しかし、それが最も美しい方法かどうかはわかりません。まったく可能ですか?


2
選択してください:ここここここ。それは混乱です、それらはすべて半複製です。
rubenvb 14

別のもの、これはより完全です。
bit2shift 2016年


非常に多くの回答がまだ「列挙型クラス」にない
Vivick

回答:


70

これを行う美しい方法は本当にありません。enumでインデックス付けされた文字列の配列を設定するだけです。

多くの出力を行う場合は、enumパラメータを使用して検索を実行するoperator <<を定義できます。


2
また、コンパイル時に、配列に期待される数の文字列があることを確認することもできます。
markh44

2
私はこれで非常に少数派であることを知っていますが、言語に固有の欠点を解決するために大規模なサードパーティライブラリやマルコだらけのコードに依存したくない私のようなプログラマーにとって、これは今日の標準で最もシンプルで最も純粋なソリューションです。+1
Syndog 2014

13
@Syndog次に、プロダクションコード内の56の列挙子の長い列挙型が、期限切れの機能をリリースするという大きなプレッシャーのもとでこのプログラマーによって更新され、その列挙型インデックス付き配列の更新を忘れています。関連する印刷機能はアプリケーションのデバッグコードでのみ使用されるため、気付かれることはありません。2か月後、あなたは実際にそのデバッグコードを実行する最初の人です:それはあなたに間違った情報を与えます、それであなたが最初にデバッグコードをデバッグしなければならなかったことに気づく前に、この間違った情報に基づく仮定を構築する半日を失います:設計は明示的な複製に依存しています。
Ad N

1
@AdNそのデザインは間違っています。enumから人間が読める文字列へのマッピングは、enum値でインデックス付けされた文字列の配列として実装しないでください。あなたの経験は(おそらく)その理由を示しています。マッピングは(enum、string)ペアのexplyiy配列である必要があるため、新しいenum値のエントリを追加し忘れると、「???」が返されます。出力としては、少なくとも他のすべての列挙型の名前を台無しにすることはありません。
2016年

8
@AdNあなたのシナリオは、配列ではなくスイッチ(デフォルト句なし)を含む関数を好み、すべてをカバーしない列挙型のスイッチに対してエラーを発行するようにビルドファイルでコンパイラスイッチを設定する理由です可能な値。関連するswitchステートメントを更新せずに新しいenumエントリを追加すると、コンパイルエラーが発生します。
divegeek

131

もちろん、素朴な解決策は、文字列への変換を実行する列挙ごとに関数を記述することです。

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

ただし、これはメンテナンス災害です。CコードとC ++コードの両方で使用できるBoost.Preprocessorライブラリを利用すると、プリプロセッサを簡単に利用して、この関数を自動的に生成できます。生成マクロは次のとおりです。

#include <boost/preprocessor.hpp>

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

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

最初のマクロ(で始まるX_)は、2番目のマクロによって内部的に使用されます。2番目のマクロは、最初に列挙を生成し、次にToString、その型のオブジェクトを取り、列挙子名を文字列として返す関数を生成します(この実装では、明らかな理由により、列挙子が一意の値にマップする必要があります)。

C ++ では、代わりにToString関数をoperator<<オーバーロードとして実装することもできますがToString、値を文字列形式に変換するために明示的に " "を要求する方が少しクリーンだと思います。

使用例として、OS_type列挙は次のように定義されます。

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

マクロは最初は多くの作業のようにOS_type見え、定義はかなり異質に見えますが、マクロを1回記述する必要があるので、すべての列挙で使用できます。あまり問題なく追加の機能(文字列形式から列挙型への変換など)を追加でき、マクロを呼び出すときに名前を1回入力するだけでよいので、メンテナンスの問題を完全に解決します。

列挙は、通常どおり定義されているかのように使用できます。

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

この投稿のコードスニペットは、#include <boost/preprocessor.hpp>行から始まり、投稿されたとおりにコンパイルしてソリューションを示すことができます。

この特定のソリューションは、C ++固有の構文(noなどtypedef enum)と関数のオーバーロードを使用するC ++向けですが、これをCでも機能させるのは簡単です。


7
+1、実装機構は恐ろしいですが、最終的なインターフェースは優雅さを打ち負かすのが難しいです。
deft_code

4
とにかくこれを取得して、列挙型整数値を与えることもできますか?たとえば、Windowsは3、Linux 5、およびApple 7になりますか?
マーク

4
はい、に変更(Windows)して、適切に記述されたに(Windows, 3)置き換えることができます。その便利な例はありませんが、必要に応じて作成できます。BOOST_PP_SEQ_ENUMBOOST_PP_SEQ_FOR_EACH
ジェームズマクネリス

2
@JamesMcNellisマークが要求したことを実行するコードの例が間違いなく欲しいのですが、私たちに道を教えてくれませんか?:)
オメルラビブ

2
注:ブーストプリプロセッサには、256要素のハード制限があります。列挙型が大きい場合は、別のソリューションが必要です。
dshin 2015

32

これはプリプロセッサブロックです

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

列挙型の定義

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
} END_ENUM(Os_type)

使用して呼び出す

GetStringOs_type(winblows);

ここから撮影。それはどのくらいクールですか?:)


1
これは、列挙に256を超える要素がある場合に機能する唯一のソリューションです。
dshin

8

std::map<OS_type, std::string>キーとしてenumを使用し、値として文字列表現を使用して設定すると、次のようになります。

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;

7

C列挙型の問題は、C ++のように、それが独自の型ではないことです。Cの列挙型は、識別子を整数値にマッピングする方法です。それだけ。そのため、列挙値は整数値と交換可能です。

ご想像どおり、列挙値と文字列の間のマッピングを作成するのが良い方法です。例えば:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};

私は仮定-どうやら間違って-プログラミング言語がCに制限されている
アンドリュー・

1
あなたは少し離れenum ています、Cの型です。整数列挙型の定数は型intであり、enumそれらが定義されている型ではありません。おそらくあなたが言うつもりでした。しかし、これが質問とどう関係しているのかはまったくわかりません。
Jens Gustedt、2011

7

ジェームズハワードエダーのソリューションを組み合わせて、より一般的な実装を作成しました。

  • int値とカスタム文字列表現は、enum要素ごとにオプションで定義できます
  • 「列挙型クラス」を使用

完全なコードは以下のように記述されます(列挙型を定義するには「DEFINE_ENUM_CLASS_WITH_ToString_METHOD」を使用します)(オンラインデモ)。

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}

これは、これまで最高の答えです
Arnout

6

この簡単な例は私にとってうまくいきました。お役に立てれば。

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}

13
DIRECTION a = NORTHがある場合は機能しません。次にENUM_TO_STR(a)を書き込みます
mathreadler

5

これを試しましたか:

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

stringify()マクロは、文字列の中に、あなたのコード内の任意のテキストを回すために使用されますが、括弧の間の唯一の正確なテキストことができます。変数の逆参照やマクロの置換、その他の処理はありません。

http://www.cplusplus.com/forum/general/2949/


最初の1つだけで十分ですが、これは実際にはトップになります:)
pholat

正常に動作しますが、コンパイルエラーを回避するために、先頭に#ifndef stringifyを追加する必要があります。また、列挙型をstd :: stringに変更しました。これは、dgmzが提案したためです。
astarakastara

5

ここにはたくさんの良い答えがありますが、私が役立つと思う人もいると思いました。マクロを定義するために使用するインターフェイスは、可能な限りシンプルなので、気に入っています。追加のライブラリを含める必要がないため、これも便利です。すべてのライブラリにC ++が付属しており、最新バージョンを必要としません。オンラインでさまざまな場所から作品を引き出したので、すべてを信用することはできませんが、新しい答えを保証するのに十分なほどユニークだと思います。

最初にヘッダーファイルを作成します... EnumMacros.hなどの名前を付け、これを次のように記述します。

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

次に、メインプログラムでこれを行うことができます...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

出力がどこにあるか>>「Apple」の値は次のとおりです:2 of 4

楽しい!


この特定のアプローチについて私が気に入っている重要な点は、それが列挙型の通常のコンマ区切り構文で機能することです(列挙型内に値設定割り当てが含まれていない限り)。私の場合、多数のメンバーを持つ既存の列挙型を使用する必要があったため、ブーストアプローチよりもはるかに簡単に参加できました。
CuriousKea

4

enumがすでに定義されていると仮定すると、ペアの配列を作成できます。

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

これで、マップを作成できます。

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

これで、マップを使用できます。列挙型が変更された場合、配列のペア[]からペアを追加/削除する必要があります。C ++で列挙型から文字列を取得する最もエレガントな方法だと思います。


2
ここではQtは必要ないという公平なコメントは別として、bimap名前を解析してそれらを列挙型に変換したい場合(たとえば、XMLファイルから)、Boostを使用することもできます。
Dmitri Nesteruk 2014

4
必要がありません一般的なC ++質問にQtのタイプを使用すること。
2014年

3

C99の場合P99_DECLARE_ENUMP99には次のenumように宣言するだけです。

P99_DECLARE_ENUM(color, red, green, blue);

次に、を使用color_getname(A)して、色の名前を含む文字列を取得します。


2

これが私のC ++コードです:

/* 
 * File:   main.cpp
 * Author: y2k1234
 *
 * Created on June 14, 2013, 9:50 AM
 */

#include <cstdlib>
#include <stdio.h>

using namespace std;


#define MESSAGE_LIST(OPERATOR)                          \
                                       OPERATOR(MSG_A), \
                                       OPERATOR(MSG_B), \
                                       OPERATOR(MSG_C)
#define GET_LIST_VALUE_OPERATOR(msg)   ERROR_##msg##_VALUE
#define GET_LIST_SRTING_OPERATOR(msg)  "ERROR_"#msg"_NAME"

enum ErrorMessagesEnum
{
   MESSAGE_LIST(GET_LIST_VALUE_OPERATOR)
};
static const char* ErrorMessagesName[] = 
{
   MESSAGE_LIST(GET_LIST_SRTING_OPERATOR)
};

int main(int argc, char** argv) 
{

    int totalMessages = sizeof(ErrorMessagesName)/4;

    for (int i = 0; i < totalMessages; i++)
    {
        if (i == ERROR_MSG_A_VALUE)
        {
                printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_B_VALUE)
        {
                printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_C_VALUE)
        {
                printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else
        {
                printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
    }   

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)

2

パーティーには少し遅れますが、これが私のC ++ 11ソリューションです。

namespace std {
    template<> struct hash<enum_one> {
        std::size_t operator()(const enum_one & e) const {
            return static_cast<std::size_t>(e);
        }
    };
    template<> struct hash<enum_two> { //repeat for each enum type
        std::size_t operator()(const enum_two & e) const {
            return static_cast<std::size_t>(e);
        }
    };
}

const std::string & enum_name(const enum_one & e) {
    static const std::unordered_map<enum_one, const std::string> names = {
    #define v_name(n) {enum_one::n, std::string(#n)}
        v_name(value1),
        v_name(value2),
        v_name(value3)
    #undef v_name
    };
    return names.at(e);
}

const std::string & enum_name(const enum_two & e) { //repeat for each enum type
    .................
}

1
error: ‘hash’ is not a class template->#include <functional>
Ruggero

2

私自身の好みは、繰り返し入力することと理解しにくいマクロの両方を最小限に抑え、マクロ定義を一般的なコンパイラー空間に導入しないことです。

だから、ヘッダーファイルで:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

そしてcppの実装は:

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }

マクロの#undefが完了したらすぐに注意してください。


2

ブーストを使用しない私の解決策:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

#define MAKE_STRING_1(str     ) #str
#define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__)
#define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__)
#define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__)
#define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__)
#define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__)
#define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__)
#define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__)

#define PRIMITIVE_CAT(a, b) a##b
#define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N)     (__VA_ARGS__)


#define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0
#define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())

#define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ };            \
  struct NAME##_str {                                              \
    static const char * get(const NAME et) {                       \
      static const char* NAME##Str[] = {                           \
                MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) };  \
      return NAME##Str[et];                                        \
      }                                                            \
    };

#endif /* EN2STR_HXX_ */

そして、ここでそれを使用する方法です

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }

2

プリプロセッサを使用して、もう1人はパーティーに遅れました。

 1  #define MY_ENUM_LIST \
 2      DEFINE_ENUM_ELEMENT(First) \
 3      DEFINE_ENUM_ELEMENT(Second) \
 4      DEFINE_ENUM_ELEMENT(Third) \
 5  
 6  //--------------------------------------
 7  #define DEFINE_ENUM_ELEMENT(name) , name
 8  enum MyEnum {
 9      Zeroth = 0
10      MY_ENUM_LIST
11  };
12  #undef DEFINE_ENUM_ELEMENT
13 
14  #define DEFINE_ENUM_ELEMENT(name) , #name
15  const char* MyEnumToString[] = {
16      "Zeroth"
17      MY_ENUM_LIST
18  };
19  #undef DEFINE_ENUM_ELEMENT
20
21  #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name;
22  enum MyEnum StringToMyEnum(const char* s){
23      if (strcmp(s, "Zeroth")==0) return Zeroth;
24      MY_ENUM_LIST
25      return NULL;
26  }
27  #undef DEFINE_ENUM_ELEMENT

(私は話しやすいように行番号を入力しているだけです。)行1〜4は、列挙型の要素を定義するために編集するものです。(物事のリストを作成するマクロなので、「リストマクロ」と呼んでいます。@ Lundinは、これらがXマクロと呼ばれるよく知られた手法であることを知らせています。)

7行目は、8〜11行目の実際の列挙型宣言を埋めるように内部マクロを定義しています。12行目は、内部のマクロの定義を解除しています(コンパイラの警告を沈黙させるためです)。

行14では、enum要素名の文字列バージョンを作成するように内部マクロを定義しています。次に、15〜18行目で、列挙値を対応する文字列に変換できる配列を生成します。

21〜27行目では、文字列を列挙値に変換する関数を生成します。文字列がいずれとも一致しない場合はNULLを返します。

これは、0番目の要素を処理する方法が少し面倒です。私は過去に実際にそれを回避しました。

このテクニックは、プリプロセッサ自体があなたのためにコードを書くようにプログラムされていると考えたくない人々を悩ますことを認めます。読みやすさ保守性の違いがよくわかると思います。コードは読みにくいですが、列挙型に数百の要素がある場合は、要素を追加、削除、または再配置しても、生成されたコードにエラーがないことを確認できます。


「Xマクロ」が問題のエレガントな解決策になることはほとんどありません。この場合、それは単にマクロの項目を定義するためにはるかに読みやすくなり#define TEST_1 hello #define TEST_2 world、その後をtypedef enum { TEST_1, TEST_2 } test_t;して、文字列化マクロを使用して文字列のルックアップテーブルを作成します。const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; 同様のソリューションを示唆複数回答がすでにあります。はるかに読みやすいです。
ルンディン

@Lundin:私は、1)これは最も原始的なCコンパイラでも機能すること、2)要素の追加または削除は1行の編集であると主張するだけです。
Mike Dunlavey、2016年

私は自分の答えを投稿しました:stackoverflow.com/a/39877228/584518。うまくいけば、xマクロソリューションから貧しい人々を救うでしょう。
2016年

1
私はあなたの解決策を使いました。最高だと思います。C構文はまだ残っているので、何が起こるかを理解でき、リストは一度だけ定義されます。DEFINE_ENUM_ELEMENTのエントリの後にコンマを配置することにより、0番目の要素を削除できます。
送信

1

これは、Cプリプロセッサだけを使用したOld Skoolメソッド(gccで広く使用されていました)です。個別のデータ構造を生成しているが、それらの間で順序を一貫させる必要がある場合に役立ちます。もちろん、mylist.tblのエントリは、もっと複雑なものに拡張できます。

test.cpp:

enum {
#undef XX
#define XX(name, ignore) name ,
#include "mylist.tbl"
  LAST_ENUM
};

char * enum_names [] = {
#undef XX
#define XX(name, ignore) #name ,
#include "mylist.tbl"
   "LAST_ENUM"
};

そしてmylist.tbl:

/*    A = enum                  */
/*    B = some associated value */
/*     A        B   */
  XX( enum_1 , 100)
  XX( enum_2 , 100 )
  XX( enum_3 , 200 )
  XX( enum_4 , 900 )
  XX( enum_5 , 500 )

1
この手法はxマクロと呼ばれます。
ワツシモト2014年

0

このようなc ++では:

enum OS_type{Linux, Apple, Windows};

std::string ToString( const OS_type v )
{
  const std::map< OS_type, std::string > lut =
    boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows");
  std::map< OS_type, std::string >::const_iterator it = lut.find( v );
  if ( lut.end() != it )
    return it->second;
  return "NOT FOUND";
}

0
#include <EnumString.h>

http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-Cと後

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

インサート

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

列挙型の値が重複していない場合は正常に機能します。

列挙値を文字列に変換するためのサンプルコード:

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

正反対のサンプルコード:

assert( EnumString< FORM >::To( f, str ) );

0

Jamesさん、ご提案ありがとうございます。それは非常に便利だったので、何らかの方法で貢献するために逆方向に実装しました。

#include <iostream>
#include <boost/preprocessor.hpp>

using namespace std;

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

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \
    if (BOOST_PP_SEQ_TAIL(data) ==                                     \
            BOOST_PP_STRINGIZE(elem)) return                           \
            static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)         \
    enum class name {                                                  \
        BOOST_PP_SEQ_ENUM(enumerators)                                 \
    };                                                                 \
                                                                       \
    inline const char* ToString(name v)                                \
    {                                                                  \
        switch (v)                                                     \
        {                                                              \
            BOOST_PP_SEQ_FOR_EACH(                                     \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,   \
                name,                                                  \
                enumerators                                            \
            )                                                          \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";  \
        }                                                              \
    }                                                                  \
                                                                       \
    inline int ToEnum(std::string s)                                   \
    {                                                                  \
        BOOST_PP_SEQ_FOR_EACH(                                         \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF,       \
                (name)(s),                                             \
                enumerators                                            \
            )                                                          \
        return -1;                                                     \
    }


DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows));

int main(void)
{
    OS_type t = OS_type::Windows;

    cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl;

    cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl;

    return 0;
}

0

Jamesの答えを拡張するために、誰かがint値でのenum defineをサポートするいくつかのサンプルコードを望んでいます。この要件もあるので、これが私の方法です。

1つ目は、FOR_EACHで使用される内部使用マクロです。

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem)         \
    BOOST_PP_IF(                                                                \
        BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2),                           \
        BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem),            \
        BOOST_PP_TUPLE_ELEM(0, elem) ),

そして、ここに定義マクロがあります:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

だからそれを使うとき、あなたはこのように書くのが好きかもしれません:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

次のように展開されます:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

基本的な考え方は、すべての要素がTUPLEであるSEQを定義することです。これにより、列挙型メンバーに加算値を設定できます。FOR_EACHループで、アイテムのTUPLEサイズを確認します。サイズが2の場合、コードをKEY = VALUEに展開します。それ以外の場合は、TUPLEの最初の要素を保持します。

入力SEQは実際にはTUPLEであるため、STRINGIZE関数を定義する場合は、最初に入力列挙子を前処理する必要がある場合があります。これは、ジョブを実行するマクロです。

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem)           \
    BOOST_PP_TUPLE_ELEM(0, elem),

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators)         \
    BOOST_PP_SEQ_SUBSEQ(                                                        \
        BOOST_PP_TUPLE_TO_SEQ(                                                  \
            (BOOST_PP_SEQ_FOR_EACH(                                             \
                DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \
            )),                                                                 \
            0,                                                                  \
            BOOST_PP_SEQ_SIZE(enumerators))

マクロ DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQはすべてのTUPLEの最初の要素のみを保持し、後でSEQに変換し、Jamesのコードを変更します。これにより、完全な機能を利用できます。

私の実装は最も単純なものではない可能性があるため、クリーンなコードが見つからない場合は、参考にしてください。


0

純粋な標準Cのクリーンで安全なソリューション:

#include <stdio.h>

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

int main()
{  
  _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
                 "Incorrect number of items in enum or look-up table");

  printf("%d %s\n", hello, test_str[hello]);
  printf("%d %s\n", world, test_str[world]);
  test_t x = world;
  printf("%d %s\n", x, test_str[x]);

  return 0;
}

出力

0 hello
1 world
1 world

根拠

「対応する文字列を持つ列挙型定数がある」という中心的な問題を解決する場合、賢明なプログラマーは次の要件を考え出します。

  • コードの繰り返しを避けます(「DRY」の原則)。
  • enum内で項目が追加または削除された場合でも、コードはスケーラブルで保守可能で安全でなければなりません。
  • すべてのコードは高品質である必要があります。読みやすく、保守も簡単です。

最初の要件、そしておそらく2番目の要件は、悪名高い "xマクロ"トリックや他の形式のマクロマジックなど、さまざまな厄介なマクロソリューションで満たすことができます。このようなソリューションの問題は、不可解なマクロの完全に判読不能な混乱を残すことです。これらは上記の3番目の要件を満たしていません。

ここで必要なのは、実際には文字列ルックアップテーブルを作成することだけです。これには、enum変数をインデックスとして使用してアクセスできます。このようなテーブルは当然ながら列挙型に直接対応している必要があり、その逆も同様です。それらの1つが更新されると、もう1つも更新する必要があります。そうしないと機能しません。


コードの説明

次のような列挙型があるとします

typedef enum
{
  hello,
  world
} test_t;

これは次のように変更できます

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

これらのマクロ定数を他の場所で使用できるという利点があるため、たとえば、文字列ルックアップテーブルを生成できます。プリプロセッサ定数を文字列に変換するには、「stringify」マクロを使用します。

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

以上です。を使用するとhello、値0の列挙定数がtest_str[hello]取得されます。使用すると、文字列 "hello"が取得されます。

列挙型とルックアップテーブルを直接対応させるには、それらに同じ量のアイテムが含まれていることを確認する必要があります。誰かがコードを保守し、enumのみを変更し、ルックアップテーブルは変更しない場合、またはその逆の場合、このメソッドは機能しません。

解決策は、それがいくつのアイテムを含んでいるかをあなたに知らせるための列挙を持っていることです。これには一般的に使用されるCのトリックがあります。最後に項目を追加するだけです。これは、列挙型が持っている項目の数を伝える目的を満たすだけです。

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

これで、列挙型の項目の数がルックアップテーブルの項目の数と同じであることをコンパイル時に確認できます。できればC11静的アサートを使用します。

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(もし誰かが恐竜コンパイラの使用を主張した場合、C標準の古いバージョンでも静的アサートを作成するための醜いが完全に機能する方法があります。C++に関しては、静的アサートもサポートしています。)


ちなみに、C11では、stringifyマクロを変更することで、型の安全性を高めることもできます。

#define STRINGIFY(x) _Generic((x), int : STRF(x))

int列挙定数は実際にはintではなくタイプであるためtest_t

これにより、のようなコードがSTRINGIFY(random_stuff)コンパイルされなくなります。


あなたの言っていることがわかりますが、要点は残っています。典型的な予見可能な変更は、最小限の編集(1行など)を必要とします。(それがDRYの背後にある理由だと思います。)したがって、ここで、列挙型のサイズが500のようで、途中に新しい要素を挿入(または削除/名前変更/スワップ)したい場合、何行のコードが必要ですか変更し、間違いを犯していないことを確認するためにどのくらいのチェックを行う必要がありますか?リストの各要素に対して一定の処理を行うコードが他にもある場合があります。
Mike Dunlavey、2016年

これらをXマクロと呼びます。知らなかった。私が見ないのは、一般的にそれらを侮辱する人々です。
Mike Dunlavey、2016年

@MikeDunlavey列挙型のサイズに関係なく、正確に3行を変更する必要があります。を#define追加し、列挙型宣言とルックアップテーブルでその定義への参照を追加します。これらの行を追加するときに失敗する場合、プログラムはコンパイルされません。私が識別子に追加した数値は決して必須ではありません。あなたが書いて#define APPLES hello#define ORANGES worldそれに続くことtypedef enum { APPES, ORANGES, TEST_N } test_t;などもできます。
2016年

@MikeDunlavey Xマクロに関しては、それらに対する引数は、関数のようなマクロに対する引数と同じです。関数のようなマクロに対する非常に有効な批判を見つけるために、遠くを見る必要はありません。
2016年

0

私が作ったのは、私がここで見たものとこのサイトの同様の質問で見たものの組み合わせです。これはVisual Studio 2013です。他のコンパイラでテストしていません。

まず最初に、トリックを実行する一連のマクロを定義します。

// concatenation macros
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B)  CONCAT_(A, B)

// generic expansion and stringification macros
#define EXPAND(X)           X
#define STRINGIFY(ARG)      #ARG
#define EXPANDSTRING(ARG)   STRINGIFY(ARG)        

// number of arguments macros
#define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N
#define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 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))

// argument extraction macros
#define FIRST_ARG(ARG, ...) ARG
#define REST_ARGS(ARG, ...) __VA_ARGS__

// arguments to strings macros
#define ARGS_STR__(N, ...)  ARGS_STR_##N(__VA_ARGS__)
#define ARGS_STR_(N, ...)   ARGS_STR__(N, __VA_ARGS__)
#define ARGS_STR(...)       ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define ARGS_STR_1(ARG)     EXPANDSTRING(ARG)
#define ARGS_STR_2(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_3(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_4(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_5(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_6(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_7(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_8(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_9(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_10(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_11(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_12(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_13(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_14(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_15(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_16(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_17(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_18(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_19(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_20(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__)))
// expand until _100 or as much as you need

次に、列挙クラスと文字列を取得する関数を作成する単一のマクロを定義します。

#define ENUM(NAME, ...)                                                                                             \
    enum class NAME                                                                                                 \
    {                                                                                                               \
        __VA_ARGS__                                                                                                 \
    };                                                                                                              \
                                                                                                                    \
    static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) };  \
                                                                                                                    \
    inline const std::string& ToString(NAME value)                                                                  \
    {                                                                                                               \
        return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)];                         \
    }                                                                                                               \
                                                                                                                    \
    inline std::ostream& operator<<(std::ostream& os, NAME value)                                                   \
    {                                                                                                               \
        os << ToString(value);                                                                                      \
        return os;                                                                                                  \
    }

列挙型を定義し、そのための文字列を設定するのはとても簡単になります。あなたがする必要があるのは:

ENUM(MyEnumType, A, B, C);

次の行を使用してテストできます。

int main()
{
    std::cout << MyEnumTypeStrings.size() << std::endl;

    std::cout << ToString(MyEnumType::A) << std::endl;
    std::cout << ToString(MyEnumType::B) << std::endl;
    std::cout << ToString(MyEnumType::C) << std::endl;

    std::cout << MyEnumType::A << std::endl;
    std::cout << MyEnumType::B << std::endl;
    std::cout << MyEnumType::C << std::endl;

    auto myVar = MyEnumType::A;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::B;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::C;
    std::cout << myVar << std::endl;

    return 0;
}

これは出力します:

3
A
B
C
A
B
C
A
B
C

とても清潔で使いやすいと思います。いくつかの制限があります。

  • 列挙型メンバーに値を割り当てることはできません。
  • enumメンバーの値はインデックスとして使用されますが、すべてが1つのマクロで定義されているため、問題ありません。
  • これを使用して、クラス内で列挙型を定義することはできません。

これを回避できる場合。特に使い方はいいと思います。利点:

  • 使いやすい。
  • 実行時に文字列を分割する必要はありません。
  • コンパイル時に個別の文字列を使用できます。
  • 読みやすい。マクロの最初のセットは追加の1秒が必要になる場合がありますが、実際にはそれほど複雑ではありません。

0

この問題に対する明確な解決策は次のとおりです。

#define RETURN_STR(val, e) {if (val == e) {return #e;}}

std::string conv_dxgi_format_to_string(int value) {
    RETURN_STR(value, DXGI_FORMAT_UNKNOWN);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT);

    /* ... */

    return "<UNKNOWN>";
}

このソリューションの良い点は、シンプルであり、関数の構築もコピーと置換によって簡単に実行できることです。多くの変換を行う予定で、列挙型に可能な値が多すぎる場合、このソリューションはCPUに負荷がかかる可能性があることに注意してください。


0

少し遅れましたが、これがg ++と標準ライブラリのみを使用した私のソリューションです。名前空間の汚染を最小限に抑え、列挙型の名前を再入力する必要をなくすようにしました。

ヘッダーファイル「my_enum.hpp」は次のとおりです。

#include <cstring>

namespace ENUM_HELPERS{
    int replace_commas_and_spaces_with_null(char* string){
        int i, N;
        N = strlen(string);
        for(i=0; i<N; ++i){
            if( isspace(string[i]) || string[i] == ','){
                string[i]='\0';
            }
        }
        return(N);
    }

    int count_words_null_delim(char* string, int tot_N){
        int i;
        int j=0;
        char last = '\0';
        for(i=0;i<tot_N;++i){
            if((last == '\0') && (string[i]!='\0')){
                ++j;
            }
            last = string[i];
        }
        return(j);
    }

    int get_null_word_offsets(char* string, int tot_N, int current_w){
        int i;
        int j=0;
        char last = '\0';
        for(i=0; i<tot_N; ++i){
            if((last=='\0') && (string[i]!='\0')){
                if(j == current_w){
                    return(i);
                }
                ++j;
            }
            last = string[i];
        }
        return(tot_N); //null value for offset
    }

    int find_offsets(int* offsets, char* string, int tot_N, int N_words){
        int i;
        for(i=0; i<N_words; ++i){
            offsets[i] = get_null_word_offsets(string, tot_N, i);
        }
        return(0);
    }
}


#define MAKE_ENUM(NAME, ...)                                            \
namespace NAME{                                                         \
    enum ENUM {__VA_ARGS__};                                            \
    char name_holder[] = #__VA_ARGS__;                                  \
    int name_holder_N =                                                 \
        ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \
    int N =                                                             \
        ENUM_HELPERS::count_words_null_delim(                           \
            name_holder, name_holder_N);                                \
    int offsets[] = {__VA_ARGS__};                                      \
    int ZERO =                                                          \
        ENUM_HELPERS::find_offsets(                                     \
            offsets, name_holder, name_holder_N, N);                    \
    char* tostring(int i){                                              \
       return(&name_holder[offsets[i]]);                                \
    }                                                                   \
}

使用例:

#include <cstdio>
#include "my_enum.hpp"

MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS)

int main(int argc, char** argv){    
    Planets::ENUM a_planet = Planets::EARTH;
    printf("%s\n", Planets::tostring(Planets::MERCURY));
    printf("%s\n", Planets::tostring(a_planet));
}

これは出力します:

MERCURY
EARTH

すべてを一度定義するだけでよく、名前空間は汚染されるべきではなく、すべての計算は一度だけ行われます(残りは単なるルックアップです)。ただし、列挙型クラスの型安全性は得られません(それらはまだ短い整数です)。列挙型に値を割り当てることはできません。名前空間を定義できる場所(たとえばグローバル)に列挙型を定義する必要があります。

これのパフォーマンスがどれほど優れているか、それが良いアイデアかどうかはわかりません(C ++の前にCを習得したので、私の脳はまだそのように機能しています)。これが悪い考えである理由を誰かが知っている場合は、遠慮なく指摘してください。


0

それは2017年ですが、質問はまだ生きています

さらに別の方法:

#include <iostream>

#define ERROR_VALUES \
ERROR_VALUE(NO_ERROR, 0, "OK") \
ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \
ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage")

enum Error
{
#define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE,
    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, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

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

出力:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage

0
#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <algorithm>

namespace StringifyEnum
{
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { ++rit; }
    return std::string(it, rit.base());
}

static std::vector<std::string> SplitEnumArgs(const char* szArgs, int     nMax)
{
    std::vector<std::string> enums;
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        enums.push_back(StringifyEnum::TrimEnumString(strSub));
        ++nIdx;
    }
    return std::move(enums);
}    
}

#define DECLARE_ENUM_SEQ(ename, n, ...) \
    enum class ename { __VA_ARGS__ }; \
    const int MAX_NUMBER_OF_##ename(n); \
    static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
    inline static std::string ename##ToString(ename e) { \
        return ename##Strings.at((int)e); \
    } \
    inline static ename StringTo##ename(const std::string& en) { \
        const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
        if (it != ename##Strings.end()) \
            return (ename) std::distance(ename##Strings.begin(), it); \
        throw std::runtime_error("Could not resolve string enum value");     \
    }

これは詳細なクラス拡張列挙型バージョンです...提供されているもの以外の列挙値は追加されません。

使用法:DECLARE_ENUM_SEQ(CameraMode、(3)、Fly、FirstPerson、PerspectiveCorrect)


0

私はこれを双方向で機能させるために必要であり、列挙型を包含クラス内に頻繁に埋め込むため、これらの回答の一番上にあるJames McNellisの方法による解決策から始めましたが、私はこの解決策を作りました。また、私は列挙型だけではなく列挙型クラスを好むことに注意してください。

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

クラス内で使用するには、次のようにします。

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

そして、それを使用する方法を示すCppUnitテストを作成しました。

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

使用するマクロ(DEFINE_ENUMERATIONまたはDEFINE_ENUMERATION_INSIDE_CLASS)を選択する必要があります。ComponentStatus :: Statusを定義するときに後者を使用したことがわかりますが、Statusを定義するときに前者を使用しました。違いは簡単です。クラス内では、to / fromメソッドに「静的」というプレフィックスを付け、クラス内にない場合は「インライン」を使用します。些細な違いですが、必要です。

残念ながら、これを回避するための明確な方法はないと思います。

const char * valueStr = ComponentStatus::ToString(value);

ただし、クラス定義の後にインラインメソッドを手動で作成することもできますが、これは次のようなクラスメソッドにチェーンするだけです。

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }

0

ブーストを使用せずに私自身の答え-重い定義の魔法なしで私の独自のアプローチを使用し、このソリューションには特定の列挙値を定義できないという制限があります。

#pragma once
#include <string>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name> {                                         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

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

    WARNING: At the moment assigning enum value to specific number is not supported.
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = (int)t;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (id == 0)
            return std::string(token, next);
        id--;
    } while (*next != 0);

    return std::string();
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = 0;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (strncmp(token, enumName, next - token) == 0)
        {
            t = (T)id;
            return true;
        }

        id++;
    } while (*next != 0);

    return false;
}

最新バージョンはgithubの次の場所にあります。

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


0

これには他にも多くの答えがありますが、C ++ 17機能を使用し、翻訳がコンパイル時に行われるようにconstexprを使用する方がより良い方法だと思います。これはタイプセーフであり、マクロをいじる必要はありません。下記参照:

//enum.hpp
#include <array>
#include <string_view>

namespace Enum
{

template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Value not in map":
        (std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
        findKey(value, map, index - 1);
};

template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Key not in map":
        (map[index - 1].first == key) ? map[index- 1].second:
        findValue(key, map, index - 1);
};

}

//test_enum.hpp
#include "enum.hpp"

namespace TestEnum
{
    enum class Fields
    {
        Test1,
        Test2,
        Test3,
        //This has to be at the end
        NUMBER_OF_FIELDS
    };

    constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
    {
        std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
        {
            {
                    {Fields::Test1, "Test1"},
                    {Fields::Test2, "Test2"},
                    {Fields::Test3, "Test3"},
            }
        };
        return map;
    };

    constexpr Fields StringToEnum(const char * value)
    {
        return Enum::findKey(value, GetMap());
    }

    constexpr const char * EnumToString(Fields key)
    {
        return Enum::findValue(key, GetMap());
    }

}

これは簡単に使用できるため、コンパイル時に文字列キーエラーが検出されます。

#include "test_enum.hpp"

int main()
{
    auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
    auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
    auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
    return 0;
}

コードは他のいくつかのソリューションよりも詳細ですが、コンパイル時にEnumからStringへの変換およびStringからEnumへの変換を簡単に実行して、型エラーを検出できます。将来のC ++ 20機能のいくつかで、これはおそらくもう少し単純化することができます。

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