8

C++11 introduced two different additions to how we can handle enums: an option to make them scoped, and an option to make them typed. So now we have four different enum subtypes:

enum Old {};
enum Typed : int8_t {};
enum class Scoped {};
enum class TypedScoped : int8_t {};

This question asks how to determine whether the enumeration is scoped. I want to know how to determine whether the enumeration is typed.


Additional information

I use the Qt framework, which provides the QDataStream class for serializing/deserializing data in a portable cross-platform way. Obviously, in order for the resulting data stream to be portable, you must store all integers in fixed-length form. That includes enums, too. Back in the day, I made a couple of helper macros to define serialization/deserialization of enums by casting them to an integer with fixed (user-specified) length:

#define SC_DECLARE_DATASTREAM_WRITE_OPERATOR(_TYPE) \
    QDataStream &operator<<(QDataStream &stream, _TYPE v);

#define SC_DECLARE_DATASTREAM_READ_OPERATOR(_TYPE) \
    QDataStream &operator>>(QDataStream &stream, _TYPE &v);

#define SC_DECLARE_DATASTREAM_OPERATORS(_TYPE) \
    SC_DECLARE_DATASTREAM_WRITE_OPERATOR(_TYPE) \
    SC_DECLARE_DATASTREAM_READ_OPERATOR(_TYPE)

#define SC_DEFINE_DATASTREAM_ENUM_WRITE_OPERATOR(_TYPE, _LEN) \
    QDataStream &operator<<(QDataStream &stream, _TYPE v) \
{ \
    qint ## _LEN t = v; \
    static_assert(sizeof(t) >= sizeof(v), "Increase length"); \
    stream << t; \
    return stream; \
    }

#define SC_DEFINE_DATASTREAM_ENUM_READ_OPERATOR(_TYPE, _LEN) \
    QDataStream &operator>>(QDataStream &stream, _TYPE &v) \
{ \
    qint ## _LEN t {0}; \
    static_assert(sizeof(t) >= sizeof(v), "Increase length"); \
    stream >> t; \
    if(stream.status() == QDataStream::Ok) \
    v = static_cast<_TYPE>(t); \
    return stream; \
    }

#define SC_DEFINE_DATASTREAM_ENUM_OPERATORS(_TYPE, _LEN) \
    SC_DEFINE_DATASTREAM_ENUM_WRITE_OPERATOR(_TYPE, _LEN) \
    SC_DEFINE_DATASTREAM_ENUM_READ_OPERATOR(_TYPE, _LEN)

Now that C++11 allows specifying the underlying enum type, I can simplify the above mentioned macros:

#define SC_DEFINE_DATASTREAM_TYPED_ENUM_WRITE_OPERATOR(_TYPE) \
    QDataStream &operator<<(QDataStream &stream, _TYPE v) \
{ \
    const std::underlying_type<_TYPE>::type t {static_cast<std::underlying_type<_TYPE>::type>(v)}; \
    stream << t; \
    return stream; \
    }

#define SC_DEFINE_DATASTREAM_TYPED_ENUM_READ_OPERATOR(_TYPE) \
    QDataStream &operator>>(QDataStream &stream, _TYPE &v) \
{ \
    std::underlying_type<_TYPE>::type t {0}; \
    stream >> t; \
    if(stream.status() == QDataStream::Ok) \
    v = static_cast<_TYPE>(t); \
    return stream; \
    }

However, if the user accidentally uses the new (*_TYPED_*) macros for enums that do not have their underlying type specified, that will break the guarantee of portability, because compiling the same code on different platform may yield different underlying type and hence different integer length in serialization/deserialization code. What I need is to add a static_assert to the code, which will break the compilation process if the enum was not strongly typed at the point of its declaration.

