キャストに汎用的な方法があるint
のenum
ではC++
?
のint
範囲内にあるenum
場合はenum
値を返す必要があり、そうでない場合はexception
。をスローします。それを一般的に書く方法はありますか?複数enum type
をサポートする必要があります。
背景:外部列挙型があり、ソースコードを制御できません。この値をデータベースに保存して取得したいと思います。
キャストに汎用的な方法があるint
のenum
ではC++
?
のint
範囲内にあるenum
場合はenum
値を返す必要があり、そうでない場合はexception
。をスローします。それを一般的に書く方法はありますか?複数enum type
をサポートする必要があります。
背景:外部列挙型があり、ソースコードを制御できません。この値をデータベースに保存して取得したいと思います。
9999
落ちません。
enum class
。
回答:
明らかなことは、列挙型に注釈を付けることです。
// generic code
#include <algorithm>
template <typename T>
struct enum_traits {};
template<typename T, size_t N>
T *endof(T (&ra)[N]) {
return ra + N;
}
template<typename T, typename ValType>
T check(ValType v) {
typedef enum_traits<T> traits;
const T *first = traits::enumerators;
const T *last = endof(traits::enumerators);
if (traits::sorted) { // probably premature optimization
if (std::binary_search(first, last, v)) return T(v);
} else if (std::find(first, last, v) != last) {
return T(v);
}
throw "exception";
}
// "enhanced" definition of enum
enum e {
x = 1,
y = 4,
z = 10,
};
template<>
struct enum_traits<e> {
static const e enumerators[];
static const bool sorted = true;
};
// must appear in only one TU,
// so if the above is in a header then it will need the array size
const e enum_traits<e>::enumerators[] = {x, y, z};
// usage
int main() {
e good = check<e>(1);
e bad = check<e>(2);
}
配列を最新の状態に保つ必要がありe
ます。これは、の作成者でない場合は厄介ですe
。Sjoerdが言うように、それはおそらくどんなまともなビルドシステムで自動化することができます。
いずれにせよ、あなたは7.2 / 6に反対しています。
eminが最小の列挙子で、emaxが最大の列挙型の場合、列挙型の値は、bminからbmaxの範囲の基になる型の値です。ここで、bminとbmaxは、それぞれ最小値と最大値です。 eminとemaxを格納できるビットフィールド。どの列挙子によっても定義されていない値を持つ列挙型を定義することができます。
したがって、の作成者でない場合はe
、の有効な値がe
実際にその定義に表示されるという保証がある場合とない場合があります。
醜い。
enum MyEnum { one = 1, two = 2 };
MyEnum to_enum(int n)
{
switch( n )
{
case 1 : return one;
case 2 : return two;
}
throw something();
}
さて、本当の質問です。なぜこれが必要なのですか?コードは醜く、書くのも簡単ではなく(*?)、保守も簡単ではなく、コードに組み込むのも簡単ではありません。それが間違っていることをあなたに告げるコード。なぜそれと戦うのですか?
編集:
あるいは、列挙型がC ++の整数型であるとすると、次のようになります。
enum my_enum_val = static_cast<MyEnum>(my_int_val);
しかし、これは上記よりもさらに醜く、エラーが発生しやすく、希望どおりにスローされません。
throw
無効なタイプに対して思いついた(または特別なことをする)ソリューションには、私が投稿したようなスイッチが必要です。
static_cast<MyEnum>
も同様に機能し、reinterpret_cast<MyEnum>
説明したように、値がデータベースにある場合は、このテーブルを読み取り、列挙型とto_enum(int)
関数の両方を含む.hファイルと.cppファイルを作成するコードジェネレーターを作成してみませんか?
利点:
to_string(my_enum)
機能を簡単に追加できます。to_enum(int)
基づいて関数を生成します。
make
2つのファイルの日付を比較して、ジェネレーターを再実行する必要があるかどうかを確認できます。
これについてどう思いますか?
#include <iostream>
#include <stdexcept>
#include <set>
#include <string>
using namespace std;
template<typename T>
class Enum
{
public:
static void insert(int value)
{
_set.insert(value);
}
static T buildFrom(int value)
{
if (_set.find(value) != _set.end()) {
T retval;
retval.assign(value);
return retval;
}
throw std::runtime_error("unexpected value");
}
operator int() const { return _value; }
private:
void assign(int value)
{
_value = value;
}
int _value;
static std::set<int> _set;
};
template<typename T> std::set<int> Enum<T>::_set;
class Apples: public Enum<Apples> {};
class Oranges: public Enum<Oranges> {};
class Proxy
{
public:
Proxy(int value): _value(value) {}
template<typename T>
operator T()
{
T theEnum;
return theEnum.buildFrom(_value);
}
int _value;
};
Proxy convert(int value)
{
return Proxy(value);
}
int main()
{
Apples::insert(4);
Apples::insert(8);
Apples a = convert(4); // works
std::cout << a << std::endl; // prints 4
try {
Apples b = convert(9); // throws
}
catch (std::exception const& e) {
std::cout << e.what() << std::endl; // prints "unexpected value"
}
try {
Oranges b = convert(4); // also throws
}
catch (std::exception const& e) {
std::cout << e.what() << std::endl; // prints "unexpected value"
}
}
Apples::insert(4)
どこかに追加する必要があるので、これはスイッチに勝る利点はありません。
あなたが説明するようなものが存在することを望まないはずです、私はあなたのコードデザインに問題があるのではないかと心配しています。
また、列挙型は範囲内にあると想定していますが、常にそうであるとは限りません。
enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
これは範囲内ではありません。可能であったとしても、0から2 ^ nまでのすべての整数をチェックして、列挙型の値と一致するかどうかを確認する必要がありますか?
列挙値をテンプレートパラメータとしてリストする準備ができている場合は、C ++ 11でvaradicテンプレートを使用してこれを行うことができます。これは良いことと見なすことができ、さまざまなコンテキストで有効な列挙値のサブセットを受け入れることができます。多くの場合、外部ソースからのコードを解析するときに役立ちます。
おそらくあなたが望むほど一般的ではありませんが、チェックコード自体は一般化されているので、値のセットを指定する必要があります。このアプローチは、ギャップ、任意の値などを処理します。
template<typename EnumType, EnumType... Values> class EnumCheck;
template<typename EnumType> class EnumCheck<EnumType>
{
public:
template<typename IntType>
static bool constexpr is_value(IntType) { return false; }
};
template<typename EnumType, EnumType V, EnumType... Next>
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...>
{
using super = EnumCheck<EnumType, Next...>;
public:
template<typename IntType>
static bool constexpr is_value(IntType v)
{
return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
}
EnumType convert(IntType v)
{
if (!is_value(v)) throw std::runtime_error("Enum value out of range");
return static_cast<EnumType>(v);
};
enum class Test {
A = 1,
C = 3,
E = 5
};
using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>;
void check_value(int v)
{
if (TestCheck::is_value(v))
printf("%d is OK\n", v);
else
printf("%d is not OK\n", v);
}
int main()
{
for (int i = 0; i < 10; ++i)
check_value(i);
}
「醜い」バージョンの代わりにC ++ 0xを使用すると、複数の列挙型を使用できます。スイッチではなく初期化リストを使用し、少しクリーンなIMOを使用します。残念ながら、これは列挙値をハードコーディングする必要性を回避しません。
#include <cassert> // assert
namespace // unnamed namespace
{
enum class e1 { value_1 = 1, value_2 = 2 };
enum class e2 { value_3 = 3, value_4 = 4 };
template <typename T>
int valid_enum( const int val, const T& vec )
{
for ( const auto item : vec )
if ( static_cast<int>( item ) == val ) return val;
throw std::exception( "invalid enum value!" ); // throw something useful here
} // valid_enum
} // ns
int main()
{
// generate list of valid values
const auto e1_valid_values = { e1::value_1, e1::value_2 };
const auto e2_valid_values = { e2::value_3, e2::value_4 };
auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
assert( result1 == e1::value_1 );
auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
assert( result2 == e2::value_3 );
// test throw on invalid value
try
{
auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
assert( false );
}
catch ( ... )
{
assert( true );
}
}
enum e{x = 10000};
この場合9999
、enum
?の範囲内にありますか?