C ++でintを列挙型にキャストする一般的な方法


81

キャストに汎用的な方法があるintenumではC++

int範囲内にあるenum場合はenum値を返す必要があり、そうでない場合はexception。をスローします。それを一般的に書く方法はありますか?複数enum typeをサポートする必要があります。

背景:外部列挙型があり、ソースコードを制御できません。この値をデータベースに保存して取得したいと思います。


enum e{x = 10000};この場合9999enum?の範囲内にありますか?
Armen Tsirunyan 2010年

いいえ、9999落ちません。
しし座

9
良い質問。「なぜ」は?これが表示されるので、「逆シリアル化」とだけ言っておきます。これは私にとって十分な理由のようです。また、C ++ 0x-compilantの回答を聞いてうれしいですenum class
コス

9
「範囲」はここでは間違った言葉です、多分「ドメイン」?
コンスタンティン

boost :: Numeric_cast <>は、値が範囲外の場合、正または負のオーバーフロー例外をスローします。しかし、それが列挙型にも適しているかどうかはわかりません。あなたはそれを試すことができます。
yasouser 2010年

回答:


37

明らかなことは、列挙型に注釈を付けることです。

// 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実際にその定義に表示されるという保証がある場合とない場合があります。


22

醜い。

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);

しかし、これは上記よりもさらに醜く、エラーが発生しやすく、希望どおりにスローされません。


これは、MyEnumという1つのタイプのみをサポートします。
シモーネ

2
@Leonid:私の知る限り、それを一般的に行うことはできません。あるレベルでは、throw無効なタイプに対して思いついた(または特別なことをする)ソリューションには、私が投稿したようなスイッチが必要です。
John Dibling 2010年

2
なぜこれが-1になるのですか?それが正解です。一部の人が望んでいた答えではないからといって、それが間違っているとは限りません。
ジョンディブリング2010年

12
Astatic_cast<MyEnum>も同様に機能し、reinterpret_cast<MyEnum>
Sjoerd 2010年

1
このアプローチはうまく機能し、ツールを使用して関数を生成するとさらに効果的です。
ニック

3

説明したように、値がデータベースにある場合は、このテーブルを読み取り、列挙型とto_enum(int)関数の両方を含む.hファイルと.cppファイルを作成するコードジェネレーターを作成してみませんか?

利点:

  • to_string(my_enum)機能を簡単に追加できます。
  • メンテナンスはほとんど必要ありません
  • データベースとコードは同期しています

列挙型のソースコードを制御することはできません。変換を実装するファシリティを生成できることに同意します。ただし、ファシリティは、外部列挙型に加えられた変更/拡張を認識しません(コンパイル時に毎回実行されない限り)。
しし

@Leonid次に、その列挙型ヘッダーを読み取り、それにto_enum(int)基づいて関数を生成します。
Sjoerd 2010年

@Leonidすべての深刻なプロジェクト管理システムは、make2つのファイルの日付を比較して、ジェネレーターを再実行する必要があるかどうかを確認できます。
Sjoerd 2010年

今のところ、ジェネレーターのより簡単なソリューションを使用すると思います。しかし、アイデアに感謝します。
しし座

私たちは職場でこのスキームを使用しています。ツールはテンプレートから.hppコードを生成し、テンプレートは最小限です。
ニック

3

いいえ-C ++にはイントロスペクションはなく、組み込みの「ドメインチェック」機能もありません。


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)どこかに追加する必要があるので、これはスイッチに勝る利点はありません。
Sjoerd 2010年

1
彼は、値はデータベースからのものであると言ったので、「挿入」メソッドを追加しました。
シモーネ

1

あなたが説明するようなものが存在することを望まないはずです、私はあなたのコードデザインに問題があるのではないかと心配しています。

また、列挙型は範囲内にあると想定していますが、常にそうであるとは限りません。

enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };

これは範囲内ではありません。可能であったとしても、0から2 ^ nまでのすべての整数をチェックして、列挙型の値と一致するかどうかを確認する必要がありますか?


それ以外の場合、データベースから列挙値を取得するにはどうすればよいですか?整数はコンパイル時に既知ですが、テンプレートに基づいて一般的な変換を行うことができないのはなぜですか?
しし座

2
@Leonid:あるレベルでは、私が言ったように、スイッチが必要だからです。
John Dibling 2010年

2
@Leonid Templatesは、考えられるすべての問題を解決するための特効薬ではありません。
Sjoerd 2010年

ジョンは正しい。必要なことを行うには、列挙型よりも複雑な型が必要です。クラス階層で実現可能だと思います。
シモーネ

クラス階層を使用するソリューションを投稿しました。チェックしてください。
シモーネ

1

列挙値をテンプレートパラメータとしてリストする準備ができている場合は、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);
}

このリンクは質問に答えることができますが、ここに答えの本質的な部分を含めて、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。-レビューから
Tas

1
@Tasこれは別のSO回答へのリンクです。外部リンクと同じ問題はありません。とにかく更新されました。
janm 2016年

0

「醜い」バージョンの代わりに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 );
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.