отказоустойчивое преобразование строки в класс enum

У меня есть такой класс перечисления (позже я планирую добавить к нему дополнительные параметры):

enum class ViSequencePointType {
   JumpToValue = 0,
   RampToValue = 1
};

Затем у меня есть текстовый файл конфигурации, каждая строка которого должна представлять одно из значений перечисления. Что-то вроде этого:

1
0
255
A
WTF

Мне нужно проанализировать этот файл и создать вектор этого класса enum... поэтому я делаю что-то вроде:

    bool conversionResult = false;
    int colThree = line.toInt(&conversionResult);
    if(!conversionResult) { 
         //failed to convert to integer
    } else {
    ViSequencePointType pt = static_cast<ViSequencePointType>(colThree);
    switch(pt) {
        case ViSequencePointType::JumpToValue:
            break;
        case ViSequencePointType::RampToValue:
            break;
        default:
            break;
    }

для этого случая default компилятор говорит

Метка по умолчанию в переключателе, которая охватывает все значения перечисления

что, как я полагаю, означает, что если в текстовом файле существует какая-либо недопустимая запись, я не могу ее найти!

Итак, как мне решить эту проблему, не допуская проскальзывания какого-либо недопустимого перечисления во время времени выполнения?


person DEKKER    schedule 19.02.2019    source источник
comment
Выдать ошибку в случае default вашего оператора switch? Например, для строки «255» в вашем файле.   -  person vahancho    schedule 19.02.2019
comment
Нет, предупреждение компилятора означает, что вы добавили значение по умолчанию, но не осталось допустимых значений перечисления, которые могли бы привести к значению по умолчанию. Другими словами, вы можете получить значение по умолчанию, только сделав что-то недопустимое. Независимо от того, является ли приведение 255 к ViSequencePointType UB, является другой вопрос.   -  person Max Langhof    schedule 19.02.2019
comment
Вы можете добавить первое и последнее значение в перечисление ({SeqPointFirst=0,JumpToValue=0,RampToValue,SeqPointLast=RampToValue}), а затем просто проверить диапазон целого числа перед приведением к перечислению. Конечно, это работает, только если ваши значения перечисления последовательны.   -  person Karsten Koop    schedule 19.02.2019
comment
Это раздражение от компилятора. Должно быть только предупреждение, а код /должен/ работать корректно. Но спецификация С++ 11 явно позволяет хранить значения в типах перечисления, которые не являются объявленными значениями. Требуется только, чтобы такие значения находились в диапазоне объявленных значений или объявленного базового типа. Существует даже положение для четкого присвоения таких значений переменной класса перечисления из С++ 17.   -  person Gem Taylor    schedule 19.02.2019


Ответы (1)


Чтобы покрыть недопустимые/бессмысленные значения перечисления, обычной практикой является

  • полагаться на тот факт, что последующим значениям перечисления неявно присваивается значение предыдущего значения перечисления + 1
  • добавьте значение перечисления "Invalid" в самое низкое значение в перечислении (неявно 0, или вы можете присвоить ему низкое значение, например -1)
  • добавить значение перечисления "Max" к самому высокому значению перечисления

Вот пример:

enum class ViSequencePointType 
{
    Invalid = -1,

    JumpToValue,            // is implicitly assigned enum value 0 (-1 + 1 == 0)
    RampToValue,            // is implicitly 1 (JumpToValue + 1)
    CrawlToValue,           // etc...
    HopToValue,    
    // add new values here       

    Max                     // Max must be the last value in the enum
};

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

ViSequencePointType parse(const std::string& value)
{
    bool converted = false;
    int val = line.toInt(&converted);
    if(!converted) 
    { 
         // do additional conversion failure handling here if necessary
         return ViSequencePointType::Invalid;
    } 
    if (val <= static_cast<int>(ViSequencePointType::Invalid) ||
        val >= static_cast<int>(ViSequencePointType::Max)
    {
         // do additional out of bounds handling here if necessary
         return ViSequencePointType::Invalid;
    }
    return static_cast<ViSequencePointType>(val);
}

Теперь вы знаете, что вывод из parse является допустимым значением перечисления, а вывод для неизвестных/недопустимых значений обозначается значением перечисления Invalid.

person Steve Lorimer    schedule 07.03.2019