https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105255

            Bug ID: 105255
           Summary: Narrowing conversion from enumerator to integer not
                    detected
           Product: gcc
           Version: unknown
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: cerrigno at gmail dot com
  Target Milestone: ---

This code should not be valid (at least since C++14), because the
direct-list-initialization is a narrowing conversion, being enumerator
underlying type either int or unsigned int.

using signed_t = signed char;
using unsigned_t = unsigned char;

enum my_enum {};
enum my_enum_auto_signed { _vsm = -1 };

void foo(my_enum s) {
    signed_t{s}; // narrowing
}

void foo(my_enum_auto_signed s) {
    signed_t{s}; // narrowing
}


Anyway, it compiles on GCC, as of GCC 11, but fails on clang 14 and MSVC 19.31.
On the other hand,


void foo(my_enum_auto_signed s) {
    signed_t{my_enum{}}; // not narrowing, constant expression known to be zero
    signed_t{my_enum_auto_signed{}}; // not narrowing, constant expression
known to be zero
}


compiles fine on every compiler, and it is correct because source is a constant
expression whose value can be stored exactly in the target type.

Here I report also a more exaustive test with any enumeration type conversion
to integer (also at https://godbolt.org/z/jYK7Gzn7s):


#include <type_traits>
#include <limits>

// void_t for C++14
template <typename... Ts> struct make_void { using type = void; };
template <typename... Ts> using void_t = typename make_void<Ts...>::type;

template <typename TOut, typename TIn, typename = void>
struct is_narrowing : std::true_type {};

template <typename TOut, typename TIn>
struct is_narrowing < TOut, TIn, void_t<decltype(TOut{ std::declval<TIn>() })
>> : std::false_type {};

using signed_t = signed char;
using unsigned_t = unsigned char;

enum my_enum_opaque_signed : signed_t;
enum my_enum_opaque_unsigned : unsigned_t;
enum my_enum {};
enum my_enum_auto_signed { _vsm = -1 };
enum my_enum_auto_unsigned { _vum = std::numeric_limits<unsigned int>::max() };
enum my_enum_signed : signed_t {};
enum my_enum_unsigned : unsigned_t {};
enum struct my_enum_opaque_scoped;
enum struct my_enum_opaque_scoped_signed : signed_t;
enum struct my_enum_opaque_scoped_unsigned : unsigned_t;
enum struct my_enum_scoped {};
enum struct my_enum_scoped_signed : signed_t {};
enum struct my_enum_scoped_unsigned : unsigned_t {};

// integer
static_assert(!is_narrowing<signed_t, signed_t>::value, "");

// unscoped
static_assert(!is_narrowing<signed_t, my_enum_opaque_signed>::value, "");
static_assert( is_narrowing<signed_t, my_enum_opaque_unsigned>::value, "");
static_assert( is_narrowing<signed_t, my_enum>::value, ""); // GCC fails
static_assert( is_narrowing<signed_t, my_enum_auto_signed>::value, ""); // GCC
fails
static_assert( is_narrowing<signed_t, my_enum_auto_unsigned>::value, "");
static_assert(!is_narrowing<signed_t, my_enum_signed>::value, "");
static_assert( is_narrowing<signed_t, my_enum_unsigned>::value, "");

// scoped
static_assert( is_narrowing<signed_t, my_enum_opaque_scoped>::value, "");
static_assert( is_narrowing<signed_t, my_enum_opaque_scoped_signed>::value,
""); // MSVC fails (fixed on MSVC2022)
static_assert( is_narrowing<signed_t, my_enum_opaque_scoped_unsigned>::value,
"");
static_assert( is_narrowing<signed_t, my_enum_scoped>::value, "");
static_assert( is_narrowing<signed_t, my_enum_scoped_signed>::value, ""); //
MSVC fails (fixed on MSVC2022)
static_assert( is_narrowing<signed_t, my_enum_scoped_unsigned>::value, "");

Reply via email to