4

I recently discovered that C++ Scoped enums are allowed to use bool as the underlying type, which somewhat makes sense since std::is_integral_v<bool> is true.

However, I've also found that the truncation behavior when static_casting to a type made this way causes different results across compilers. In particular:

enum class Option : bool {
    No = false,
    Yes = true,
};

constexpr auto v = static_cast<Option>(8); 

std::cout << static_cast<bool>(v) << std::endl;

Compiler Explorer

GCC reports this result as false, whereas Clang and MSVC report true (MSVC can be seen by checking the mov cl, 1 prior to calling).

This value reports as true for GCC if the last bit is set, so static_cast<Option>(9) will result in true as the underlying type:

constexpr auto v = static_cast<Option>(9); 

std::cout << static_cast<bool>(v) << std::endl;

Compiler Explorer

It seems that GCC treats the static_cast as a 1-bit unsigned integral value cast, and thus truncates the value to 1 bit, much like a uint32_t would narrow to a uint8_t, whereas Clang and MSVC both treat it like a normal boolean cast using n > 0.

Which compiler here is correct? If this is some form of undefined behavior, why can it be used in a constexpr expression?


Note: An obvious workaround is to use static_cast<Option>(static_cast<bool>(...)), but I'm curious about what behavior should be expected here -- if any. This behavior appears to be occur in all C++ versions that support scoped enums (from and up).

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
  • I'm not sure whether I should tag this as [tag:language-lawyer]... I kind of get the feeling this question might reach into that territory – Human-Compiler Jan 14 '22 at 13:47
  • If you do that (yes I think you perhaps shoule) I'll delete my answer ;-) – Bathsheba Jan 14 '22 at 13:48
  • And @StoryTeller - Unslander Monica is listening in too and may submit an answer. – Bathsheba Jan 14 '22 at 13:49
  • @Bathsheba actually looks like there is a reasonable duplicate for this already, so this question was closed. Seems that it's an unspecified value prior to CWG 1766, and undefined behavior after. Doesn't quite explain how this exists in constant expressions at all though. – Human-Compiler Jan 14 '22 at 13:50
  • @Human-Compiler Is *unspecified* behaviour the same as *undefined* behaviour? – Adrian Mole Jan 14 '22 at 13:51
  • That "duplicate" is about a different backing type. I'll reopen. – Bathsheba Jan 14 '22 at 13:52
  • @Bathsheba That's your prerogative, of course, but I'm pretty sure the dupe I linked answers the question. – Adrian Mole Jan 14 '22 at 13:53
  • @AdrianMole: Linking to the C++ standard answers all questions too. Sometimes it's nice to have something a little more tailored. – Bathsheba Jan 14 '22 at 13:54
  • 1
    Seems like it needs to follow the same path as a conversion to bool https://timsong-cpp.github.io/cppwp/n4868/expr.static.cast#10.sentence-2 – StoryTeller - Unslander Monica Jan 14 '22 at 13:54
  • 1
    Some interesting stuff here too: https://stackoverflow.com/questions/21319413/why-do-constant-expressions-have-an-exclusion-for-undefined-behavior – Bathsheba Jan 14 '22 at 14:04
  • @StoryTeller-UnslanderMonica I'm confused how to apply this information. I agree that it appears like the cast to boolean should occur, but the previously-linked duplicate to this question seemed to suggest that the cast being out-of-range would be UB in the first-place. – Human-Compiler Jan 14 '22 at 14:06
  • @Bathsheba That might explain why it's accepted in `constexpr` when it shouldn't be. Very strange that it wouldn't catch this as being UB at compile-time though, when it would catch most other conversions – Human-Compiler Jan 14 '22 at 14:06
  • 1
    I think it's just a case of compiler versions. C++20 defines the code, and the fix is a DR, so it's retroactively well-defined too. Newer compiler versions should do as G++ and MSVC. Olders versions might not. – StoryTeller - Unslander Monica Jan 14 '22 at 14:06
  • @StoryTeller-UnslanderMonica Do you mean Clang and MSVC (since they performed the conversion first)? If so, then it sounds like there may be a bug in GCC then, since compiling with `-std=c++20` still produces `false` rather than `true`. If you have a moment to type this up as an answer, I'll accept it – Human-Compiler Jan 14 '22 at 14:09
  • Yes, Clang and MSVC. I mistyped. I stand behind the dupe I chose though. It goes through the history of this from C++11 to C++20. Along with the biggest motivation (`std::byte` being well-defined). – StoryTeller - Unslander Monica Jan 14 '22 at 14:11
  • @StoryTeller-UnslanderMonica I have no hard-feelings to this being re-closed as a dupe (I never did); though the answer to that question seemed to more indicate that it was UB rather than well-defined, as you're suggesting. Also I thought the motivation for `std::byte` being well-defined only applied to enums that had no enumerators explicitly defined (though my information might be outdated). **Edit:** Oh, I didn't realize the question was closed 10 minutes ago with a different dupe answer. Ignore me. Thanks! – Human-Compiler Jan 14 '22 at 14:12
  • Enumerators are just names for specific values of the underlying type (expressed as the enumeration type). I don't think defining the behavior really depends on the existence of enumerators. – StoryTeller - Unslander Monica Jan 14 '22 at 14:17
  • Interesting note on [cppreference](https://en.cppreference.com/w/cpp/language/enum): *Although the conversion from an out-of-range to an enumeration without fixed underlying type is made undefined behavior by CWG issue 1766, currently no compiler performs the required diagnostic for it in constant evaluation.* – Adrian Mole Jan 14 '22 at 14:31
  • I've edited the target list to include the original target that was used to close this question, as I think someone who has this question will find both those targets useful. – cigien Jan 14 '22 at 14:53

0 Answers0