Hi folks, this time it will be a kind of photo story! Something new this time, aaaaand I must type much less text!
It started a couple of day ago, when I saw following quiz on Twitter:
In C++20 given— Shafik Yaghmour (@shafikyaghmour) August 29, 2018
enum A { e1=1, e2 } ;
enum class B { e1=1, e2 } ;
Which of the following invokes undefined behavior
A.
A a = (A)(3);
B.
A a = (A)(4);
C.
B b = (B)(std::numeric_limits<std::underlying_type<B>::type >::max());#Cplusplus #Cpp20 #CppPolls #Programming
My answer was: all of them of course, in C or C++98 they would be probably all fine, but who knows what crazy UB stuff did they prepare for us in C++20. So when seen logically, all of that stuff should be undefined behaviour! But wait, dind't I used extensions to enum ranges in some project in some framework by casting integers to enums? That did work ok, didn't it? Ok, it was couple of years ago, C++03 I suppose, but AFAIK nothing has changed in C++11 in that respect, so maybe we did it wrong all the time back than? So how did it work?
So I wasn't so sure anymore, and would say A+B are UB, C is something that C++11 introduced, so no idea, but probably UB too. But wait, all of them are UB again? Probably, but the trouble with that was that there wasn't such an answer in the quiz! Crazy!
When I saw the answer I was even more perplexed:
What? Why 3 but not 4? And that numeric_limits<>::max() can't be right either! In a following tween the relevant spots in the standard were shown, and I read them, but was none the wiser - standardese isn't the lightest read on earth. ☹️|— Shafik Yaghmour (@shafikyaghmour) August 29, 2018
|
|
|
|
|
|
|
V
For an enum w/ a fixed underlying type all values of the underlying type are valid
For an enum w/o a fixed type, the range is limited and based on the values of enumerators, specified in [dcl.enum]p8
roughly limited to the bits needed to represent the values
But before I gave up I remembered that I've seen something similar on Twitter already, namely in (accidently also cited) tweet by @lefticus:
So had the same gut feeling as me, but he was corrected by some people well-versed in interpreting the standard (which I also happen to follow):I'm pretty sure I have this right, but this is Undefined Behavior, right?— Jason Turner (@lefticus) August 10, 2018
enum struct Values {
val1 = 1,
val2 = 2
};
Values val = static_cast<Values>(3);
and:No, it is not. Enums with a fixed underlying type (which is int by default for enum class/struct) can have all the values of the underlying type. For enums without a fixed underlying type it gets more complicated.https://t.co/pVZoTBpIt2— Miro Knejp (@mknejp) August 10, 2018
In this particular case there is no UB even if it was an oldschool enum because 3 can be represented by the two bits required to store the enumerators 1 and 2.— Miro Knejp (@mknejp) August 10, 2018
That was than even confirmed by @CppSage himself:
That's not UB. It's also perfectly fine for a regular enum. However, for a regular enum, casting the value 4 *would* be UB (even though 3 is okay). For an enum class/struct, casting the value 4 would *not* be UB.— Matt Calabrese (@CppSage) August 10, 2018
This all explains it pretty well. But isn't that crazy? Binding logical range of a type to its underlying representation? I'd understand that if we were in the 80-ties, but in C++20 standard with all that lofty newfangled features? OK, I suppose it was always like that in C, but I'm too lazy to check it, sorry.... 😩
BTW, now I've got an idea why this enum casting in a distant project might even have been working - we used Visual Studio compiler! And I dimly remember that there were some Microsoft extension for enums setting int as their underlying type. Was it so? For that I'd have to check my own blogpost from last year, but frankly, I cannot be bothered. 😩 That project has died already long ago.
TL;DR: Twitter is a good place to learn about C++, I learn almost every day!