Apa yang terjadi jika Anda static_cast nilai tidak valid ke kelas enum?

146

Pertimbangkan kode C ++ 11 ini:

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

Misalkan data [0] sebenarnya 100. Apa warna yang diatur sesuai dengan standar? Secara khusus, jika nanti saya lakukan

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

apakah jaminan standar bahwa default akan terkena? Jika tidak, apa cara yang tepat, paling efisien, paling elegan untuk memeriksa kesalahan di sini?

EDIT:

Sebagai bonus, apakah standar membuat jaminan seperti ini tetapi dengan enum sederhana?

darth happyface
sumber

Jawaban:

131

Apa warna diatur sesuai dengan standar?

Menjawab dengan kutipan dari Standar C ++ 11 dan C ++ 14:

[expr.static.cast] / 10

Nilai tipe integral atau enumerasi dapat secara eksplisit dikonversi ke tipe enumerasi. Nilai tidak berubah jika nilai asli berada dalam kisaran nilai enumerasi (7.2). Jika tidak, nilai yang dihasilkan tidak ditentukan (dan mungkin tidak berada dalam kisaran itu).

Mari kita mencari rentang nilai enumerasi : [dcl.enum] / 7

Untuk enumerasi yang tipe dasarnya adalah tetap, nilai-nilai enumerasi adalah nilai-nilai dari tipe yang mendasarinya.

Sebelum CWG 1766 (C ++ 11, C ++ 14) Oleh karena itu, untuk data[0] == 100, nilai yang dihasilkan ditentukan (*), dan tidak ada Perilaku Tidak Terdefinisi (UB) yang terlibat. Lebih umum, saat Anda menggunakan dari tipe yang mendasari ke tipe enumerasi, tidak ada nilai dalam data[0]dapat menyebabkan UB untuk static_cast.

Setelah CWG 1766 (C ++ 17) Lihat cacat CWG 1766 . Paragraf [expr.static.cast] p10 telah diperkuat, jadi Anda sekarang dapat memanggil UB jika Anda memberikan nilai yang berada di luar rentang yang dapat direpresentasikan dari enum ke tipe enum. Ini masih tidak berlaku untuk skenario dalam pertanyaan, karena data[0]ini adalah tipe yang mendasari enumerasi (lihat di atas).

Harap dicatat bahwa CWG 1766 dianggap cacat dalam Standar, oleh karena itu diterima bagi pelaksana kompiler untuk menerapkan ke mode kompilasi C ++ 11 dan C ++ 14 mereka.

(*) charharus memiliki lebar minimal 8 bit, tetapi tidak harus unsigned. Nilai maksimum yang dapat disimpan harus paling tidak 127per Lampiran E dari Standar C99.


Bandingkan dengan [expr] / 4

Jika selama evaluasi ekspresi, hasilnya tidak didefinisikan secara matematis atau tidak dalam kisaran nilai yang dapat diwakili untuk jenisnya, perilaku tidak terdefinisi.

Sebelum CWG 1766, tipe integral konversi -> tipe enumerasi dapat menghasilkan nilai yang tidak ditentukan . Pertanyaannya adalah: Dapatkah nilai yang tidak ditentukan berada di luar nilai yang dapat diwakili untuk jenisnya? Saya percaya jawabannya tidak - jika jawabannya adalah ya , tidak akan ada perbedaan dalam jaminan yang Anda dapatkan untuk operasi pada jenis yang ditandatangani antara "operasi ini menghasilkan nilai yang tidak ditentukan" dan "operasi ini memiliki perilaku yang tidak ditentukan".

Karenanya, sebelum CWG 1766, bahkan tidakstatic_cast<Color>(10000) akan memanggil UB; tapi setelah CWG 1766, itu memang memanggil UB.


Sekarang, switchpernyataannya:

[stmt.switch] / 2

Syaratnya harus tipe integral, tipe enumerasi, atau tipe kelas. [...] Promosi integral dilakukan.

[conv.prom] / 4

Nilai awal dari tipe pencacahan yang tidak dicentang yang jenis dasarnya adalah tetap (7.2) dapat dikonversi ke nilai awal dari jenis yang mendasarinya. Selain itu, jika promosi integral dapat diterapkan pada tipe yang mendasarinya, nilai awal dari tipe enumerasi yang tidak dicopot yang tipe dasarnya diperbaiki juga dapat dikonversi ke nilai awal dari tipe dasar yang dipromosikan.

Catatan: Tipe dasar dari scoped enum w / o enum-base adalah int. Untuk enum yang tidak dicentang, tipe yang mendasarinya adalah implementasi yang ditentukan, tetapi tidak boleh lebih besar dari intjika intdapat berisi nilai-nilai semua enumerator.

Untuk enumerasi yang tidak dicentang , ini membawa kita ke / 1

Sebuah prvalue dari tipe integer selain bool, char16_t, char32_t, atau wchar_tyang bulat konversi rank (4.13) kurang dari pangkat intdapat dikonversi ke prvalue jenis intjika intdapat mewakili semua nilai-nilai dari jenis sumber; jika tidak, prvalue sumber dapat dikonversi ke prvalue tipe unsigned int.

