Что произойдет, если вы укажете static_cast недопустимое значение для перечисления класса?

146

Рассмотрим этот код C ++ 11:

enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(data[0]);

Предположим, что data [0] на самом деле 100. Какой цвет установлен в соответствии со стандартом? В частности, если я позже сделаю

switch (color) {
    // ... red and yellow cases omitted
    default:
        // handle error
        break;
}

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

РЕДАКТИРОВАТЬ:

В качестве бонуса стандарт дает какие-либо гарантии как об этом, но с простым перечислением?

дарт хэппифэйл
источник

Ответы:

131

Какой цвет установлен в соответствии со стандартом?

Ответ с цитатой из стандартов C ++ 11 и C ++ 14:

[Expr.static.cast] / 10

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

Давайте посмотрим диапазон значений перечисления : [dcl.enum] / 7

Для перечисления, базовый тип которого является фиксированным, значения перечисления являются значениями базового типа.

До CWG 1766 (C ++ 11, C ++ 14) Следовательно, для data[0] == 100, результирующее значение указывается (*), и не используется неопределенное поведение (UB) . В более общем смысле, когда вы преобразуете базовый тип в тип перечисления, никакое значение в не data[0]может привести к UB для static_cast.

После CWG 1766 (C ++ 17) См. Дефект CWG 1766 . Абзац [expr.static.cast] p10 был усилен, так что теперь вы можете вызывать UB, если приведете значение, которое находится за пределами представимого диапазона перечисления, к типу перечисления. Это по-прежнему не относится к рассматриваемому сценарию, поскольку data[0]относится к базовому типу перечисления (см. Выше).

Обратите внимание, что CWG 1766 считается дефектом в Стандарте, поэтому для исполнителей компилятора принято применять к своим режимам компиляции C ++ 11 и C ++ 14.

(*) charдолжен иметь ширину не менее 8 бит, но не обязательно unsigned. Максимальное хранимое значение должно быть как минимум 127согласно Приложению E к стандарту C99.


Сравнить с [expr] / 4

Если во время вычисления выражения результат не определен математически или не находится в диапазоне представляемых значений для его типа, поведение не определено.

До CWG 1766 целочисленный тип преобразования -> тип перечисления может давать неопределенное значение . Вопрос заключается в следующем: может ли неуказанное значение находиться за пределами представимых значений для его типа? Я считаю , что ответ нет - если ответ был да , то не было бы никакой разницы в гарантии вы получаете для операций по подписанным между типами «эта операция производит неопределенное значение» и «эта операция имеет неопределенное поведение».

Следовательно, до CWG 1766, даже неstatic_cast<Color>(10000) будет вызывать UB; но после того, как РГС 1766, он делает Invoke UB.


Теперь switchутверждение:

[Stmt.switch] / 2

Условие должно быть целочисленного типа, типа перечисления или типа класса. [...] Интегральные продвижения выполняются.

[Conv.prom] / 4

Значение типа перечисления с незаданной областью , базовый тип которого является фиксированным (7.2), может быть преобразовано в значение базового типа. Кроме того, если интегральное продвижение может быть применено к его базовому типу, значение типа перечисления с незаданной областью, базовый тип которого является фиксированным, также может быть преобразовано в значение улучшенного базового типа.

Примечание: Основной тип Scoped перечисления без перечисления базы является int. Для перечислений с незаданной областью базовый тип определяется реализацией, но не должен быть больше, чем intесли бы он intмог содержать значения всех перечислителей.

Для перечисления с незаданной областью это приводит нас к / 1

Prvalue целого типа, кроме bool, char16_t, char32_tили wchar_tчье число преобразования ранга (4.13) меньше , чем ранг intможет быть преобразован в prvalue типа , intесли intможно представить все значения типа источника; в противном случае исходное значение может быть преобразовано в тип значения unsigned int.

