Загальний спосіб передати int в enum у C ++


82

Чи є універсальний спосіб кинути intна enumв C++?

Якщо intпотрапляє в діапазон an, 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.
Сімоне

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

2
Чому це -1'ed? Це правильна відповідь. Те, що це не відповідь, на яку сподівалися деякі, не означає, що вона неправильна.
Джон Дайблінг

12
A також static_cast<MyEnum>буде працювати, і йому слід віддавати перевагу надreinterpret_cast<MyEnum>
Sjoerd

1
Цей підхід працює добре, і навіть краще, якщо ви використовуєте інструмент для генерації функції.
Нік

3

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

Переваги:

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

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

Потім прочитайте заголовок переліку та сформуйте на 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
Він сказав, що значення походять з бази даних, тому я додав метод "вставки".
Сімоне

1

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

Крім того, ви припускаєте, що перелічення знаходяться в діапазоні, але це не завжди так:

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

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


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

2
@ Леонід: Тому що на якомусь рівні потрібно мати перемикач, як я вже сказав.
Джон Дайблінг,

2
@Leonid Шаблони - це не срібна куля, щоб вирішити кожну проблему, яку ви можете подумати.
Sjoerd

Джон має рацію. Вам потрібен тип, більш складний, ніж enum, щоб робити те, що ви хочете, я думаю, це можливо з ієрархією класів.
Сімоне

Я опублікував рішення, яке використовує ієрархію класів, перевірте його.
Сімоне

1

Якщо ви готові вказати значення перерахування як параметри шаблону, ви можете зробити це в C ++ 11 за допомогою нестандартних шаблонів. Ви можете розглядати це як хорошу річ, дозволяючи приймати підмножини дійсних значень перерахування в різних контекстах; часто корисний при аналізі кодів із зовнішніх джерел.

Можливо, не настільки загальний, як хотілося б, але сам код перевірки узагальнений, вам просто потрібно вказати набір значень. Цей підхід обробляє прогалини, довільні значення тощо.

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.