Why is compiler support needed for std::underlying_type?

Hi,

the libcxx source has a comment that compiler support is needed for
std::underlying_type.

Is there some reason that something like the following can't work?

#include <type_traits>

template <int> struct int_for_size;
template <> struct int_for_size<1> { typedef signed char Signed; typedef
unsigned char Unsigned; };
template <> struct int_for_size<2> { typedef short Signed; typedef
unsigned short Unsigned; };
template <> struct int_for_size<4> { typedef int Signed; typedef
unsigned int Unsigned; };
template <> struct int_for_size<8> { typedef long long Signed; typedef
long long Unsigned; };

template<typename T, bool>
struct ApplySign
{
   typedef typename int_for_size<sizeof(T)>::Signed Type;
};
template<typename T>
struct ApplySign<T, false>
{
   typedef typename int_for_size<sizeof(T)>::Unsigned Type;
};

template<typename T>
struct UnderLyingType
{
     typedef typename ApplySign<T, std::is_signed<T>::value>::Type Type;
};

enum Something {
   foo
};

int main()
{
   UnderLyingType<Something>::Type s = 9;
   return 0;
}

Thanks,

Steve.

Hi,

the libcxx source has a comment that compiler support is needed for
std::underlying_type.

Is there some reason that something like the following can't work?

#include <type_traits>

template <int> struct int_for_size;
template <> struct int_for_size<1> { typedef signed char Signed;
typedef
unsigned char Unsigned; };
template <> struct int_for_size<2> { typedef short Signed; typedef
unsigned short Unsigned; };
template <> struct int_for_size<4> { typedef int Signed; typedef
unsigned int Unsigned; };
template <> struct int_for_size<8> { typedef long long Signed; typedef
long long Unsigned; };

template<typename T, bool>
struct ApplySign
{
   typedef typename int_for_size<sizeof(T)>::Signed Type;
};
template<typename T>
struct ApplySign<T, false>
{
   typedef typename int_for_size<sizeof(T)>::Unsigned Type;
};

template<typename T>
struct UnderLyingType
{
     typedef typename ApplySign<T, std::is_signed<T>::value>::Type Type;
};

enum Something {
   foo
};

int main()
{
   UnderLyingType<Something>::Type s = 9;
   return 0;
}

enum E1 : long {};
static_assert(std::is_same<std::underlying_type<E>::type, long>::value,
""); // ok
static_assert(std::is_same<UnderLyingType<E>::Type, long>::value, ""); //
fails

enum E2 : char {};
static_assert(std::is_same<std::underlying_type<E>::type, char>::value,
""); // ok
static_assert(std::is_same<UnderLyingType<E>::Type, char>::value, ""); //
fails

Hi,

the libcxx source has a comment that compiler support is needed for
std::underlying_type.

Is there some reason that something like the following can't work?

#include <type_traits>

template <int> struct int_for_size;
template <> struct int_for_size<1> { typedef signed char Signed;
typedef
unsigned char Unsigned; };
template <> struct int_for_size<2> { typedef short Signed; typedef
unsigned short Unsigned; };
template <> struct int_for_size<4> { typedef int Signed; typedef
unsigned int Unsigned; };
template <> struct int_for_size<8> { typedef long long Signed; typedef
long long Unsigned; };

template<typename T, bool>
struct ApplySign
{
   typedef typename int_for_size<sizeof(T)>::Signed Type;
};
template<typename T>
struct ApplySign<T, false>
{
   typedef typename int_for_size<sizeof(T)>::Unsigned Type;
};

template<typename T>
struct UnderLyingType
{
     typedef typename ApplySign<T, std::is_signed<T>::value>::Type Type;
};

enum Something {
   foo
};

int main()
{
   UnderLyingType<Something>::Type s = 9;
   return 0;
}

enum E1 : long {};
static_assert(std::is_same<std::underlying_type<E1>::type, long>::value,
""); // ok
static_assert(std::is_same<UnderLyingType<E1>::Type, long>::value, ""); //
fails

enum E2 : char {};
static_assert(std::is_same<std::underlying_type<E2>::type, char>::value,
""); // ok
static_assert(std::is_same<UnderLyingType<E2>::Type, char>::value, ""); //
fails

*fixed*

Richard Smith wrote:

enum E1 : long {};
static_assert(std::is_same<std::underlying_type<E>::type, long>::value,
""); // ok
static_assert(std::is_same<UnderLyingType<E>::Type, long>::value, ""); //
fails

enum E2 : char {};
static_assert(std::is_same<std::underlying_type<E>::type, char>::value,
""); // ok
static_assert(std::is_same<UnderLyingType<E>::Type, char>::value, ""); //
fails

Am I right to say this? :

Even if sizeof(long)==sizeof(long long), they are not the same type. The
compiler intrinsic knows that, but my solution does not.

Similarly, signed char, char and unsigned char are three different distinct
types. The compiler knows this, but my solution does not.

Thanks,

Steve.

Right. This doesn't prove the non-existence of a non-intrinsic approach,
though. You can probably get a lot closer by triggering integral promotion
for a value of the enumeration type and then looking at the type of that
value -- that should work for any unscoped enumeration type (whether or not
the underlying type is fixed).

For a scoped enumeration type, I think it is not possible to determine the
underlying type without compiler support, because no core language feature
exposes the type.