ScumCoder
  • 690
  • 1
  • 8
  • 20
  • What are you really trying to do? There are ways to get the underlying type regardless. – John Aug 17 '18 at 19:21
  • 3
    Would you consider `enum Old { VALUE = -1ULL; };` a typed enum? – NathanOliver Aug 17 '18 at 19:24
  • 1
    I'm not sure if you can check whether the `enum` has a specified type, but you can check what the underlying type is: https://en.cppreference.com/w/cpp/types/underlying_type – Ben Jones Aug 17 '18 at 19:24
  • I know about `std::underlying_type`. I need to know whether the result of this type trait template describes a user's choice or was chosen by the compiler. – ScumCoder Aug 17 '18 at 19:25
  • @NathanOliver: no. In the future, when 128-bit integers become standard default `int`, the compiler will be free to choose whether to make the underlying type of your enum 64-bit signed or 128-bit signed. – ScumCoder Aug 17 '18 at 19:28
  • According to the standard default underlying enum type is at least `int` i.e. it is an int in most cases. But no any guaranty that some `–short-enums` flag is used. So - probably there is no way for detect such things with type traits etc. – Victor Gubin Aug 17 '18 at 19:39
  • 1
    @VictorGubin Where do you get that? The `int` by default is only for scooped enums if the type is not specified. – NathanOliver Aug 17 '18 at 19:46
  • @NathanOliver [enum](https://en.cppreference.com/w/cpp/language/enum) enumeration type whose underlying type is not fixed (in this case, the underlying type is an implementation-defined integral type that can represent all enumerator values; this type is not larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0). – Victor Gubin Aug 17 '18 at 19:52
  • 1
    @VictorGubin So what you just quoted is opposite of what you said earlier. *the underlying type is an implementation-defined integral type that can represent all enumerator values; this **type is not larger than int** unless the value of an enumerator cannot fit in an int or unsigned int* means anything from `char` to `int` is acceptable depending on the values. `enum foo { bar };` is legally allowed to have an underlying type of a `char`. – NathanOliver Aug 17 '18 at 19:54
  • *"I need to know whether the result of this type trait template describes a user's choice or was chosen by the compiler."* - can you tell us why you need to know this, e.g. what decision you will make based on this information, and whether it is at build time or run time? – John Zwinck Aug 18 '18 at 00:39
  • @JohnZwinck I have edited the question. – ScumCoder Aug 21 '18 at 18:28

2 Answers2

1

std::underlying_type can be used to limit the compilation to a set of fixed width integer types (e.g. with std::is_same):

#include <type_traits>
#include <cstdint>

template <typename T>
    constexpr bool is_fixed =
        std::is_same<T, std::int8_t>::value ||
        std::is_same<T, std::int16_t>::value
        // etc..
    ;

enum class E1 : std::int8_t {};
    static_assert( is_fixed<std::underlying_type_t<E1>>, "fixed");

enum class E2 {};
    static_assert(!is_fixed<std::underlying_type_t<E2>>, "not fixed");

Variable templates are indeed since C++14, but in C++11 the same can be achieved with a constexpr function or struct/class:

template <typename T>
    constexpr bool is_fixed_f() {
        return  std::is_same<T, std::int8_t>::value ||
                std::is_same<T, std::int16_t>::value
                // etc..
        ;
    }

template <typename T>
    struct is_fixed_s {
        static constexpr bool value =
            std::is_same<T, std::int8_t>::value ||
            std::is_same<T, std::int16_t>::value
            // etc..
        ;
    };
oknenavin
  • 87
  • 5
  • Hi, it's nice you are contributing to SO, but for now this is more like comment, not an answer. OP already linked the answer you just posted. Also, I don't see two questions here, only the one asked in title: "Is it possible to determine if an enumeration was strongly typed?". Even though writing "you can use XXX to do YYY" is technically correct answer, it is good to provide some proof of concept code. – R2RT May 27 '19 at 13:53
0

In answer to the title question: no, it is not possible to know whether an enum has an explicit underlying type.

Even if there were, it would not solve your actual problem, which is more like "How to know whether an enum type has a fixed size?"

Imagine this simple case:

enum class Foo : long {};

On some systems this will be 32 bits, and on others it will be 64 bits. So even if some mechanism let you find out that it has an explicit type, it won't help you, because the size is not portable.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Well obviously the user is supposed to use fixed length integers when specifying the enum's underlying type. That's the whole point. Specifying non-fixed types like in your example is such a nonsensical choice that its possibility is not a problem, unlike the possibility of the user not specifying the type whatsoever. – ScumCoder Aug 22 '18 at 10:31