Sınıfı enum için static_cast geçersiz değeri yaparsanız ne olur?


146

Bu C ++ 11 kodunu düşünün:

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

Diyelim ki veriler [0] aslında 100'dür. Standarda göre renk nedir? Özellikle, daha sonra yaparsam

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

standart, varsayılanın vurulacağını garanti ediyor mu? Değilse, burada bir hatayı kontrol etmenin uygun, en verimli, en zarif yolu nedir?

DÜZENLE:

Bir bonus olarak, standart bununla ilgili olarak düz numaralandırma ile herhangi bir garanti veriyor mu?

Yanıtlar:


131

Standarda göre renk nedir?

C ++ 11 ve C ++ 14 Standartlarından alıntı ile cevaplama:

[Expr.static.cast] / 10

Bir integral veya numaralandırma türünün değeri açıkça bir numaralandırma türüne dönüştürülebilir. Orijinal değer numaralandırma değerleri (7.2) aralığındaysa değer değişmez. Aksi takdirde, ortaya çıkan değer belirtilmez (ve bu aralıkta olmayabilir).

Numaralandırma değerlerinin aralığına bakalım : [dcl.enum] / 7

Temel türü sabit olan bir numaralandırma için, numaralandırmanın değerleri temeldeki türün değerleridir.

CWG 1766'dan (C ++ 11, C ++ 14) önce Bu nedenle, data[0] == 100sonuçta elde edilen değer belirtilir (*) ve Tanımsız Davranış (UB) dahil edilmez . Daha genel olarak, altta yatan türden numaralandırma türüne döküm yaptığınızda, için hiçbir değer data[0]UB'ye neden olamaz static_cast.

CWG 1766'dan sonra (C ++ 17) Bkz. CWG hatası 1766 . Eğer şimdi çok [expr.static.cast] p10 paragraf, güçlendirilmiştir olabilir dışarıda enum türüne bir enum gösterilebilen aralığıdır bir değer dağıtmak eğer UB çağırmak. Bu data[0], sayımın altında yatan türden olduğu için , söz konusu senaryo için hala geçerli değildir (yukarıya bakın).

CWG 1766'nın Standartta bir kusur olarak kabul edildiğini, bu nedenle derleyici uygulayıcılarının C ++ 11 ve C ++ 14 derleme modlarına başvurmaları kabul edildiğini lütfen unutmayın.

(*) charen az 8 bit genişlikte olması gerekir, ancak olması gerekmez unsigned. Saklanabilir maksimum değerin en azından 127C99 Standardının Ek E'sine uygun olması gerekir .


[İfade] / 4 ile karşılaştır

Bir ifadenin değerlendirilmesi sırasında sonuç, matematiksel olarak tanımlanmazsa veya türü için temsil edilebilir değerler aralığında değilse, davranış tanımsızdır.

CWG 1766'dan önce, dönüşüm integrali türü -> numaralandırma türü belirtilmemiş bir değer üretebilir . Soru şudur: Belirtilmemiş bir değer, türü için temsil edilebilir değerlerin dışında olabilir mi? Cevabın hayır olduğuna inanıyorum - cevap evet olsaydı, "bu işlem belirtilmemiş bir değer üretir" ile "bu işlem tanımlanmamış davranışa sahiptir" arasında imzalanmış türler için aldığınız garantilerde herhangi bir fark olmazdı.

Bu nedenle, cwg 1766 kadar önceden bile static_cast<Color>(10000)olur olup UB çağırmak; ama CWG 1766 sonrasında, bu does UB çağırmak.


Şimdi switchifade:

[Stmt.switch] / 2

Koşul, integral tipte, numaralandırma tipinde veya sınıf tipinde olmalıdır. [...] İntegral tanıtımlar yapılır.

[Conv.prom] / 4

Altta yatan türü sabit olan (7,2) kapatılmamış numaralandırma türünün bir değeri , altta yatan türün bir değerine dönüştürülebilir. Üstelik, altta yatan tipine integral yükselme uygulanabiliyorsa, altta yatan tipi sabit olan, örtüsüz bir numaralandırma tipinin bir değeri, yükseltilmiş alttaki tipin bir değerine dönüştürülebilir.

Not: Enum-baz içermeyen kapsamlandırılmış bir enum tipi int. Unscoped çeteleler için temel tipi uygulama tanımlı, fakat daha büyük olmayacaktır inteğer inttüm enumerator'ın değer içerebilir.

Bir İçin unscoped numaralandırma , bu potansiyel müşteriler bize / 1'e

Başka bir tamsayıdır tip bir prvalue bool, char16_t, char32_tya da wchar_tolan bir tamsayı dönüşüm sıralaması (4.13) daha az bir sıralaması daha inttipte bir prvalue dönüştürülebilir intise int, kaynak türü bütün değerlerini temsil edebilir; Aksi takdirde, kaynak değer bir tür değere dönüştürülebilir unsigned int.

