Общий способ использования int для перечисления в С++

Существует ли общий способ отбрасывания int до enum в C++?

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

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

Ответ 1

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

// 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. Как говорит Сьорд, он может быть автоматизирован с любой достойной системой сборки.

В любом случае, вы против 7.2/6:

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

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

Ответ 2

Гадкий.

enum MyEnum { one = 1, two = 2 };

MyEnum to_enum(int n)
{
  switch( n )
  {
    case 1 :  return one;
    case 2 : return two;
  }
  throw something();
}

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

EDIT:

В качестве альтернативы, учитывая, что перечисления являются интегральными типами в С++:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

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

Ответ 3

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

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

  • Легко добавить функцию to_string(my_enum).
  • Небольшое техническое обслуживание
  • База данных и код находятся в синхронизации

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

С++ 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 );
    }
}

Ответ 9

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

enum EType
{
  type1,
  type2
};

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