Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to determine if an enumeration was strongly typed?

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.

like image 368
ScumCoder Avatar asked Aug 17 '18 19:08

ScumCoder


People also ask

Are enums strongly typed?

C++11 has introduced enum classes (also called scoped enumerations), that makes enumerations both strongly typed and strongly scoped.

How are enumeration variables declared explain?

An enumeration consists of a set of named integer constants. An enumeration type declaration gives the name of the (optional) enumeration tag. And, it defines the set of named integer identifiers (called the enumeration set, enumerator constants, enumerators, or members).

Can different enumeration may have same name?

Multiple enum names or elements can have the same value.

Are enumeration values coerced to integer?

I recently explained that although both C and C++ provide void * as the generic data pointer type, each language treats the type a little differently. For any object type T , both C and C++ let you implicitly convert an object of type T * to void * .


1 Answers

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..
        ;
    };
like image 190
oknenavin Avatar answered Sep 29 '22 01:09

oknenavin