Общий способ приведения int к перечислению в C++



есть ли общий способ бросить int до enum in C++?



если int попадает в диапазон enum он должен возвратить enum значение, в противном случае бросьте exception. Есть ли способ написать это в общем? Более одного enum type должна быть поддержана.



фон: у меня есть внешний перечисление тип и нет контроля над исходным кодом. Я хотел бы сохранить это значение в базе данных и извлекать оно.

990   9  

9 ответов:

очевидная вещь, чтобы аннотировать ваше перечисление:

// 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:

для перечисления, где Эмин - наименьший перечислитель и emax - это самый большой, значения перечисления значения базовый тип в диапазоне bmin до bmax, где bmin и bmax, соответственно, наименьшего и наибольшего значений наименьший бит-поле, которое может хранить Эмин и эмакс. Это можно определить перечисление, у которого нет значений определяется любым из его перечислителей.

так что если вы не автор 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();
}

теперь сам вопрос. Зачем тебе это нужно? Код уродливый, не легко писать (*?) и не легко поддерживать, и не легко включить в свой код. Код это говорит вам, что это неправильно. Зачем с этим бороться?

EDIT:

альтернативно, учитывая, что перечисления являются целыми типами в C++:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

но это еще уродливее, что выше, гораздо более склонны к ошибкам, и он не будет бросать, как вы хотите.

Если, как вы описываете, значения в базе данных, почему бы не написать генератор кода, который читает эту таблицу и создает .h и .cpp-файл с перечислением и A ?

плюсы:

  • легко добавить

нет-в C++ нет самоанализа, и нет встроенного средства "проверка домена".

что вы думаете об этом?

#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"
    }
}

затем вы можете использовать код, который я опубликовал здесь для включения значений.

вы не должны хотеть, чтобы что-то вроде того, что вы описываете, существовало, я боюсь, что в вашем дизайне кода есть проблемы.

кроме того, вы предполагаете, что перечисления входят в диапазон, но это не всегда так:

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 альтернатива "уродливой" версии, позволяет использовать несколько перечислений. Использует списки инициализаторов, а не переключатели, немного чище ИМО. К сожалению, это не работает вокруг необходимости жесткого кода значений перечисления.

#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 EType
{
  type1,
  type2
};

unsigned int number = 3;
EType e = static_cast<EType>(number);
if(static_cast<unsigned int>(e) != number)
  throw std::exception();

Comments

    Ничего не найдено.