Dalam kasus enumerasi yang tidak teropong , kita akan berurusan dengan ints di sini. Untuk pencacahan dengan cakupan ( enum classdan enum struct), tidak ada promosi integral yang berlaku. Dengan cara apa pun, promosi integral tidak mengarah ke UB juga, karena nilai yang disimpan berada dalam kisaran jenis yang mendasarinya dan dalam kisaran int.

[stmt.switch] / 5

Ketika switchpernyataan dieksekusi, kondisinya dievaluasi dan dibandingkan dengan setiap kasus konstan. Jika salah satu konstanta kasus sama dengan nilai kondisi, kontrol dilewatkan ke pernyataan berikut caselabel yang cocok . Jika tidak ada casekonstanta yang cocok dengan kondisinya, dan jika ada defaultlabel, kontrol beralih ke pernyataan yang berlabel defaultlabel.

The defaultlabel harus memukul.

Catatan: Seseorang dapat melihat lagi pada operator perbandingan, tetapi tidak secara eksplisit digunakan dalam "perbandingan" yang dimaksud. Bahkan, tidak ada petunjuk bahwa ini akan memperkenalkan UB untuk enum yang dicakup atau tidak dicopot dalam kasus kami.


Sebagai bonus, apakah standar membuat jaminan seperti ini tetapi dengan enum sederhana?

Apakah enumscoped atau tidak tidak ada bedanya di sini. Namun, itu membuat perbedaan apakah tipe yang mendasarinya tetap. Lengkap [decl.enum] / 7 adalah:

Untuk enumerasi yang tipe dasarnya adalah tetap, nilai-nilai enumerasi adalah nilai-nilai dari tipe yang mendasarinya. Jika tidak, untuk penghitungan di mana e min adalah yang terkecil pencacah dan e max adalah yang terbesar, nilai-nilai pencacahan adalah nilai-nilai di kisaran b min ke b max , didefinisikan sebagai berikut: Misalkan Kmenjadi 1untuk dua ini melengkapi representasi dan 0untuk representasi komplemen atau sign-magnitude seseorang. b max adalah nilai terkecil lebih besar dari atau sama dengan max (| e min | - K, | e max |) dan sama dengan 2M - 1 , di manaMbilangan bulat non-negatif. b min adalah nol jika e min adalah non-negatif dan - (b max + K) jika tidak.

Mari kita lihat enumerasi berikut:

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

Perhatikan bahwa kami tidak dapat mendefinisikan ini sebagai enum yang dicakup, karena semua enum yang dicakup telah memperbaiki tipe yang mendasarinya.

Untungnya, ColorUnfixedenumerator terkecil adalah red = 0x1, jadi max (| e min | - K, | e max |) sama dengan | e max | dalam hal apa pun, yaitu yellow = 0x2. Nilai terkecil lebih besar atau sama dengan 2, yang sama dengan 2 M - 1 untuk bilangan bulat positif Madalah 3( 2 2 - 1 ). (Saya pikir tujuannya adalah untuk memungkinkan kisaran sampai batas dalam 1-bit-langkah.) Oleh karena itu b max adalah 3dan bmin adalah 0.

Oleh karena itu, 100akan berada di luar kisaran ColorUnfixed, dan static_castakan menghasilkan nilai yang tidak ditentukan sebelum CWG 1766 dan perilaku yang tidak ditentukan setelah CWG 1766.

dyp
sumber
3
Tipe yang mendasarinya adalah tetap, sehingga kisaran nilai enumerasi (§7.2 [dcl.enum] p7) adalah "nilai-nilai dari tipe yang mendasarinya". 100 tentu saja merupakan nilai char, jadi "Nilai tersebut tidak berubah jika nilai aslinya berada dalam kisaran nilai enumerasi (7.2)." berlaku.
Casey
2
Saya harus mencari untuk mencari tahu apa arti "UB". ('perilaku tidak terdefinisi') Pertanyaan itu tidak menyebutkan kemungkinan perilaku tidak terdefinisi; jadi tidak terpikir olehku bahwa kamu mungkin berbicara tentang itu.
karadoc
2
@karadoc Saya telah menambahkan tautan saat pertama kali istilah tersebut muncul.
dyp
1
Sukai jawaban ini. Bagi mereka yang membaca sekilas terlalu cepat, perhatikan bahwa kalimat terakhir "Oleh karena itu, 100 akan berada di luar kisaran ..." hanya berlaku jika kode dimodifikasi untuk menghapus spesifikasi tipe yang mendasarinya (char dalam kasus ini). Saya pikir itulah yang dimaksud.
Eric Seppanen
1
@Ruslan CWG 1766 (atau resolusi daripadanya) bukan bagian dari C ++ 14, tapi saya pikir itu akan menjadi bagian dari C ++ 17. Bahkan dengan aturan C ++ 17, saya tidak begitu mengerti apa yang Anda maksud dengan "membatalkan jawaban Anda lebih lanjut". Bagian lain dari jawaban saya terutama berkaitan dengan bahwa "kisaran nilai enumerasi" adalah yang mengacu pada expr.static.cast p10.
dyp