Why in C++17 cannot scoped enums be converted to integers without narrowing by using list initialization
Asked Answered
A

0

6

C++17 allows scoped enums to be initialized with integers as long as it's not narrowing, e.g.

#include <cstdint>
enum class e16 : uint16_t { x16, y16 };
enum class e32 : uint32_t { x32, y32 };

int main() {
  e32{ uint16_t{} };       // okay
  e32{ uint32_t{} };       // okay
  e16{ uint16_t{} };       // okay
  e16{ uint32_t{} };       // not allowed -- narrowing
}

Why not the reverse? I know scoped enums are not implicitly convertible to integer, but a integer{ scoped_enum } initialization is explicit, and C++ allows scoped_enum{ integer } if it's non-narrowing.

#include <cstdint>
enum class e16 : uint16_t { x16, y16 };
enum class e32 : uint32_t { x32, y32 };

int main() {
  uint16_t{ e16{} };       // should be okay
  uint16_t{ e32{} };       // narrowing
  uint32_t{ e16{} };       // should be okay
  uint32_t{ e32{} };       // should be okay
}

I normally have to use static_cast to convert a scoped enum to an integer, which allows unsafe narrowing conversions.

To avoid narrowing conversions, I created a template cast function which uses SFINAE:

#include <cstdint>
#include <type_traits>

template<typename INT, typename ENUM, typename = decltype(INT{std::declval<std::underlying_type_t<ENUM>>()})>
inline constexpr INT enum_int_cast(ENUM e) { return static_cast<INT>( e ); }

enum class enum8  : uint8_t  {};
enum class enum16 : uint16_t {};
enum class enum32 : uint32_t {};
enum class enum64 : uint64_t {};

int main() {
   enum_int_cast<uint8_t> ( enum8 {} );    // okay
   enum_int_cast<uint8_t> ( enum16{} );    // not allowed -- narrowing
   enum_int_cast<uint8_t> ( enum32{} );    // not allowed -- narrowing
   enum_int_cast<uint8_t> ( enum64{} );    // not allowed -- narrowing

   enum_int_cast<uint16_t>( enum8 {} );    // okay
   enum_int_cast<uint16_t>( enum16{} );    // okay
   enum_int_cast<uint16_t>( enum32{} );    // not allowed -- narrowing
   enum_int_cast<uint16_t>( enum64{} );    // not allowed -- narrowing

   enum_int_cast<uint32_t>( enum8 {} );    // okay
   enum_int_cast<uint32_t>( enum16{} );    // okay
   enum_int_cast<uint32_t>( enum32{} );    // okay
   enum_int_cast<uint32_t>( enum64{} );    // not allowed -- narrowing

   enum_int_cast<uint64_t>( enum8 {} );    // okay
   enum_int_cast<uint64_t>( enum16{} );    // okay
   enum_int_cast<uint64_t>( enum32{} );    // okay
   enum_int_cast<uint64_t>( enum64{} );    // okay
}

What is the rationale for C++17 allowing scoped_enum{ integer } non-narrowing conversions, but not allowing integer{ scoped_enum } non-narrowing conversions?

I know that brace initialization does not call explicit constructors, so using braces may appear to imply that implicit conversion cannot occur, but that would seem to rule out scoped_enum{ integer } as well.

Andria answered 23/10 at 23:35 Comment(7)
Is this something for language-lawyer?Evelinaeveline
your snippet does not allow to check your assertion as it is not strictly narrowing (values are known at compile time as not overflowing). Here is a snippet that triggers narrowing: godbolt.org/z/Tdb78qnTsMamelon
see timsong-cpp.github.io/cppwp/n4659/dcl.init.list#7 there is an exception for constant expression: scoped_enum{ integer } does not compile in my snippetMamelon
uint32_t is larger than std::underlying_type_t<e16>. Narrowing is the result an implicit conversion to uint16_t. Other cases contain implicit conversions too, but none trys to create a small integer type.Tamera
There is no implicit promotion or conversion of a scoped enum to its underlying type. Those not okay conversions commented with "should be okay" are not okay, but if the language was different (call it !C++) then, sure, it could do that.Counteraccusation
@Oersted: I used a version of GCC which produced an error in that snippet even though the values are constants which can be non-narrowly converted.Andria
could you provide me with the version and the compilation options? Or better, reproduce it on compiler explorer?Mamelon

© 2022 - 2024 — McMap. All rights reserved.