İşlenmemiş bir numaralandırma söz konusu olduğunda, intburada s ile ilgileniriz . For kapsamına sahip numaralandırma ( enum classve enum struct), hiçbir ayrılmaz promosyon uygular. Saklı değer, altta yatan türün aralığında ve aralığında olduğu için, hiçbir şekilde, tümleşik tanıtım da UB'ye yol açmaz int.

[Stmt.switch] / 5

İfade switchyürütüldüğünde durumu değerlendirilir ve her durum sabiti ile karşılaştırılır. Vaka sabitlerinden biri koşulun değerine eşitse, kontrol eşleşen caseetiketi izleyen ifadeye iletilir . Hayır ise casesabit durumu ile eşleşir ve bir varsa defaultetiket, kontrol tarafından etiketli açıklamaya geçer defaultetiket.

defaultEtiket isabet edilmelidir.

Not: Karşılaştırma operatörüne bir kez daha bakılabilir, ancak atıfta bulunulan "karşılaştırma" da açıkça kullanılmaz. Aslında, bizim durumumuzda kapsamlandırılmış ya da örtülmemiş numaralandırmalar için UB'yi getireceğine dair bir ipucu yoktur.


Bir bonus olarak, standart bununla ilgili olarak düz numaralandırma ile herhangi bir garanti veriyor mu?

Olsun ya da olmasın enumburada herhangi bir fark yaratmıyor kapsamı. Ancak, altta yatan türün sabit olup olmadığı bir fark yaratır. [Decl.enum] / 7'nin tamamı:

Temel türü sabit olan bir numaralandırma için, numaralandırmanın değerleri temeldeki türün değerleridir. Aksi durumda, bir sayım için , e az küçük Numaralayıcı ve bir E maksimum büyük olan, sayım değerleri değerleri aralığı içindedir b dakika için b maksimum şu şekilde tanımlanmıştır: Let Kolduğu 1bir ikinin tümleyici gösteriminde ve 0A kişinin tamamlayıcı veya işaret büyüklüğünde gösterimi. b max , max (| e min | - K, | e max |) değerinden büyük veya ona eşit en küçük değerdir ve 2'ye eşittirM - 1 , buradaMnegatif olmayan bir tamsayıdır. e min negatifdeğilse b min sıfır,aksi takdirde - (b max + ) .K

Aşağıdaki numaralandırmaya bir göz atalım:

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

Tüm kapsamlandırılmış numaralandırmaların altta yatan sabit türleri olduğu için bunu kapsamlandırılmış numaralandırma olarak tanımlayamayacağımızı unutmayın.

Neyse ki, ColorUnfixeden küçük listeleyicisi olduğunu red = 0x1, bu yüzden max (| E dk | - K, | e maks |) eşittir etmek | e max | her durumda yellow = 0x2. En küçük değeri büyük veya ona eşit 2eşittir, 2 M - 1 bir pozitif tamsayı Molduğu 3( 2 2 - 1 ). (I niyet 1 bit adımda ölçüde sağlanmasına olanak veren bir olduğunu düşünüyorum.) O, aşağıdaki B max olduğu 3ve Bmin olup 0.

Bu nedenle, 100bir aralığının dışında olabilir ColorUnfixedve static_castDDI 1766 sonra cwg 1766 önce tanımlanmamış değeri ve tanımlanmamış davranışı üretecektir.


3
Temel tür sabittir, bu nedenle numaralandırma değerlerinin aralığı (§7.2 [dcl.enum] p7) "temel türün değerleri" dir. 100 kesinlikle bir değerdir char, bu nedenle "Orijinal değer numaralandırma değerleri (7.2) aralığındaysa değer değişmez." geçerlidir.
Casey

2
"UB" nin ne anlama geldiğini araştırmak zorunda kaldım. ('tanımsız davranış') Soru, tanımsız davranış olasılığından bahsetmedi; bu yüzden bana bundan bahsediyor olmadın.
karadoc

2
@karadoc Dönemin ilk tekrarında bir bağlantı ekledim.
dyp

1
Bu yanıtı seviyorum. Çok çabuk gözden geçirenler için, "Bu nedenle, 100 aralığın dışında olur ..." son cümlesinin yalnızca kod, altta yatan tür belirtimini kaldırmak için değiştirildiyse geçerlidir (bu durumda karakter). Bence bu zaten kastetti.
Eric Seppanen

1
@Ruslan CWG 1766 (veya çözünürlüğü) C ++ 14'ün bir parçası değildir , ancak C ++ 17'nin bir parçası olacağını düşünüyorum. C ++ 17 kuralları ile bile, "cevabınızın daha fazla metnini geçersiz kıl" ile ne demek istediğinizi tam olarak anlamıyorum. Cevabımın diğer kısımları temel olarak "numaralandırma değerleri aralığının" expr.static.cast p10'un bahsettiği ile ilgilidir.
dyp
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.