В случае перечисления с незаданной областью мы будем иметь дело с ints здесь. Для перечислений с областью действия ( enum classи enum struct) интегральное продвижение не применяется. В любом случае, интегральное продвижение также не приводит к UB, так как сохраненное значение находится в диапазоне базового типа и в диапазоне int.

[Stmt.switch] / 5

Когда switchоператор выполняется, его условие оценивается и сравнивается с каждой константой. Если одна из констант регистра равна значению условия, управление передается оператору после соответствующей caseметки. Если никакая caseконстанта не соответствует условию и если есть defaultметка, управление переходит к оператору, помеченному defaultметкой.

defaultМетка должна быть хитом.

Примечание. Можно еще раз взглянуть на оператор сравнения, но он явно не используется в упомянутом «сравнении». На самом деле, нет никаких намеков на то, что в нашем случае мы введем UB для перечислений с ограниченным или незаданным объемом.


В качестве бонуса стандарт дает какие-либо гарантии как об этом, но с простым перечислением?

Независимо от того enum, ограничена ли область действия, здесь не имеет значения. Тем не менее, имеет значение, является ли базовый тип фиксированным. Полный [decl.enum] / 7:

Для перечисления, базовый тип которого является фиксированным, значения перечисления являются значениями базового типа. В противном случае для перечисления, где e min - наименьший перечислитель, а e max - наибольшее, значения перечисления - это значения в диапазоне от b min до b max , определяемые следующим образом: Позвольте Kбыть 1для представления дополнения до двух и 0для свое дополнение или представление величины знака. b max - наименьшее значение, большее или равное max (| e min | - K, | e max |) и равное 2М- 1 , где Mнеотрицательное целое число. b min равно нулю, если e min неотрицательно, и - (b max + K) в противном случае.

Давайте посмотрим на следующее перечисление:

enum ColorUnfixed /* no fixed underlying type */
{
    red = 0x1,
    yellow = 0x2
}

Обратите внимание, что мы не можем определить это как перечисление с областью видимости, так как все перечисления с областью видимости имеют фиксированные базовые типы.

К счастью, ColorUnfixedнаименьший перечислитель есть red = 0x1, поэтому max (| e min | - K, | e max |) равно | e max | в любом случае, который есть yellow = 0x2. Наименьшее значение, большее или равное 2, равное 2 M - 1 для положительного целого числа, Mравно 3( 2 2 - 1 ). (Я думаю, что цель состоит в том, чтобы разрешить диапазон в 1-битных шагах.) Из этого следует, что b max есть, 3а bmin есть 0.

Следовательно, 100будет вне диапазона ColorUnfixed, и static_castбудет производить неопределенное значение до CWG 1766 и неопределенное поведение после CWG 1766.

дип
источник
3
Базовый тип является фиксированным, поэтому диапазон значений перечисления (§7.2 [dcl.enum] p7) - это «значения базового типа». 100, безусловно, является значением char, поэтому «Значение не изменяется, если исходное значение находится в диапазоне значений перечисления (7.2)». применяется.
Кейси
2
Мне пришлось искать, чтобы посмотреть, что означает «UB». («неопределенное поведение») В вопросе не упоминалась возможность неопределенного поведения; так что мне не пришло в голову, что вы можете говорить об этом.
karadoc
2
@karadoc Я добавил ссылку при первом появлении термина.
13
1
Люблю этот ответ. Для тех, кто занимается слишком быстрым скиммингом, обратите внимание, что последнее предложение «Следовательно, 100 будет за пределами диапазона ...» применяется только в том случае, если код был изменен для удаления базовой спецификации типа (в данном случае char). Я думаю, это то, что имелось ввиду.
Эрик Сеппанен
1
@Ruslan CWG 1766 (или его разрешение) не является частью C ++ 14, но я думаю, что он будет частью C ++ 17. Даже с правилами C ++ 17 я не совсем понимаю, что вы имеете в виду под «лишить законной силы дальнейший текст вашего ответа». Другие части моего ответа в основном касаются того, что «диапазон значений перечисления» - это то, на что ссылается expr.static.cast p10.
DYP