1. ホーム
  2. c++

[解決済み] enumクラスに無効な値をstatic_castしたらどうなる?

2022-05-30 16:16:30

質問

このC++のコードを考えてみましょう。

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

data[0]が実際には100であったとします。規格上、colorは何色に設定されているのでしょうか? 特に、もし私が後で

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

は、標準ではデフォルトがヒットすることを保証しているのでしょうか?もしそうでなければ、ここでエラーをチェックするための適切で、最も効率的で、最もエレガントな方法は何でしょうか?標準はこれについての保証をしますが、プレーンなenumではどうでしょうか?

どのように解決するのですか?

<ブロッククオート

標準ではどのような色に設定されていますか?

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 である必要があります。


expr]/4と比較

式の評価中に、結果が数学的に定義されていない、またはその型に対して表現可能な値の範囲にない場合、動作は未定義となります。

CWG1766以前では、変換積分型 -> 列挙型では 不特定値 . という質問があります。 不特定値はその型に対して表現可能な値の外にあることができますか? 私は、答えは いいえ -- もし、その答えが はい であった場合、符号付きの型に対する操作で得られる保証には、"この操作は未定義の値を生成する" と "この操作は未定義の動作をする" の間に何の違いもないでしょう。

したがって、CWG 1766 より前は、たとえ static_cast<Color>(10000) ではない はUBを呼び出します。しかし、CWG1766以降では する。 はUBを呼び出す。


さて、この switch ステートメントを使用します。

[stmt.switch]・2

条件は、積分型、列挙型、またはクラス型でなければならない。[...] 積分型の昇格が行われる。

[conv.prom]/4

<ブロッククオート

のプルバリューは 非スコープの のprvalueは、その基礎となる型のprvalueに変換することができます(7.2)。さらに、もし積分促進がその基礎となる型に適用できるなら、基礎となる型が固定されている非スコープの列挙型のprvalueも、促進された基礎となる型のprvalueに変換することができる。

注意: スコープされた enum の基本型は enum-base int . スコープされていない列挙型の場合、基礎となる型は実装で定義されますが、以下の値より大きくてはいけません。 int もし int は全ての列挙子の値を含むことができます。

には 非スコープの列挙 の場合、これは /1 につながります。

以外の整数型のprvalは bool , char16_t , char32_t または wchar_t のランクより小さく、整数変換のランク (4.13) は int の型のprvalueに変換することができます。 int もし int はソース型の全ての値を表すことができ、そうでなければ、ソース型のprvalueは、タイプ unsigned int .

の場合は 非スコープの の列挙の場合、私たちが扱うのは int を扱うことになります。の場合 スコープ の列挙( enum classenum struct を含む)、積分促進は適用されません。いずれにせよ、積分促進はUBにもつながりません。格納された値は、基礎となる型の範囲内と int .

[stmt.switch]/5件

<ブロッククオート

を実行すると switch ステートメントが実行されると、その条件が評価され、各ケース定数と比較されます。ケース定数のいずれかが条件の値と等しい場合、制御はマッチした case ラベルに続くステートメントに制御が移る。もし case 定数が条件にマッチせず、かつ default ラベルがある場合、制御は default ラベルで指定されたステートメントに制御が移ります。

default のラベルがヒットするはずです。

注意: 比較演算子をもう一度見てみることもできますが、参照される "comparison" では明示的に使用されていません。実際、私たちのケースでは、スコープされた、またはスコープされていない列挙型の UB を導入するヒントがありません。


おまけですが、これに関してもenumでプレーンなものは規格で保証されているのでしょうか?

の有無は enum がスコープされているかどうかは、ここでは何の違いも生じません。しかし、基礎となる型が固定されているかどうかの違いはあります。完全な[decl.enum]/7は。

基礎となる型が固定されている列挙型については、列挙型の値は基礎となる型の値である。そうでない場合、列挙型に対して e min は最小の列挙体であり e が最大 が最大であれば、列挙された値は範囲内の b min から b <サブ 最大 は,次のように定義される.ここで K である 1 は 2 の補数表現で 0 は 1 の補数または符号-倍数表現になります。 b 最大 と同等以上の最小の値です。 max(|e min | - K , |e <サブ 最大 |) と等しく、かつ 2 M - 1 ここで M は非負の整数である。 b min が0であれば e min は非負であり -(b 最大 + K ) のように、それ以外の場合は

次の列挙を見てみましょう。

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

すべてのスコープ付き列挙型は固定した基礎型を持つので、これをスコープ付き列挙型として定義することはできないことに注意してください。

幸いなことに ColorUnfixed の最小の列挙子は red = 0x1 であり、したがって max(|e min | - K , |e <サブ 最大 |) |e 最大 | というのは、いずれにしても yellow = 0x2 . より大きいか等しい最小の値は 2 と等しく、かつ 2 M - 1 正の整数の場合 M3 ( 2 2 - 1 ). (1ビット単位で範囲を広げることを意図していると思われます。) よって、以下のようになります。 b 最大 3 であり bmin 0 .

したがって 100 の範囲外になってしまいます。 ColorUnfixed の範囲外であり static_cast は、CWG 1766以前は未指定の値を生成し、CWG 1766以降は未定義の動作を生成するでしょう。