Общий способ приведения int к enum в C ++ - PullRequest
79 голосов
/ 12 ноября 2010

Есть ли общий способ приведения int к enum в C++?

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

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

Ответы [ 9 ]

37 голосов
/ 12 ноября 2010

Очевидная вещь - комментировать ваше перечисление:

// 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 действительно появятся в его определении.

20 голосов
/ 12 ноября 2010

Гадкий.

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

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

3 голосов
/ 12 ноября 2010

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

Преимущества:

  • Легко добавить функцию to_string(my_enum).
  • Требуется мало обслуживания
  • База данных и код синхронизированы
2 голосов
/ 12 ноября 2010

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

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

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

2 голосов
/ 12 ноября 2010

Нет - в C ++ нет самоанализа и нет встроенной функции проверки домена.

1 голос
/ 13 октября 2015

Если вы готовы перечислить значения перечисления в качестве параметров шаблона, вы можете сделать это в 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);
}
1 голос
/ 12 ноября 2010

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

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

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

Это не в диапазоне: даже если это было возможно, вы должны проверять каждое целое число от 0 до 2 ^ n, чтобы увидеть, соответствуют ли они некоторому значению перечисления?

0 голосов
/ 26 февраля 2014

Попробуйте что-то вроде этого:

enum EType
{
  type1,
  type2
};

unsigned int number = 3;
EType e = static_cast<EType>(number);
if(static_cast<unsigned int>(e) != number)
  throw std::exception();
0 голосов
/ 06 января 2014

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 );
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...