Общий способ привести int к enum в C ++


82

Есть ли универсальный способ бросить intна enumв C++?

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

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


enum e{x = 10000};9999попадает ли в этом случае в диапазон enum?
Армен Цирунян

Нет, 9999не падает.
Леонид

9
Хороший вопрос. Что касается любого «почему?» который должен появиться, позвольте мне просто сказать «десериализация» - кажется мне достаточной причиной. Я также был бы рад услышать ответ, совместимый с C ++ 0x для enum class.
Кос,

9
"Диапазон" здесь неправильное слово, может быть, "домен"?
Константин

boost :: numeric_cast <> вызывает исключение положительного или отрицательного переполнения, если значение выходит за допустимые пределы. Но не уверен, подходит ли это и для типов перечислений. Вы можете попробовать это.
yasouser

Ответы:


38

Очевидно, что нужно аннотировать ваше перечисление:

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


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.
Simone

2
@Leonid: Насколько я знаю, это невозможно сделать в целом. На каком-то уровне любое решение, которое вы придумаете, которое будет throw(или сделает что-то особенное) для недопустимых типов, должно иметь переключатель, как я опубликовал.
Джон Диблинг,

2
Почему это минус? Это правильный ответ. Тот факт, что это не тот ответ, на который некоторые надеялись, не означает, что это неправильно.
Джон Диблинг,

12
A также static_cast<MyEnum>будет работать, и его следует предпочестьreinterpret_cast<MyEnum>
Sjoerd

1
Этот подход работает хорошо, и даже лучше, если вы используете инструмент для создания функции.
Ник

3

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

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

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

Нет никакого контроля над исходным кодом типа enum . Я согласен с тем, что может быть создано средство, реализующее преобразование. Однако средство не будет знать о каких-либо изменениях / расширениях, внесенных во внешний тип перечисления (если они не выполняются каждый раз во время компиляции).
Леонид

@Leonid Затем прочтите заголовок перечисления и сгенерируйте to_enum(int)функцию на его основе.
Sjoerd

@Leonid Любая серьезная система управления проектами makeможет даже сравнить дату двух файлов, чтобы определить, нужно ли перезапускать генератор.
Sjoerd

Я думаю, что сейчас выберу более простое решение для генератора. Но спасибо за идею.
Леонид

Мы используем эту схему на своем рабочем месте, инструмент генерирует код .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

1
Он сказал, что значения берутся из базы данных, поэтому я добавил метод «вставки».
Simone

1

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

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

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

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


как бы вы иначе получили значения перечисления из базы данных? Целые числа известны во время компиляции, так почему же невозможно выполнить универсальное преобразование на основе шаблонов?
Леонид

2
@Leonid: Потому что на каком-то уровне вам нужен переключатель, как я уже сказал.
Джон Диблинг,

2
Шаблоны @Leonid - не панацея для решения любой проблемы, о которой вы только можете подумать.
Sjoerd

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

Я опубликовал решение, использующее иерархию классов, проверьте его.
Simone

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

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 );
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.