Класс перечисления C ++: приведение к несуществующей записи - PullRequest
2 голосов
/ 28 марта 2019

У меня такая ситуация в одном проекте, где у нас есть некоторое сокет-соединение, которое в основном обменивает символы для управления потоком.Мы приводим эти символы к enum class : char в переключателе.Мне было интересно, что может произойти, если другой конец отправит символ, которого нет в нашем классе enum.

У меня есть это mwe:

enum class Foo : char {
    UNKNOWN,
    ENUM1 = 'A',
    ENUM2 = 'B',
    ENUM3 = 'C'
};

char bar1() {
    return 'B';
}

char bar2() {
    return 'D';
}

int main() {
    switch((Foo)bar1()) {
        case Foo::UNKNOWN:std::cout << "UNKNWON" << std::endl;break;
        case Foo::ENUM1:std::cout << "ENUM1" << std::endl;break;
        case Foo::ENUM2:std::cout << "ENUM2" << std::endl;break;
        case Foo::ENUM3:std::cout << "ENUM3" << std::endl;break;
        default:std::cout << "DEFAULT" << std::endl;break;
    }
    switch((Foo)bar2()) {
        case Foo::UNKNOWN:std::cout << "UNKNWON" << std::endl;break;
        case Foo::ENUM1:std::cout << "ENUM1" << std::endl;break;
        case Foo::ENUM2:std::cout << "ENUM2" << std::endl;break;
        case Foo::ENUM3:std::cout << "ENUM3" << std::endl;break;
        default:std::cout << "DEFAULT" << std::endl;break;
    }
    return 0;
}

В этом примере у меня есть enum class : char с неуказанной записью и тремя назначенными символами записями.Когда я запускаю его, я получаю вывод

ENUM2
DEFAULT

Это, кажется, работает безупречно, так как неопределенный пример просто переходит к случаю по умолчанию.Тем не менее, это «сохранить, чтобы сделать»?Есть ли какие-то подводные камни или другие осложнения, которые я мог бы сейчас не увидеть?

Ответы [ 2 ]

2 голосов
/ 28 марта 2019

Это полностью безопасно, потому что:

  • ваш enum class - это перечисление в области видимости;
  • ваше перечисление имеет фиксированный базовый тип : char;
  • поэтому значения вашего перечисления являются значениями типа char;
  • так что приведение значения char к перечислению полностью допустимо.

Здесь C ++ 17 стандартных кавычек, которые соответствуют приведенным выше утверждениям:

[dcl.enum] / 2: (...) Клавиши enum enum class и enum struct являются семантически эквивалентный; тип перечисления, объявленный с одним из это перечисление scoped , а его перечислители ограничены счетчики.

[dcl.enum] / 5: (...) Каждое перечисление также имеет базовый тип. базовый тип может быть явно указан с помощью enum-base. (...) В обоих этих случаях базовый тип называется исправлена ​​. (...)

[dcl.enum] / 8: Для перечисления, базовый тип которого фиксирован, значения перечисления являются значениями базового типа . (...)

[expr.static.cast] / 10 Значение целочисленного типа или типа перечисления может быть явно преобразован в полный тип перечисления. Если тип перечисления имеет фиксированный базовый тип, значение является первым преобразовать в этот тип путем интегрального преобразования, если необходимо, а затем к типу перечисления. [expr.cast] / 4 Преобразования, выполненные const_cast, static_cast, static_cast, за которым следует const_cast, reinterpret_cast, reinterpret_cast, сопровождаемый const_cast, может быть выполняется с использованием приведенной нотации явного преобразования типов. (...) Если преобразование можно интерпретировать более чем одним из перечисленных способов выше используется интерпретация, которая появляется первой в списке (...)

Выводы будут другими, если базовый тип не будет фиксированным. В этом случае будет применяться оставшаяся часть [dcl.enum] / 8: более или менее сказано, что если вы не находитесь в пределах наименьшего и наибольшего перечислителей перечисления, вы не уверены, что значение может быть представлял.

См. Также вопрос Разрешено ли перечислению иметь незарегистрированное значение? , которое является более общим (C ++ & C), но не использует перечисление с областью видимости или указанный базовый тип.

А здесь фрагмент кода для использования значений перечисления, для которых не определен перечислитель:

switch((Foo)bar2()) {
    case Foo::UNKNOWN:          std::cout << "UNKNWON" << std::endl;break;
    case Foo::ENUM1:            std::cout << "ENUM1" << std::endl;break;
    case Foo::ENUM2:            std::cout << "ENUM2" << std::endl;break;
    case Foo::ENUM3:            std::cout << "ENUM3" << std::endl;break;
    case static_cast<Foo>('D'): std::cout << "ENUM-SPECIAL-D" << std::endl;break;
    default:                    std::cout << "DEFAULT" << std::endl;break;
}
1 голос
/ 28 марта 2019

Это не совсем безопасно.Я обнаружил, что стандарт C ++, [expr.static.cast], параграф 10, гласит следующее:

Значение целочисленного типа или типа перечисления может быть явно преобразовано в тип перечисления.Значение не изменяется, если исходное значение находится в диапазоне значений перечисления (7.2).В противном случае результирующее значение не определено (и может не входить в этот диапазон).Значение типа с плавающей точкой также может быть явно преобразовано в тип перечисления.Результирующее значение аналогично преобразованию исходного значения в базовый тип перечисления (4.9), а затем в тип перечисления.

В разделе 7.2 объясняется, как определяются пределы:

Для перечисления, базовый тип которого является фиксированным, значения перечисления являются значениями базового типа.В противном случае для перечисления, где emin является наименьшим перечислителем, а emax является наибольшим, значения перечисления представляют собой значения в диапазоне от bmin до bmax, определяемые следующим образом: пусть K будет 1 для представления дополнения до двух и 0 для единицыдополнение или представление величины знака.bmax является наименьшим значением, большим или равным max (| emin | - K, | emax |) и равным 2M - 1, где M - неотрицательное целое число.bmin равно нулю, если emin неотрицателен, и - (bmax + K) в противном случае.Размер наименьшего битового поля, достаточного для хранения всех значений типа перечисления, равен max (M, 1), если bmin равно нулю, и M + 1 в противном случае.Можно определить перечисление, значения которого не определены ни одним из перечислителей.Если список перечислителя пуст, значения перечисления такие, как если бы перечисление имело единственный перечислитель со значением 0.

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

...