例は長い説明よりも優れています。
このスニペットは Coliru でコンパイルして実行できます。
( 別の前の例 も利用可能です)
#include <map>
#include <iostream>
struct MyClass
{
enum class MyEnum : char {
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
};
// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
{ MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
{ MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
{ MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
};
auto it = MyEnumStrings.find(e);
return it == MyEnumStrings.end() ? "Out of range" : it->second;
}
int main()
{
std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}
#define
のオーバーヘッドをできるだけ少なくするようにしてください。enum
- > string
マッピングはしないでください。enum
値をサポートenum
値をサポートenum
値をサポートclass enum
をサポート(C++ 11)class enum : <type>
が許可された<type>
を持つことをサポートします。(C++ 11)std::map
はお勧めできません...)constexpr
(C++ 11、C++ 14で緩和)noexcept
(C++ 11)考えられる1つの考えは、variadic template class
およびconstexpr
関数に基づくメタプログラミングトリックを使用して、コンパイル時にC++コードを生成するC++コンパイラ機能を使用することです。
これはYuri Finkelsteinに似ています。ブーストは必要ありません。マップを使用しているので、列挙型に任意の値、任意の順序を割り当てることができます。
Enumクラスの宣言:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
次のコードは自動的にenumクラスとオーバーロードを作成します。
ブーストは不要で、すべての必須機能が提供されています。
コード:
#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())
std::vector<std::string> splitString(std::string str, char sep = ',') {
std::vector<std::string> vecString;
std::string item;
std::stringstream stringStream(str);
while (std::getline(stringStream, item, sep))
{
vecString.Push_back(item);
}
return vecString;
}
#define DECLARE_ENUM_WITH_TYPE(E, T, ...) \
enum class E : T \
{ \
__VA_ARGS__ \
}; \
std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__)); \
std::ostream &operator<<(std::ostream &os, E enumTmp) \
{ \
os << E##MapName[static_cast<T>(enumTmp)]; \
return os; \
} \
size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); } \
std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; } \
std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
std::string &operator+=(std::string &str, E enumTmp) \
{ \
str += E##MapName[static_cast<T>(enumTmp)]; \
return str; \
} \
E operator++(E &enumTmp) \
{ \
auto iter = E##MapName.find(static_cast<T>(enumTmp)); \
if (iter == E##MapName.end() || std::next(iter) == E##MapName.end()) \
iter = E##MapName.begin(); \
else \
{ \
++iter; \
} \
enumTmp = static_cast<E>(iter->first); \
return enumTmp; \
} \
bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }
#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
STRING_REMOVE_CHAR(strMap, ' ');
STRING_REMOVE_CHAR(strMap, '(');
std::vector<std::string> enumTokens(splitString(strMap));
std::map<T, std::string> retMap;
T inxMap;
inxMap = 0;
for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
{
// Token: [EnumName | EnumName=EnumValue]
std::string enumName;
T enumValue;
if (iter->find('=') == std::string::npos)
{
enumName = *iter;
}
else
{
std::vector<std::string> enumNameValue(splitString(*iter, '='));
enumName = enumNameValue[0];
//inxMap = static_cast<T>(enumNameValue[1]);
if (std::is_unsigned<T>::value)
{
inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
}
else
{
inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
}
}
retMap[inxMap++] = enumName;
}
return retMap;
}
例:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
int main(void) {
TestEnumClass first, second;
first = TestEnumClass::FOUR;
second = TestEnumClass::TWO;
std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)
std::string strOne;
strOne = ~first;
std::cout << strOne << std::endl; // FOUR
std::string strTwo;
strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
std::cout << strTwo << std::endl; // Enum-TWOTHREE-test
std::string strThree("TestEnumClass: ");
strThree += second;
std::cout << strThree << std::endl; // TestEnumClass: TWO
std::cout << "Enum count=" << *first << std::endl;
}
You can run the code here
( better_enums ライブラリのアプローチ)
現在のC++では、文字列をenumする方法は次のようになります。
ENUM(Channel, char, Red = 1, Green, Blue)
// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };
使用法:
Channel c = Channel::_from_string("Green"); // Channel::Green (2)
c._to_string(); // string "Green"
for (Channel c : Channel::_values())
std::cout << c << std::endl;
// And so on...
すべての操作はconstexpr
にすることができます。 @ecatmurによる回答に記載されているC++ 17リフレクションの提案を実装することもできます。
#
)がトークンを文字列に変換する唯一の方法であるため、これは最小限の可能性があると思います。constexpr
のため、少なくともC++ 11では最も自然で便利です。 C++ 98 + __VA_ARGS__
でも動作するようにすることもできます。それは間違いなく現代的なC++です。マクロの定義は多少複雑なので、いくつかの方法で答えています。
この答えをライブラリの機能に拡張するのは簡単です - ここで「重要な」ことは何も除外されません。しかし、それは非常に面倒であり、コンパイラの移植性の問題があります。
免責事項 :私はCodeProjectの記事とライブラリの両方の作者です。
あなたは この答えのコード 、 library 、そして N4428 の実装はWandboxでオンラインでライブ。)ライブラリのドキュメントには N4428としての使い方の概要も含まれています。 、これはその提案の列挙型部分を説明しています。
以下のコードは列挙型と文字列の間の変換を実装しています。ただし、反復など、他のことを実行するように拡張することもできます。この答えは列挙型をstruct
にラップします。代わりに、列挙型と一緒に特性struct
を生成することもできます。
戦略は次のようなものを生成することです。
struct Channel {
enum _enum : char { __VA_ARGS__ };
constexpr static const Channel _values[] = { __VA_ARGS__ };
constexpr static const char * const _names[] = { #__VA_ARGS__ };
static const char* _to_string(Channel v) { /* easy */ }
constexpr static Channel _from_string(const char *s) { /* easy */ }
};
問題は次のとおりです。
{Red = 1, Green, Blue}
のようなものになってしまいます。 Red
は代入可能な式ではないため、これは有効なC++ではありません。これは、各定数を代入演算子を持つT
型にキャストすることで解決されますが、代入は破棄されます:{(T)Red = 1, (T)Green, (T)Blue}
。{"Red = 1", "Green", "Blue"}
を使います。 " = 1"
を切り捨てる必要があります。私はコンパイル時にこれを実行するための素晴らしい方法を知らないので、実行時にこれを延期します。その結果、_to_string
はconstexpr
にはなりませんが、_from_string
は依然としてconstexpr
になることができます。__VA_ARGS__
の各要素に別のマクロを適用できる "マッピング"マクロが必要です。これはかなり標準的です。この答えは8つまでの要素を扱うことができる簡単なバージョンを含みます。constexpr
(または単にconst
)配列、またはnon -constexpr
静的インライン関数の通常の配列です。この答えのコードはC++ 11用で、前者のアプローチを取ります。 CodeProjectの記事はC++ 98用で、後者を取り上げています。#include <cstddef> // For size_t.
#include <cstring> // For strcspn, strncpy.
#include <stdexcept> // For runtime_error.
// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
IDENTITY( \
APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
(macro, __VA_ARGS__))
#define CHOOSE_MAP_START(count) MAP ## count
#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))
#define IDENTITY(x) x
#define MAP1(m, x) m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))
#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
count
#define COUNT(...) \
IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))
// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
constexpr explicit ignore_assign(U value) : _value(value) { }
constexpr operator U() const { return _value; }
constexpr const ignore_assign& operator =(int dummy) const
{ return *this; }
U _value;
};
// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))
// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))
// Some helpers needed for _from_string.
constexpr const char terminators[] = " =\t\r\n";
// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
return
index >= sizeof(terminators) ? false :
c == terminators[index] ? true :
is_terminator(c, index + 1);
}
constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
size_t index = 0)
{
return
is_terminator(untrimmed[index]) ? s[index] == '\0' :
s[index] != untrimmed[index] ? false :
matches_untrimmed(untrimmed, s, index + 1);
}
// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.
#define ENUM(EnumName, Underlying, ...) \
namespace data_ ## EnumName { \
using _underlying = Underlying; \
enum { __VA_ARGS__ }; \
\
constexpr const size_t _size = \
IDENTITY(COUNT(__VA_ARGS__)); \
\
constexpr const _underlying _values[] = \
{ IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) }; \
\
constexpr const char * const _raw_names[] = \
{ IDENTITY(STRINGIZE(__VA_ARGS__)) }; \
} \
\
struct EnumName { \
using _underlying = Underlying; \
enum _enum : _underlying { __VA_ARGS__ }; \
\
const char * _to_string() const \
{ \
for (size_t index = 0; index < data_ ## EnumName::_size; \
++index) { \
\
if (data_ ## EnumName::_values[index] == _value) \
return _trimmed_names()[index]; \
} \
\
throw std::runtime_error("invalid value"); \
} \
\
constexpr static EnumName _from_string(const char *s, \
size_t index = 0) \
{ \
return \
index >= data_ ## EnumName::_size ? \
throw std::runtime_error("invalid identifier") : \
matches_untrimmed( \
data_ ## EnumName::_raw_names[index], s) ? \
(EnumName)(_enum)data_ ## EnumName::_values[ \
index] : \
_from_string(s, index + 1); \
} \
\
EnumName() = delete; \
constexpr EnumName(_enum value) : _value(value) { } \
constexpr operator _enum() const { return (_enum)_value; } \
\
private: \
_underlying _value; \
\
static const char * const * _trimmed_names() \
{ \
static char *the_names[data_ ## EnumName::_size]; \
static bool initialized = false; \
\
if (!initialized) { \
for (size_t index = 0; index < data_ ## EnumName::_size; \
++index) { \
\
size_t length = \
std::strcspn(data_ ## EnumName::_raw_names[index],\
terminators); \
\
the_names[index] = new char[length + 1]; \
\
std::strncpy(the_names[index], \
data_ ## EnumName::_raw_names[index], \
length); \
the_names[index][length] = '\0'; \
} \
\
initialized = true; \
} \
\
return the_names; \
} \
};
そして
// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"
ENUM(Channel, char, Red = 1, Green, Blue)
constexpr Channel channel = Channel::_from_string("Red");
int main()
{
std::cout << channel._to_string() << std::endl;
switch (channel) {
case Channel::Red: return 0;
case Channel::Green: return 1;
case Channel::Blue: return 2;
}
}
static_assert(sizeof(Channel) == sizeof(char), "");
あなたが期待するように、上記のプログラムはRed
を出力します。初期化せずにenumを作成することはできず、switch
からケースの1つを削除すると(コンパイラーとフラグによっては)コンパイラーから警告が出されるので、ある程度のタイプの安全性があります。また、"Red"
はコンパイル中にenumに変換されたことに注意してください。
にとって C++ 17 C++ 20では、Reflection Study Group(SG7)の研究に興味があるでしょう。 文言 ( P0194 )および 論理的根拠、設計および進化 ( P0385 )をカバーする一連の平行した論文があります。 (リンクは各シリーズの最新の論文に解決されます。)
P0194r2(2016-10-15)の時点で、構文は提案されたreflexpr
キーワードを使用するでしょう:
meta::get_base_name_v<
meta::get_element_m<
meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>
例えば( Matus Choclikによるclangのreflexprブランチ から変更):
#include <reflexpr>
#include <iostream>
enum MyEnum { AAA = 1, BBB, CCC = 99 };
int main()
{
auto name_of_MyEnum_0 =
std::meta::get_base_name_v<
std::meta::get_element_m<
std::meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>;
// prints "AAA"
std::cout << name_of_MyEnum_0 << std::endl;
}
静的リフレクションはC++ 17にはなりませんでした(むしろ、2016年11月のイサクアでの標準化会議で発表された、おそらく最後のドラフトには入りました)が、C++ 20になると確信しています。から ハーブサッターの旅行レポート :
特に、 Reflection スタディグループは、最新のマージされた静的リフレクションの提案を検討し、次回の会議でメインのEvolutionグループに参加してTSまたは次の規格の統一静的リフレクションの提案を検討する準備を整えました。 。
2011年に戻って、私はマクロベースのソリューションを微調整するために週末を費やしました そしてそれを使わないことになりました。
私の現在の手順は、Vimを起動し、空のswitch本体に列挙子をコピーし、新しいマクロを起動し、最初の列挙子をcase文に変換する、 カーソルを次の行の先頭に移動する、 stop他の列挙子でマクロを実行して、残りのcaseステートメントを生成します。
VimマクロはC++マクロよりも楽しいです。
実際の例:
enum class EtherType : uint16_t
{
ARP = 0x0806,
IPv4 = 0x0800,
VLAN = 0x8100,
IPv6 = 0x86DD
};
私はこれを作成します。
std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
switch (ethertype)
{
case EtherType::ARP : return os << "ARP" ;
case EtherType::IPv4: return os << "IPv4";
case EtherType::VLAN: return os << "VLAN";
case EtherType::IPv6: return os << "IPv6";
// omit default case to trigger compiler warning for missing cases
};
return os << static_cast<std::uint16_t>(ethertype);
}
そしてそれは私がどのようにして得るかです。
列挙型文字列化のネイティブサポートは、はるかに優れているでしょう。 C++ 17のリフレクションワークグループの結果を見ることに非常に興味があります。
別の方法として@seheの コメント に投稿されました。
これが気に入らないかどうかはわかりませんが、このソリューションにはあまり満足できませんが、テンプレート変数を使用してテンプレートの特殊化を悪用しているため、C++ 14にやさしいアプローチです。
enum class MyEnum : std::uint_fast8_t {
AAA,
BBB,
CCC,
};
template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";
int main()
{
// Prints "AAA"
std::cout << MyEnumName<MyEnum::AAA> << '\n';
// Prints "Invalid MyEnum value"
std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
// Well... in fact it prints "Invalid MyEnum value" for any value
// different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.
return 0;
}
このアプローチの最悪の点は、維持するのが面倒ですが、他の似たようなアプローチのいくつかを維持するのも面倒ですね。
このアプローチについての良い点:
怪しい ser673679 その通りです。 C++ 14変数テンプレートのアプローチは実行時のケースを処理しません、それを忘れて私のせいでした:(
しかし、enum値から文字列への実行時変換を実現するために、現代のC++機能と変数テンプレートと可変テンプレートのトリックを使用することができます。
Enum-to-stringマップへのアクセスを短くするためにテンプレートエイリアスを使い始めましょう:
// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;
// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};
それから、可変個のテンプレートのトリック:
template <typename ENUM>
void initialize() {}
template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
enum_values<ENUM>.emplace(value, name);
initialize<ENUM>(tail ...);
}
"ベストトリック"はそれぞれのenumエントリの値と名前を含むマップのための可変テンプレートの使用です。このマップは各翻訳単位で同じになり、至る所で同じ名前を持つことになるので、initialize
関数を次のように呼び出すと、非常に簡単でわかりやすくなります。
initialize
(
MyEnum::AAA, "AAA",
MyEnum::BBB, "BBB",
MyEnum::CCC, "CCC"
);
それぞれのMyEnum
エントリに名前を割り当てており、実行時に使用できます。
std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';
しかし、SFINAEと<<
演算子のオーバーロードで改善することができます。
template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
auto found = enum_values<ENUM>.find(value);
return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}
正しいoperator <<
を使えば、このようにenumを使うことができます。
std::cout << MyEnum::AAA << '\n';
これも維持するのが面倒で改善することができますが、あなたがアイデアを得ることを願っています。
あなたのenum
が以下のように見える場合
enum MyEnum
{
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
enum
の内容を新しいファイルに移動することができます。
AAA = -8,
BBB = '8',
CCC = AAA + BBB
そして値はマクロで囲むことができます。
// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif
// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)
// clean up
#undef ITEM
次のステップでは、enum
にアイテムを再び含めることができます。
enum MyEnum
{
#define ITEM(X,Y) X=Y,
#include "enum_definition_file"
};
そして最後に、このenum
に関する効用関数を生成することができます。
std::string ToString(MyEnum value)
{
switch( value )
{
#define ITEM(X,Y) case X: return #X;
#include "enum_definition_file"
}
return "";
}
MyEnum FromString(std::string const& value)
{
static std::map<std::string,MyEnum> converter
{
#define ITEM(X,Y) { #X, X },
#include "enum_definition_file"
};
auto it = converter.find(value);
if( it != converter.end() )
return it->second;
else
throw std::runtime_error("Value is missing");
}
このソリューションは古いC++標準にも適用でき、最新のC++要素は使用しませんが、あまり手間とメンテナンスを必要とせずに多数のコードを生成するために使用できます。
私は数日前に同じ問題を抱えていました。奇妙なマクロの魔法がなければC++の解決策を見つけることができなかったので、単純なswitch case文を生成するために CMakeコードジェネレータ を書くことにしました。
使用法:
enum2str_generate(
PATH <path to place the files in>
CLASS_NAME <name of the class (also prefix for the files)>
FUNC_NAME <name of the (static) member function>
NAMESPACE <the class will be inside this namespace>
INCLUDES <LIST of files where the enums are defined>
ENUMS <LIST of enums to process>
BLACKLIST <LIST of constants to ignore>
USE_CONSTEXPR <whether to use constexpr or not (default: off)>
USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)
この関数はファイルシステム内のインクルードファイルを検索し(include_directoriesコマンドで提供されたインクルードディレクトリを使用します)、それらを読み込み、クラスと関数を生成するためにいくつかの正規表現を行います。
注意:constexprはC++ではインラインを意味するので、USE_CONSTEXPRオプションを使うとヘッダーのみのクラスが生成されます。
例:
./includes/a.h:
enum AAA : char { A1, A2 };
typedef enum {
VAL1 = 0,
VAL2 = 1,
VAL3 = 2,
VAL_FIRST = VAL1, // Ignored
VAL_LAST = VAL3, // Ignored
VAL_DUPLICATE = 1, // Ignored
VAL_STRANGE = VAL2 + 1 // Must be blacklisted
} BBB;
./CMakeLists.txt:
include_directories( ${PROJECT_SOURCE_DIR}/includes ...)
enum2str_generate(
PATH "${PROJECT_SOURCE_DIR}"
CLASS_NAME "enum2Str"
NAMESPACE "abc"
FUNC_NAME "toStr"
INCLUDES "a.h" # WITHOUT directory
ENUMS "AAA" "BBB"
BLACKLIST "VAL_STRANGE")
生成します。
./enum2Str.hpp:
/*!
* \file enum2Str.hpp
* \warning This is an automatically generated file!
*/
#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP
#include <string>
#include <a.h>
namespace abc {
class enum2Str {
public:
static std::string toStr( AAA _var ) noexcept;
static std::string toStr( BBB _var ) noexcept;
};
}
#endif // ENUM2STR_HPP
./enum2Str.cpp:
/*!
* \file enum2Str.cpp
* \warning This is an automatically generated file!
*/
#include "enum2Str.hpp"
namespace abc {
/*!
* \brief Converts the enum AAA to a std::string
* \param _var The enum value to convert
* \returns _var converted to a std::string
*/
std::string enum2Str::toStr( AAA _var ) noexcept {
switch ( _var ) {
case A1: return "A1";
case A2: return "A2";
default: return "<UNKNOWN>";
}
}
/*!
* \brief Converts the enum BBB to a std::string
* \param _var The enum value to convert
* \returns _var converted to a std::string
*/
std::string enum2Str::toStr( BBB _var ) noexcept {
switch ( _var ) {
case VAL1: return "VAL1";
case VAL2: return "VAL2";
case VAL3: return "VAL3";
default: return "<UNKNOWN>";
}
}
}
このスクリプトはスコープ付き列挙(enum class | struct)もサポートしています。私はよく使用する他のスクリプトとは別のリポジトリに移動しました。 https://github.com/mensinda/cmakeBuildTools
列挙型を生成するだけです。そのためのジェネレータを書くのは約5分の作業です。
Javaおよびpythonのジェネレータコード。C++を含む、好きな言語に簡単に移植できます。
あなたが望むどんな機能でも拡張するのはとても簡単。
入力例:
First = 5
Second
Third = 7
Fourth
Fifth=11
生成されたヘッダ:
#include <iosfwd>
enum class Hallo
{
First = 5,
Second = 6,
Third = 7,
Fourth = 8,
Fifth = 11
};
std::ostream & operator << (std::ostream &, const Hallo&);
生成されたcppファイル
#include <ostream>
#include "Hallo.h"
std::ostream & operator << (std::ostream &out, const Hallo&value)
{
switch(value)
{
case Hallo::First:
out << "First";
break;
case Hallo::Second:
out << "Second";
break;
case Hallo::Third:
out << "Third";
break;
case Hallo::Fourth:
out << "Fourth";
break;
case Hallo::Fifth:
out << "Fifth";
break;
default:
out << "<unknown>";
}
return out;
}
そしてジェネレータは、移植と拡張のためのテンプレートとして非常に簡潔な形で。このサンプルコードは、実際にはファイルの上書きを避けようとしますが、それでも自分の責任でそれを使用します。
package cppgen;
import Java.io.BufferedReader;
import Java.io.File;
import Java.io.FileInputStream;
import Java.io.FileOutputStream;
import Java.io.InputStreamReader;
import Java.io.OutputStreamWriter;
import Java.io.PrintWriter;
import Java.nio.charset.Charset;
import Java.util.LinkedHashMap;
import Java.util.Map;
import Java.util.Map.Entry;
import Java.util.regex.Matcher;
import Java.util.regex.Pattern;
public class EnumGenerator
{
static void fail(String message)
{
System.err.println(message);
System.exit(1);
}
static void run(String[] args)
throws Exception
{
Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
Charset charset = Charset.forName("UTF8");
String tab = " ";
if (args.length != 3)
{
fail("Required arguments: <enum name> <input file> <output dir>");
}
String enumName = args[0];
File inputFile = new File(args[1]);
if (inputFile.isFile() == false)
{
fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
}
File outputDir = new File(args[2]);
if (outputDir.isDirectory() == false)
{
fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
}
File headerFile = new File(outputDir, enumName + ".h");
File codeFile = new File(outputDir, enumName + ".cpp");
for (File file : new File[] { headerFile, codeFile })
{
if (file.exists())
{
fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
}
}
int nextValue = 0;
Map<String, Integer> fields = new LinkedHashMap<>();
try
(
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
)
{
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
if (line.trim().length() == 0)
{
continue;
}
Matcher matcher = pattern.matcher(line);
if (matcher.matches() == false)
{
fail("Syntax error: [" + line + "]");
}
String fieldName = matcher.group(1);
if (fields.containsKey(fieldName))
{
fail("Double fiend name: " + fieldName);
}
String valueString = matcher.group(2);
if (valueString != null)
{
int value = Integer.parseInt(valueString);
if (value < nextValue)
{
fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
}
nextValue = value;
}
fields.put(fieldName, nextValue);
++nextValue;
}
}
try
(
PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
)
{
headerWriter.println();
headerWriter.println("#include <iosfwd>");
headerWriter.println();
headerWriter.println("enum class " + enumName);
headerWriter.println('{');
boolean first = true;
for (Entry<String, Integer> entry : fields.entrySet())
{
if (first == false)
{
headerWriter.println(",");
}
headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());
first = false;
}
if (first == false)
{
headerWriter.println();
}
headerWriter.println("};");
headerWriter.println();
headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
headerWriter.println();
codeWriter.println();
codeWriter.println("#include <ostream>");
codeWriter.println();
codeWriter.println("#include \"" + enumName + ".h\"");
codeWriter.println();
codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
codeWriter.println('{');
codeWriter.println(tab + "switch(value)");
codeWriter.println(tab + '{');
first = true;
for (Entry<String, Integer> entry : fields.entrySet())
{
codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
codeWriter.println(tab + tab + "break;");
first = false;
}
codeWriter.println(tab + "default:");
codeWriter.println(tab + tab + "out << \"<unknown>\";");
codeWriter.println(tab + '}');
codeWriter.println();
codeWriter.println(tab + "return out;");
codeWriter.println('}');
codeWriter.println();
}
}
public static void main(String[] args)
{
try
{
run(args);
}
catch(Exception exc)
{
exc.printStackTrace();
System.exit(1);
}
}
}
そしてPython 3.5への移植は、潜在的に役立つように十分に異なっているので
import re
import collections
import sys
import io
import os
def fail(*args):
print(*args)
exit(1)
pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = " "
if len(sys.argv) != 4:
n=0
for arg in sys.argv:
print("arg", n, ":", arg, " / ", sys.argv[n])
n += 1
fail("Required arguments: <enum name> <input file> <output dir>")
enumName = sys.argv[1]
inputFile = sys.argv[2]
if not os.path.isfile(inputFile):
fail("Not a file: [" + os.path.abspath(inputFile) + "]")
outputDir = sys.argv[3]
if not os.path.isdir(outputDir):
fail("Not a directory: [" + os.path.abspath(outputDir) + "]")
headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")
for file in [ headerFile, codeFile ]:
if os.path.exists(file):
fail("Will not overwrite file [" + os.path.abspath(file) + "]")
nextValue = 0
fields = collections.OrderedDict()
for line in open(inputFile, 'r'):
line = line.strip()
if len(line) == 0:
continue
match = pattern.match(line)
if match == None:
fail("Syntax error: [" + line + "]")
fieldName = match.group(1)
if fieldName in fields:
fail("Double field name: " + fieldName)
valueString = match.group(2)
if valueString != None:
value = int(valueString)
if value < nextValue:
fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)
nextValue = value
fields[fieldName] = nextValue
nextValue += 1
headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')
try:
headerWriter.write("\n")
headerWriter.write("#include <iosfwd>\n")
headerWriter.write("\n")
headerWriter.write("enum class " + enumName + "\n")
headerWriter.write("{\n")
first = True
for fieldName, fieldValue in fields.items():
if not first:
headerWriter.write(",\n")
headerWriter.write(tab + fieldName + " = " + str(fieldValue))
first = False
if not first:
headerWriter.write("\n")
headerWriter.write("};\n")
headerWriter.write("\n")
headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
headerWriter.write("\n")
codeWriter.write("\n")
codeWriter.write("#include <ostream>\n")
codeWriter.write("\n")
codeWriter.write("#include \"" + enumName + ".h\"\n")
codeWriter.write("\n")
codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
codeWriter.write("{\n")
codeWriter.write(tab + "switch(value)\n")
codeWriter.write(tab + "{\n")
for fieldName in fields.keys():
codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
codeWriter.write(tab + tab + "break;\n")
codeWriter.write(tab + "default:\n")
codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
codeWriter.write(tab + "}\n")
codeWriter.write("\n")
codeWriter.write(tab + "return out;\n")
codeWriter.write("}\n")
codeWriter.write("\n")
finally:
headerWriter.close()
codeWriter.close()
私は@antronからアイデアを得て、それを異なる方法で実装しました。真の enumクラス を生成します。
この実装は最初の質問で挙げたすべての要件を満たしていますが、現在のところ実際の制限は1つしかありません:enum値が指定されていないか、指定された場合.
これは本質的な制限ではありません - 単にアドホックなenum値を使わないということです。これが必要な場合は、ベクトル検索を従来のスイッチ/ケースの実装に置き換えることができます。
このソリューションではインライン変数にc ++ 17を使用していますが、これは必要に応じて簡単に回避できます。簡単のためにboost:trimも使っています。
最も重要なことに、それはわずか30行のコードと黒い魔法のマクロを必要としません。コードは以下の通りです。これはヘッダーに入れられ、複数のコンパイルモジュールに含まれることを意図しています。
このスレッドで以前に提案したのと同じ方法で使用できます。
ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green
Plsはこれが有用であるかどうか、そしてそれがどのようにさらに改善されることができるかを私に知らせました。
#include <boost/algorithm/string.hpp>
struct EnumSupportBase {
static std::vector<std::string> split(const std::string s, char delim) {
std::stringstream ss(s);
std::string item;
std::vector<std::string> tokens;
while (std::getline(ss, item, delim)) {
auto pos = item.find_first_of ('=');
if (pos != std::string::npos)
item.erase (pos);
boost::trim (item);
tokens.Push_back(item);
}
return tokens;
}
};
#define ENUM(EnumName, Underlying, ...) \
enum class EnumName : Underlying { __VA_ARGS__, _count }; \
struct EnumName ## Support : EnumSupportBase { \
static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
static constexpr const char* get_name(EnumName enum_value) { \
int index = (int)enum_value; \
if (index >= (int)EnumName::_count || index < 0) \
return "???"; \
else \
return _token_names[index].c_str(); \
} \
}; \
inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
return os << EnumName##Support::get_name(es); \
}
OPからの依頼によると、ここでは Boost Preprosessor および Variadic Macros に基づいた醜いマクロソリューションの簡略版です。
それは列挙型要素の構文のような単純なリストを特定の要素のための設定値と共に可能にします。
XXX_ENUM(foo,(a,b,(c,42)));
に展開
enum foo {
a,
b,
c=42
};
出力して変換を戻すために必要な関数と一緒に。このマクロは何年も前から使われてきました、そして私はそれが最も効率的な方法であること、あるいはそれが準拠していることであることを完全にはわかりません
完全なコードは、 Ideone および Coliru の両方で実際に動作しています。
その巨大な醜さは上です。私はその方法を知っていたら、私はあなたの目を保護するためにネタバレの後ろにそれを置くだろうが、値下げは私を好きではない。
#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>
namespace xxx
{
template<class T>
struct enum_cast_adl_helper { };
template<class E>
E enum_cast( const std::string& s )
{
return do_enum_cast(s,enum_cast_adl_helper<E>());
}
template<class E>
E enum_cast( const char* cs )
{
std::string s(cs);
return enum_cast<E>(s);
}
} // namespace xxx
#define XXX_PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define XXX_PP_RSEQ_N() \
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,0
#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...) XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_Tuple_SIZE_INTERNAL(Tuple) XXX_PP_NARG Tuple
#define XXX_Tuple_CHOICE(i) \
BOOST_PP_APPLY( \
BOOST_PP_Tuple_ELEM( \
25, i, ( \
(0), (1), (2), (3), (4), (5), (6), (7), (8), \
(9), (10), (11), (12), (13), (14), (15), (16), \
(17), (18), (19), (20), (21), (22), (23), (24) \
) ) )
#define BOOST_PP_BOOL_00 BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01 BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02 BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03 BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04 BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05 BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06 BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07 BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08 BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09 BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63
#define BOOST_PP_DEC_00 BOOST_PP_DEC_0
#define BOOST_PP_DEC_01 BOOST_PP_DEC_1
#define BOOST_PP_DEC_02 BOOST_PP_DEC_2
#define BOOST_PP_DEC_03 BOOST_PP_DEC_3
#define BOOST_PP_DEC_04 BOOST_PP_DEC_4
#define BOOST_PP_DEC_05 BOOST_PP_DEC_5
#define BOOST_PP_DEC_06 BOOST_PP_DEC_6
#define BOOST_PP_DEC_07 BOOST_PP_DEC_7
#define BOOST_PP_DEC_08 BOOST_PP_DEC_8
#define BOOST_PP_DEC_09 BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63
#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_Tuple(tpl) XXX_Tuple_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_Tuple_SIZE(Tuple) XXX_TO_NUM(XXX_Tuple_CHOICE(XXX_Tuple_SIZE_INTERNAL(Tuple)))
#define XXX_Tuple_FOR_EACH(MACRO,DATA,Tuple) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_Tuple_TO_LIST(XXX_Tuple_SIZE(Tuple),Tuple))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_Tuple((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(Tuple) XXX_Tuple_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,Tuple)
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_Tuple_ELEM(XXX_Tuple_SIZE(en),0,en) = BOOST_PP_Tuple_ELEM(XXX_Tuple_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_Tuple_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_Tuple_ELEM(XXX_Tuple_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_Tuple_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_INTERNAL(TYPE,NAME,Tuple) \
enum TYPE \
{ \
XXX_Tuple_FOR_EACH(XXX_ENUM_ELEMENT,,Tuple) \
BOOST_PP_CAT(last_enum_,NAME) \
}; \
\
inline \
const char* to_string( NAME en ) \
{ \
if(false) \
{ \
} \
XXX_Tuple_FOR_EACH(XXX_ENUM_IFELSE,NAME,Tuple) \
else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) ) \
{ \
return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME)); \
} \
else \
{ \
return "Invalid enum value specified for " # NAME; \
} \
} \
\
inline \
std::ostream& operator<<( std::ostream& os, const NAME& en ) \
{ \
os << to_string(en); \
return os; \
} \
\
inline \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{ \
static const std::unordered_map<std::string,NAME> map = \
{ \
XXX_Tuple_FOR_EACH(XXX_ENUM_CASTLIST,NAME,Tuple) \
XXX_Tuple_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,Tuple) \
}; \
\
auto cit = map.find(s); \
if( cit == map.end() ) \
{ \
throw std::runtime_error("Invalid value to cast to enum"); \
} \
return cit->second; \
}
#define XXX_ENUM(NAME,Tuple) XXX_ENUM_INTERNAL(NAME,NAME,Tuple)
#define XXX_ENUM_CLASS(NAME,Tuple) XXX_ENUM_INTERNAL(class NAME,NAME,Tuple)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,Tuple) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,Tuple)
#define XXX_ENUM_TYPE(NAME,TYPE,Tuple) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,Tuple)
#include "xxx_enum.h" // the above lib
#include <iostream>
XXX_ENUM(foo,(a,b,(c,42)));
int main()
{
std::cout << "foo::a = " << foo::a <<'\n';
std::cout << "(int)foo::c = " << (int)foo::c <<'\n';
std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}
main.cpp
内にペーストヘッダーをコピー)> g++ --version | sed 1q
g++ (GCC) 4.9.2
> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
XXX_ENUM(foo,(a,b,(c,42)));
^
foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b
ライブラリ magic_enum
は、enum-to-stringおよびstring-to-enum機能を提供し、単一のヘッダーのみのファイルで構成され、C++-17機能を使用します。 readmeの使用例:
#include "magic_enum.hpp"
enum Color { RED = 2, BLUE = 4, GREEN = 8 };
Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"
std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
// color.value() -> Color::GREEN
};
私は長い間この問題にも不満を感じてきました、それと同時に、型を適切な方法で文字列に変換するという問題もあります。しかし、最後の問題について、 で説明した解決策に驚きました。標準C++で変数の型を出力することは可能ですか? 、 のアイデアを使ってC++の型名をconstexprの方法で取得できますか? 。このテクニックを使用して、enum値を文字列として取得するための類似の関数を構築することができます。
#include <iostream>
using namespace std;
class static_string
{
const char* const p_;
const std::size_t sz_;
public:
typedef const char* const_iterator;
template <std::size_t N>
constexpr static_string(const char(&a)[N]) noexcept
: p_(a)
, sz_(N - 1)
{}
constexpr static_string(const char* p, std::size_t N) noexcept
: p_(p)
, sz_(N)
{}
constexpr const char* data() const noexcept { return p_; }
constexpr std::size_t size() const noexcept { return sz_; }
constexpr const_iterator begin() const noexcept { return p_; }
constexpr const_iterator end() const noexcept { return p_ + sz_; }
constexpr char operator[](std::size_t n) const
{
return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
}
};
inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
return os.write(s.data(), s.size());
}
/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
static_string p = __PRETTY_FUNCTION__;
return static_string(p.data() + 30, p.size() - 30 - 1);
#Elif defined(_MSC_VER)
static_string p = __FUNCSIG__;
return static_string(p.data() + 37, p.size() - 37 - 7);
#endif
}
namespace details
{
template <class Enum>
struct EnumWrapper
{
template < Enum enu >
static static_string name()
{
#ifdef __clang__
static_string p = __PRETTY_FUNCTION__;
static_string enumType = typeName<Enum>();
return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#Elif defined(_MSC_VER)
static_string p = __FUNCSIG__;
static_string enumType = typeName<Enum>();
return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
}
};
}
/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
return details::EnumWrapper<Enum>::template name<enu>();
}
enum class Color
{
Blue = 0,
Yellow = 1
};
int main()
{
std::cout << "_" << typeName<Color>() << "_" << std::endl;
std::cout << "_" << enumName<Color, Color::Blue>() << "_" << std::endl;
return 0;
}
上記のコードはClang( https://ideone.com/je5Quv を参照)とVS2015でのみテストされていますが、整数定数を少し変更することで他のコンパイラにも適応できるはずです。もちろん、内部ではまだマクロを使用していますが、少なくとも1つはenum実装にアクセスする必要はありません。
このアプローチが他の答えの1つで既にカバーされているかどうか私は確信が持てません(実際にそれはそうです、下記参照)。私は何度も問題に遭遇し、難読化されたマクロやサードパーティのライブラリを使わない解決策を見つけませんでした。そこで私は自分で難読化したマクロバージョンを書くことにしました。
有効にしたいのは、と同等です。
enum class test1 { ONE, TWO = 13, SIX };
std::string toString(const test1& e) { ... }
int main() {
test1 x;
std::cout << toString(x) << "\n";
std::cout << toString(test1::TWO) << "\n";
std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
//std::cout << toString(123);// invalid
}
どちらを印刷すべきか
ONE
TWO
13
私はマクロのファンではありません。しかし、c ++が列挙型から文字列への変換をネイティブにサポートしていない限り、ある種のコード生成やマクロを使用する必要があります(これは間もなく起こるとは思わない)。 X-macro :を使っています。
// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#define x_begin inline std::string toString(const x_name& e) { \
static std::map<x_name,std::string> names = {
#define x_val(X) { x_name::X , #X }
#define x_value(X,Y) { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def
そのほとんどは、インクルードを介してユーザーがX-marcoにパラメータとして渡すシンボルを定義し、未定義にすることです。使い方はこんな感じです
#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
x_value(TWO,13) , \
x_val(SIX) \
x_end
#include "x_enum.h"
基礎となるタイプを選択することはまだ含めていないことに注意してください。これまでは必要ありませんでしたが、それを可能にするためにコードを修正するのは簡単なことです。
これを書いた後で初めて、私はそれが eferions answer にかなり似ていることに気づきました。たぶん私は以前にそれを読んだ、そして多分それはインスピレーションの主な原因だった。私が自分で書くまで、私はいつもXマクロの理解に失敗していました;)。
各問い合わせ可能な列挙型に対して別々の.h/.cpp
ペアを書いても問題ないのであれば、この解決法は通常のc ++の列挙型とほぼ同じ構文と機能で動作します。
// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if
enum MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = AAA + BBB
};
.cpp
ファイルは3行の定型句です。
// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>
使用例
for (MyEnum value : EnumTraits<MyEnum>::GetValues())
std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;
このソリューションには2つのソースファイルが必要です。
// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#define ETRAITS
#define EDECL(x) x
template <class ENUM>
class EnumTraits
{
public:
static const std::vector<ENUM>& GetValues()
{
return values;
}
static ENUM GetValue(const char* name)
{
auto match = valueMap.find(name);
return (match == valueMap.end() ? ENUM() : match->second);
}
static const char* GetName(ENUM value)
{
auto match = nameMap.find(value);
return (match == nameMap.end() ? nullptr : match->second);
}
public:
EnumTraits() = delete;
using vector_type = std::vector<ENUM>;
using name_map_type = std::unordered_map<ENUM, const char*>;
using value_map_type = std::unordered_map<std::string, ENUM>;
private:
static const vector_type values;
static const name_map_type nameMap;
static const value_map_type valueMap;
};
struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }
...そして
// EnumTraits.inl
#define ENUM_INCLUDE_MULTI
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;
#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
この実装は、enum定義の要素の中括弧付きリストがクラスメンバの初期化用の中括弧付き初期化子リストとしても使用できるという事実を利用します。
ETRAITS
がEnumTraits.inl
のコンテキストで評価されると、EnumTraits<>
クラスの静的メンバー定義に展開されます。
EDECL
マクロは、各enumメンバを初期化子リストの値に変換します。初期化子リストの値は、その後enum情報を生成するためにメンバコンストラクタに渡されます。
EnumInitGuard
クラスはenum初期化子の値を消費してから折りたたむように設計されています - 純粋なenumデータのリストを残します。
c++
のような構文enum
とenum class
の両方で同じように動作します(*ほぼ)enum
型に対して機能します。enum
型に対して機能します。*
enums
とは対照的に、同じenumの他の値を参照するenum class
型の初期化子はそれらの値が完全に修飾されていなければなりません
enum
ごとに別々の.h/.cpp
ペアが必要macro
とinclude
マジックに依存class
またはnamespace
スコープのenumを定義することは重要です。IntellisenseはEnumTraits.inl
を開くときにプライベートメンバアクセスについて少々不満を言いますが、展開されたマクロは実際にはクラスメンバを定義しているので、それは実際には問題ではありません。
ヘッダファイルの一番上にある#ifndef ENUM_INCLUDE_MULTI
ブロックは、おそらくマクロか何かに縮小されるかもしれないささいな煩わしさですが、それは現在のサイズで生きるのに十分小さいです。
名前空間スコープのenumを宣言するには、まずその名前空間スコープ内でそのenumを前方宣言し、次にグローバル名前空間で定義する必要があります。さらに、同じenumの値を使用するenum初期化子は、それらの値が完全修飾されている必要があります。
namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}
これは 要旨 はC++可変長テンプレートに基づく簡単なマッピングを提供します。
これは 要旨 からの型に基づくマップのC++ 17 - 簡略化バージョンです。
#include <cstring> // http://stackoverflow.com/q/24520781
template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
static constexpr typename KeyValue::key_t get(const char* val) noexcept {
if constexpr (sizeof...(RestOfKeyValues)==0) // C++17 if constexpr
return KeyValue::key; // Returns last element
else {
static_assert(KeyValue::val != nullptr,
"Only last element may have null name");
return strcmp(val, KeyValue::val())
? map<RestOfKeyValues...>::get(val) : KeyValue::key;
}
}
static constexpr const char* get(typename KeyValue::key_t key) noexcept {
if constexpr (sizeof...(RestOfKeyValues)==0)
return (KeyValue::val != nullptr) && (key == KeyValue::key)
? KeyValue::val() : "";
else
return (key == KeyValue::key)
? KeyValue::val() : map<RestOfKeyValues...>::get(key);
}
};
template<typename Enum, typename ... KeyValues>
class names {
typedef map<KeyValues...> Map;
public:
static constexpr Enum get(const char* nam) noexcept {
return Map::get(nam);
}
static constexpr const char* get(Enum key) noexcept {
return Map::get(key);
}
};
使用例
enum class fasion {
fancy,
classic,
sporty,
Emo,
__last__ = Emo,
__unknown__ = -1
};
#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
NAME(fancy)
NAME(classic)
NAME(sporty)
NAME(Emo)
}
template<auto K, const char* (*V)()> // C++17 template<auto>
struct _ {
typedef decltype(K) key_t;
typedef decltype(V) name_t;
static constexpr key_t key = K; // enum id value
static constexpr name_t val = V; // enum id name
};
typedef names<fasion,
_<fasion::fancy, name::fancy>,
_<fasion::classic, name::classic>,
_<fasion::sporty, name::sporty>,
_<fasion::Emo, name::Emo>,
_<fasion::__unknown__, nullptr>
> fasion_names;
map<KeyValues...>
は両方向に使用できます。
fasion_names::get(fasion::Emo)
fasion_names::get("Emo")
この例は godbolt.org にあります。
int main ()
{
constexpr auto str = fasion_names::get(fasion::Emo);
constexpr auto fsn = fasion_names::get(str);
return (int) fsn;
}
gcc-7 -std=c++1z -Ofast -S
からの結果
main:
mov eax, 3
ret
クラス/構造体(構造体のデフォルトはパブリックメンバ)およびオーバーロード演算子内で列挙型を使用する解決策:
struct Color
{
enum Enum { RED, GREEN, BLUE };
Enum e;
Color() {}
Color(Enum e) : e(e) {}
Color operator=(Enum o) { e = o; return *this; }
Color operator=(Color o) { e = o.e; return *this; }
bool operator==(Enum o) { return e == o; }
bool operator==(Color o) { return e == o.e; }
operator Enum() const { return e; }
std::string toString() const
{
switch (e)
{
case Color::RED:
return "red";
case Color::GREEN:
return "green";
case Color::BLUE:
return "blue";
default:
return "unknown";
}
}
};
外側から見ると、クラスの列挙型とほぼ同じです。
Color red;
red = Color::RED;
Color blue = Color::BLUE;
cout << red.toString() << " " << Color::GREEN << " " << blue << endl;
これは "red 1 2"を出力します。 <<をオーバーロードして青い出力を文字列にすることはできますが(あいまいさが発生する可能性はありませんが)、Colorに自動的に変換されないため、Color :: GREENでは機能しません。
暗黙的にEnumに変換すること(暗黙的にint型または与えられた型に変換すること)の目的は、することができることです。
Color color;
switch (color) ...
これは機能しますが、これもまた機能することを意味します。
int i = color;
Enumクラスではコンパイルできません。 enumと整数を取る2つの関数をオーバーロードするか、暗黙の型変換を削除する場合は注意が必要です。
別の解決策は実際のenumクラスとstaticメンバを使うことです。
struct Color
{
enum class Enum { RED, GREEN, BLUE };
static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;
//same as previous...
};
それはおそらくもっと多くのスペースをとり、作るのはもっと長いですが、暗黙のint変換に対してコンパイルエラーを引き起こします。そのためこれを使用します。
確かにこれにはオーバーヘッドがありますが、私はそれがちょうど単純で、私が見た他のコードより良く見えると思います。機能を追加する可能性もあります。それらはすべてクラス内の範囲にすることができます。
編集 :これは機能し、ほとんどは実行前にコンパイルできます。
class Color
{
public:
enum class Enum { RED, GREEN, BLUE };
static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;
constexpr Color() : e(Enum::RED) {}
constexpr Color(Enum e) : e(e) {}
constexpr bool operator==(Enum o) const { return e == o; }
constexpr bool operator==(Color o) const { return e == o.e; }
constexpr operator Enum() const { return e; }
Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }
std::string toString() const
{
switch (e)
{
case Enum::RED:
return "red";
case Enum::GREEN:
return "green";
case Enum::BLUE:
return "blue";
default:
return "unknown";
}
}
private:
const Enum e;
};
1つの大きな制約がある非常に単純な解決策:カスタム値をenum
値に割り当てることはできませんが、正しい正規表現を使えば可能です。さらに手間をかけずにマップを追加して、それらをenum
値に変換することもできます。
#include <vector>
#include <string>
#include <regex>
#include <iterator>
std::vector<std::string> split(const std::string& s,
const std::regex& delim = std::regex(",\\s*"))
{
using namespace std;
vector<string> cont;
copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1),
regex_token_iterator<string::const_iterator>(),
back_inserter(cont));
return cont;
}
#define EnumType(Type, ...) enum class Type { __VA_ARGS__ }
#define EnumStrings(Type, ...) static const std::vector<std::string> \
Type##Strings = split(#__VA_ARGS__);
#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
EnumStrings(Type, __VA_ARGS__)
使用例
EnumToString(MyEnum, Red, Green, Blue);
上記のように、 N4113 がこの問題の最終的な解決策ですが、それが出るのを見るには1年以上待たなければなりません。
一方、そのような機能が必要な場合は、「単純な」テンプレートとプリプロセッサの魔法に頼る必要があります。
template<typename T>
class Enum final
{
const char* m_name;
const T m_value;
static T m_counter;
public:
Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}
const T value() const {return m_value;}
const char* name() const {return m_name;}
};
template<typename T>
T Enum<T>::m_counter = 0;
#define ENUM_TYPE(x) using Enum = Enum<x>;
#define ENUM_DECL(x,...) x(#x,##__VA_ARGS__)
#define ENUM(...) const Enum ENUM_DECL(__VA_ARGS__);
#include <iostream>
//the initialization order should be correct in all scenarios
namespace Level
{
ENUM_TYPE(std::uint8)
ENUM(OFF)
ENUM(SEVERE)
ENUM(WARNING)
ENUM(INFO, 10)
ENUM(DEBUG)
ENUM(ALL)
}
namespace Example
{
ENUM_TYPE(long)
ENUM(A)
ENUM(B)
ENUM(C, 20)
ENUM(D)
ENUM(E)
ENUM(F)
}
int main(int argc, char** argv)
{
Level::Enum lvl = Level::WARNING;
Example::Enum ex = Example::C;
std::cout << lvl.value() << std::endl; //2
std::cout << ex.value() << std::endl; //20
}
Enum<T>::m_counter
は、各名前空間宣言内で0に設定されています。
(誰かが標準のどこで^^この動作が言及されているかを指摘してもらえますか?)
プリプロセッサマジックは列挙子の宣言を自動化します。
enum
型ではないので、intには昇格できませんこれは行番号付けを犠牲にします(実際にはそうではありません) しかし、スイッチの場合には使用できます 。
#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x) constexpr type x{__LINE__,#x}
template<typename T>
struct Enum final
{
const T value;
const char* name;
constexpr operator const T() const noexcept {return value;}
constexpr const char* operator&() const noexcept {return name;}
};
#line 0
はGCCおよびclangの-pedantic
と競合します。
#line 1
から開始し、__LINE__
から1を引きます。
または、-pedantic
を使用しないでください。
そして私達がそれに着手している間、VC++は絶対に避けてください、それは常にコンパイラーの冗談でした。
#include <iostream>
namespace Level
{
ENUM_TYPE(short);
#line 0
ENUM(OFF);
ENUM(SEVERE);
ENUM(WARNING);
#line 10
ENUM(INFO);
ENUM(DEBUG);
ENUM(ALL);
#line <next line number> //restore the line numbering
};
int main(int argc, char** argv)
{
std::cout << Level::OFF << std::endl; // 0
std::cout << &Level::OFF << std::endl; // OFF
std::cout << Level::INFO << std::endl; // 10
std::cout << &Level::INFO << std::endl; // INFO
switch(/* any integer or integer-convertible type */)
{
case Level::OFF:
//...
break;
case Level::SEVERE:
//...
break;
//...
}
return 0;
}
r3dVoxel - 列挙
r3dボクセル - ELoggingLevel
Ponder のようにリフレクションライブラリを使うことができます。
enum class MyEnum
{
Zero = 0,
One = 1,
Two = 2
};
ponder::Enum::declare<MyEnum>()
.value("Zero", MyEnum::Zero)
.value("One", MyEnum::One)
.value("Two", MyEnum::Two);
ponder::EnumObject zero(MyEnum::Zero);
zero.name(); // -> "Zero"
私の答えはここにあります。
このライブラリ(私が作成したもの)を使用すると、列挙型定数の名前を取得できます。 https://github.com/Neargye/nameof
// Name of enum
auto c = Color::RED;
NAMEOF_ENUM(c) -> "RED"
// Name of enum
nameof::nameof_enum(c) -> "RED"
constexpr auto cx_name = NAMEOF_ENUM(c);
static_assert("RED" == cx_name);
このライブラリは(__PRETTY_FUNCTION__
/__FUNCSIG__
に基づく)コンパイラ固有のハックを使用します。これはClang> = 5、MSVC> = 15.3およびGCC> = 9で動作します。
次の解決策は与えられた列挙型のstd::array<std::string,N>
に基づいています。
enum
からstd::string
への変換では、enumをsize_t
にキャストして配列から文字列を検索するだけです。操作はO(1)で、ヒープ割り当ては不要です。
#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <string>
#include <array>
#include <iostream>
#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)
// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X { \
enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
} \
static std::string to_string(Enum e) { \
auto a = array_of_strings(); \
return a[static_cast<size_t>(e)]; \
} \
}
std::string
からenum
への変換では、配列を線形検索し、配列インデックスをenum
にキャストする必要があります。
使用例でここでそれを試してみてください: http://coliru.stacked-crooked.com/a/e4212f93bee65076
編集: カスタムEnumをクラス内で使用できるように、私の解決法を作り直しました。
私の解決策はマクロを使わないことです。
利点:
デメリット:
c ++がC#Enum.Parse機能を実装する日まで、私はこれで行き詰まります。
#include <unordered_map>
enum class Language
{ unknown,
Chinese,
English,
French,
German
// etc etc
};
class Enumerations
{
public:
static void fnInit(void);
static std::unordered_map <std::wstring, Language> m_Language;
static std::unordered_map <Language, std::wstring> m_invLanguage;
private:
static void fnClear();
static void fnSetValues(void);
static void fnInvertValues(void);
static bool m_init_done;
};
std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();
void Enumerations::fnInit()
{
fnClear();
fnSetValues();
fnInvertValues();
}
void Enumerations::fnClear()
{
m_Language.clear();
m_invLanguage.clear();
}
void Enumerations::fnSetValues(void)
{
m_Language[L"unknown"] = Language::unknown;
m_Language[L"Chinese"] = Language::Chinese;
m_Language[L"English"] = Language::English;
m_Language[L"French"] = Language::French;
m_Language[L"German"] = Language::German;
// and more etc etc
}
void Enumerations::fnInvertValues(void)
{
for (auto it = m_Language.begin(); it != m_Language.end(); it++)
{
m_invLanguage[it->second] = it->first;
}
}
// usage -
//Language aLanguage = Language::English;
//wstring sLanguage = Enumerations::m_invLanguage[aLanguage];
//wstring sLanguage = L"French" ;
//Language aLanguage = Enumerations::m_Language[sLanguage];
( https://stackoverflow.com/a/54967187/2338477 のアナログ、わずかに変更)。
これが私の独自の解決策で、最低限の定義魔法と個々のenum割り当てのサポートです。
これがヘッダファイルです。
#pragma once
#include <string>
#include <map>
#include <regex>
template <class Enum>
class EnumReflect
{
public:
static const char* getEnums() { return ""; }
};
//
// Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
static std::map<std::string, int> enum2int;
static std::map<int, std::string> int2enum;
static void EnsureEnumMapReady( const char* enumsInfo )
{
if (*enumsInfo == 0 || enum2int.size() != 0 )
return;
// Should be called once per each enumeration.
std::string senumsInfo(enumsInfo);
std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *"); // C++ identifier to optional " = <value>"
std::smatch sm;
int value = 0;
for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
{
string enumName = sm[1].str();
string enumValue = sm[2].str();
if (enumValue.length() != 0)
value = atoi(enumValue.c_str());
enum2int[enumName] = value;
int2enum[value] = enumName;
}
}
};
template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;
template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;
#define DECLARE_ENUM(name, ...) \
enum name { __VA_ARGS__ }; \
template <> \
class EnumReflect<##name>: public EnumReflectBase<##name> { \
public: \
static const char* getEnums() { return #__VA_ARGS__; } \
};
/*
Basic usage:
Declare enumeration:
DECLARE_ENUM( enumName,
enumValue1,
enumValue2,
enumValue3 = 5,
// 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...");
*/
//
// Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& int2enum = EnumReflect<T>::int2enum;
auto it = int2enum.find(t);
if (it == int2enum.end())
return "";
return it->second;
}
//
// Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& enum2int = EnumReflect<T>::enum2int;
auto it = enum2int.find(enumName);
if (it == enum2int.end())
return false;
t = (T) it->second;
return true;
}
そして、これがテストアプリケーションの例です。
DECLARE_ENUM(TestEnum,
ValueOne,
ValueTwo,
ValueThree = 5,
ValueFour = 7
);
DECLARE_ENUM(TestEnum2,
ValueOne2 = -1,
ValueTwo2,
ValueThree2 = -4,
ValueFour2
);
void main(void)
{
string sName1 = EnumToString(ValueOne);
string sName2 = EnumToString(ValueTwo);
string sName3 = EnumToString(ValueThree);
string sName4 = EnumToString(ValueFour);
TestEnum t1, t2, t3, t4, t5 = ValueOne;
bool b1 = StringToEnum(sName1.c_str(), t1);
bool b2 = StringToEnum(sName2.c_str(), t2);
bool b3 = StringToEnum(sName3.c_str(), t3);
bool b4 = StringToEnum(sName4.c_str(), t4);
bool b5 = StringToEnum("Unknown", t5);
string sName2_1 = EnumToString(ValueOne2);
string sName2_2 = EnumToString(ValueTwo2);
string sName2_3 = EnumToString(ValueThree2);
string sName2_4 = EnumToString(ValueFour2);
TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
bool b2_5 = StringToEnum("Unknown", t2_5);
同じヘッダファイルの更新版はここに保存されます。
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h
私はこの問題を解決するためのライブラリを書きました。メッセージを受け取る以外はすべてコンパイル時に起こります。
マクロとメッセージのペアを定義するには、マクロDEF_MSG
を使用します。
DEF_MSG(CODE_OK, "OK!")
DEF_MSG(CODE_FAIL, "Fail!")
CODE_OK
は使用するマクロで、"OK!"
は対応するメッセージです。
メッセージを取得するには、get_message()
または単にgm()
を使用します。
get_message(CODE_FAIL); // will return "Fail!"
gm(CODE_FAIL); // works exactly the same as above
マクロがいくつ定義されているか調べるには、MSG_NUM
を使用してください。これは自動的に増加します、あなたは何もする必要はありません。
定義済みメッセージ
MSG_OK: OK
MSG_BOTTOM: Message bottom
プロジェクト: libcodemsg
ライブラリは余分なデータを作成しません。すべてがコンパイル時に起こります。 message_def.h
では、MSG_CODE
というenum
を生成します。 message_def.c
では、static const char* _g_messages[]
内のすべての文字列を保持する変数を生成します。
そのような場合、ライブラリは1つのenum
のみを作成するように制限されています。これは戻り値にとって理想的です。例えば:
MSG_CODE foo(void) {
return MSG_OK; // or something else
}
MSG_CODE ret = foo();
if (MSG_OK != ret) {
printf("%s\n", gm(ret););
}
この設計が気に入っているもう1つのことは、メッセージ定義をさまざまなファイルで管理できることです。
最も簡単な方法は?
Adaを使う:Enumeration'Image( Value )
はあなたが望むことを正確に行います。もしあなたが 本当に C++を必要としているなら、関数をエクスポートしてみることができます:
Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
with Export, Convention => C;
私の答えはここにあります。
あなたは文字列の両端キューとして列挙値の名前とこれらのインデックスを同時に得ることができます。
この方法は、コピー&ペーストと編集がほとんど必要ありません。
あなたがenumクラス型の値を必要とするとき、得られた結果はsize_tからenumクラス型への型キャストを必要とします、しかし私はそれがenumクラスを扱うための非常に移植性があり強力な方法だと思います。
enum class myenum
{
one = 0,
two,
three,
};
deque<string> ssplit(const string &_src, boost::regex &_re)
{
boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
boost::sregex_token_iterator e;
deque<string> tokens;
while (it != e)
tokens.Push_back(*it++);
return std::move(tokens);
}
int main()
{
regex re(",");
deque<string> tokens = ssplit("one,two,three", re);
for (auto &t : tokens) cout << t << endl;
getchar();
return 0;
}
#define ENUM_MAKE(TYPE, ...) \
enum class TYPE {__VA_ARGS__};\
struct Helper_ ## TYPE { \
static const String& toName(TYPE type) {\
int index = static_cast<int>(type);\
return splitStringVec()[index];}\
static const TYPE toType(const String& name){\
static std::unordered_map<String,TYPE> typeNameMap;\
if( typeNameMap.empty() )\
{\
const StringVector& ssVec = splitStringVec();\
for (size_t i = 0; i < ssVec.size(); ++i)\
typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
}\
return typeNameMap[name];}\
static const StringVector& splitStringVec() {\
static StringVector typeNameVector;\
if(typeNameVector.empty()) \
{\
typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
for (auto& name : typeNameVector)\
{\
name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
name = String(#TYPE) + "::" + name;\
}\
}\
return typeNameVector;\
}\
};
using String = std::string;
using StringVector = std::vector<String>;
StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
{
StringVector ret;
// Pre-allocate some space for performance
ret.reserve(maxSplits ? maxSplits+1 : 10); // 10 is guessed capacity for most case
unsigned int numSplits = 0;
// Use STL methods
size_t start, pos;
start = 0;
do
{
pos = str.find_first_of(delims, start);
if (pos == start)
{
// Do nothing
start = pos + 1;
}
else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
{
// Copy the rest of the string
ret.Push_back( str.substr(start) );
break;
}
else
{
// Copy up to delimiter
ret.Push_back( str.substr(start, pos - start) );
if(preserveDelims)
{
// Sometimes there could be more than one delimiter in a row.
// Loop until we don't find any more delims
size_t delimStart = pos, delimPos;
delimPos = str.find_first_not_of(delims, delimStart);
if (delimPos == String::npos)
{
// Copy the rest of the string
ret.Push_back( str.substr(delimStart) );
}
else
{
ret.Push_back( str.substr(delimStart, delimPos - delimStart) );
}
}
start = pos + 1;
}
// parse up to next real data
start = str.find_first_not_of(delims, start);
++numSplits;
} while (pos != String::npos);
return ret;
}
例
ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)
MY_TEST s1 = MY_TEST::MY_1;
MY_TEST s2 = MY_TEST::MY_2;
MY_TEST s3 = MY_TEST::MY_3;
String z1 = Helper_MY_TEST::toName(s1);
String z2 = Helper_MY_TEST::toName(s2);
String z3 = Helper_MY_TEST::toName(s3);
MY_TEST q1 = Helper_MY_TEST::toType(z1);
MY_TEST q2 = Helper_MY_TEST::toType(z2);
MY_TEST q3 = Helper_MY_TEST::toType(z3);
自動的にENUM_MAKEマクロは 'enum reflection function'を使って 'enum class'とヘルパークラスを生成します。
間違いを減らすために、一度にすべてが1つのENUM_MAKEで定義されます。
このコードの利点は、反射とマクロコードの詳細な見やすさ、わかりやすいコードのために自動的に作成されます。 'enum to string'、 'string to enum'のパフォーマンスはどちらもアルゴリズムO(1)です。
デメリットは、最初に使用したときに、enum relectionの文字列ベクトルとマップのヘルパークラスが初期化されることです。しかし、あなたが望むなら、あなたはまた事前に初期化されるでしょう。 -