Подтвердить что ты не робот

Обнаружение, если листинг int для перечисления приводит к неперечисленному значению

Скажем, у меня есть что-то вроде этого:

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES};

CardColor MyColor = static_cast<CardColor>(100);

Есть ли (простой) способ обнаружить во время компиляции или во время выполнения значение MyColor не соответствует любому перечислимому значению?

И в общем случае, если значения перечисления не следуют друг за другом, например:

enum CardColor { HEARTS = 0, DIAMONDS, CLUBS = 4, SPADES};
4b9b3361

Ответ 1

CashCow представляет достойный ответ на этот вопрос: для выполнения проверенного акта, безусловно, просто написать пользовательскую функцию.

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

Вместо того, чтобы выполнять всю эту ручную работу, мы можем автоматизировать генерацию всего этого кода с помощью препроцессора (с небольшой помощью из библиотеки Boost Preprocessor). Вот макрос, который генерирует определение перечисления вместе с функцией checked_enum_cast. Это, вероятно, немного страшно выглядит (макросы генерации кода часто ужасны, чтобы смотреть на них), но это чрезвычайно полезный метод, чтобы ознакомиться с ним.

#include <stdexcept>
#include <boost/preprocessor.hpp>

// Internal helper to provide partial specialization for checked_enum_cast
template <typename Target, typename Source>
struct checked_enum_cast_impl;

// Exception thrown by checked_enum_cast on cast failure
struct invalid_enum_cast : std::out_of_range 
{ 
    invalid_enum_cast(const char* s)
        : std::out_of_range(s) { }
};

// Checked cast function
template <typename Target, typename Source>
Target checked_enum_cast(Source s)
{
    return checked_enum_cast_impl<Target, Source>::do_cast(s);
}

// Internal helper to help declare case labels in the checked cast function
#define X_DEFINE_SAFE_CAST_CASE(r, data, elem) case elem:

// Macro to define an enum with a checked cast function.  name is the name of 
// the enumeration to be defined and enumerators is the preprocessing sequence
// of enumerators to be defined.  See the usage example below.
#define DEFINE_SAFE_CAST_ENUM(name, enumerators)                           \
    enum name                                                              \
    {                                                                      \
        BOOST_PP_SEQ_ENUM(enumerators)                                     \
    };                                                                     \
                                                                           \
    template <typename Source>                                             \
    struct checked_enum_cast_impl<name, Source>                            \
    {                                                                      \
        static name do_cast(Source s)                                      \
        {                                                                  \
            switch (s)                                                     \
            {                                                              \
            BOOST_PP_SEQ_FOR_EACH(X_DEFINE_SAFE_CAST_CASE, 0, enumerators) \
                return static_cast<name>(s);                               \
            default:                                                       \
                throw invalid_enum_cast(BOOST_PP_STRINGIZE(name));         \
            }                                                              \
            return name();                                                 \
        }                                                                  \
    };

Вот как вы могли бы использовать это со своим примером CardColor:

DEFINE_SAFE_CAST_ENUM(CardColor, (HEARTS) (CLUBS) (SPADES) (DIAMONDS))

int main()
{
    checked_enum_cast<CardColor>(1);   // ok
    checked_enum_cast<CardColor>(400); // o noez!  an exception!
}

Первая строка заменяет ваше определение enum CardColor ...; он определяет перечисление и предоставляет специализацию, которая позволяет использовать checked_enum_cast для целых чисел для CardColor.

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

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

Ответ 2

Простейшим решением во время выполнения было бы не использовать static_cast, а использовать функцию, которая выполняет проверку для вас. Если вы поместите свой enum внутри класса, вы можете сделать это через класс. Что-то вроде:

class CardCheck
{
public:
  enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES };

  explicit CardCheck( int x ) : c( static_cast< CardColor >( x ) )
  {
     switch( c )
     {
       case HEARTS: case DIAMONDS: case CLUBS: case SPADES:
          break;

       default:
         // assert or throw
    }
  }

  CardColor get() const
  {
     return c;
  }

 private:
  CardColor c;      
};

Ответ 3

clang поддерживают динамические проверки переполнения. См. - fsanitize = enum. Программа, скомпилированная с этим переключателем, будет сигнализировать ошибки присвоения перечисления через вывод stderr. Это позволит вам выполнять тесты отладки. Не рекомендуется проверять подозрительный ввод в официальной сборке.

Ответ 4

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

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES, CARDS_COUNT};

CardColor MyColor = static_cast<CardColor>(100);

if (MyColor >= CARDS_COUNT) {
    /* Invalid value */
}

Ответ 5

В начале - это плохая идея сделать это.

Но если вы хотите, я предлагаю жестко кодировать целочисленные значения перечисления:

enum CardColor { HEARTS = 10, DIAMONDS = 11, CLUBS = 12, SPADES = 13};

Затем оператор присваивания перегрузки:

CardColor operator = (int value)
{
    switch (value)
    {
        case 10:
            return HEARTS;
        // case for other values
        default:
            // throw an exception or something
    }
}

Ответ 6

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

enum suites { hearts, diamonds, clubs, spades };

допускает значения 0, 1, 2, 3;

enum suites { hearts = 1 << 0, diamonds = 1 << 1, clubs = 1 << 2, spades = 1 << 4 };

позволяет любое значение от 0 до 15.

Если вы используете перечисление для определения значений битов, обычно рекомендуется определять (двоичные) operator&, operator|, operator&= и operator|=. Если вы этого не сделаете, вам понадобится явное приведение всякий раз, когда генерируется значение, не входящее в набор, поэтому места, где это происходит, могут быть легко обнаружены.

Есть компиляторы, которые могут предупреждать, если назначено число за пределами разрешенного диапазона, или если ни один из них не содержит ни одного, у каждого или первого имени есть инициализаторы, прикрепленные к ним (это является нарушением правил MISRA-C).