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.
scoped_enum{ integer }
does not compile in my snippet – Mamelonuint32_t
is larger thanstd::underlying_type_t<e16>
. Narrowing is the result an implicit conversion touint16_t
. Other cases contain implicit conversions too, but none trys to create a small integer type. – Tamera!C++
) then, sure, it could do that. – Counteraccusation