# How to get the integer type of a C++ enum - CodeProject

: 9

## Introduction

I have used enumerators many times in C++ but sadly, these enums are not supported well. When you want to serialize an enum, you don't know what is the smallest integer type which can safely store the values of this enum. Of course, you can choose the biggest integer but it is not efficient, and in this case you don't handle signed/unsigned integers. I wrote a small class which can determine the right size of the enum at compile time. To do this, there are some restrictions:

- The smallest value in the enum must be
`_MIN_VALUE`

- The highest value in the enum must be
`_MAX_VALUE`

- The enum must be nested into a struct or class (see the example)

If you meet these requirements, the smallest size can be determined correctly. Otherwise you will get a compile error.

I implemented the solution with the new C++14 standard. If you don't use the new C++ standard (C++0x/C++11 or above), then you can use the boost or the Loki library implementation which is compatible with the C++03 standard. You can choose which do you prefer. The implementation logics are very similar.

For more information about these libraries, visit the boost mpl or the Loki sites.

## Using the code

It is very easy to use the class. Let's see!

```
struct ResultLevel
{
enum Value
{
_MIN_VALUE = -4,
FATAL_ERROR,
CRITICAL_ERROR,
ERROR,
OK,
WARNING,
_MAX_VALUE
};
};
// ...
Integral<ResultLevel>::Type level = 0;
// the Type will be signed char (int8)
// _MIN_VALUE = -4 and _MAX_VALUE = 2
// ...
struct ASCII
{
enum Value
{
_MIN_VALUE,
MIN_CONTROL = _MIN_VALUE,
MAX_CONTROL = 31,
MIN_PRINTABLE = MAX_CONTROL + 1,
MAX_PRINTABLE = 127,
MIN_EXTENDED = MAX_PRINTABLE + 1,
MAX_EXTENDED = 255,
_MAX_VALUE = MAX_EXTENDED
};
};
// ...
Integral<ASCII>::Type ascii = 0;
// the Type will be unsigned char (uint8)
// _MIN_VALUE = 0 and _MAX_VALUE = 255
```

In the above examples, the `Type`

of the `Integral`

will be the expected type. As you can see, it handles signed and unsigned integers, too, and of course, you can use any signed and unsigned integer (`char`

, `short`

, `int`

, `long`

, `long`

`long`

).

As you can see, there are some restrictions to use this class. First, you have to use `_MIN_VALUE`

and `_MAX_VALUE`

in your enums. `_MIN_VALUE`

must be the smallest value (smaller than your first enum) and `_MAX_VALUE`

must be the greatest enum value. It is necessary, because these two enums will help to determine the right integer type.

Each implementations (C++14, boost and Loki version) are in different namespaces:

`Base::EnumeratorHelper::Loki::Integral<>`

`Base::EnumeratorHelper::Boost::Integral<>`

`Base::EnumeratorHelper::Std::Integral<>`

Or, if you can define one of the following macro and you can simply access the class via Base::EnumeratorHelper::Integral<>:

`INTEGRAL_LOKI`

`INTEGRAL_BOOST`

`INTEGRAL_STD`

Now let's see the implementation!

## The implementation

#### Integral

The main class which is called every time is the `Integral`

class.

```
template <typename _TEnumerator, typename..._TTypes>
struct Integral
{
protected:
typedef typename TypeList<_TTypes...>::Iterator Types;
public:
typedef typename FindIntegral<_TEnumerator, IntegralResult::cGetNextType, void, Types>::Type Type;
};
template <typename _TEnumerator>
struct Integral<_TEnumerator>
{
public:
typedef typename Integral<_TEnumerator, uint8, int8, uint16, int16, uint32, int32, uint64, int64, uintmax, intmax>::Type Type;
};
```

The C++14 version is the simpliest, because with the power of the new standard (from C++11) we can use variadic template parameters.

In the boost and Loki implementation we using variadic sequence (boost::mpl::vector or Loki::TypeList) which intended to use as a variadic template parameter. Here is the boost version:

```
template <typename _TEnumerator, typename _TTypes = void> struct Integral;
template <typename _TEnumerator>
struct Integral<_TEnumerator, void>
{
protected:
typedef ::boost::mpl::vector<uint8, int8, uint16, int16, uint32, int32, uint64, int64, uintmax, intmax>::type Result;
public:
typedef typename Integral<_TEnumerator, Result>::Type Type;
};
template <typename _TEnumerator, typename _TTypes>
struct Integral
{
protected:
typedef typename ::boost::mpl::deref< typename ::boost::mpl::begin< _TTypes >::type >::type TFirstType;
static const bool isTypeListEmpty = !!::boost::is_same< typename ::boost::mpl::end< _TTypes >::type, typename ::boost::mpl::begin< _TTypes >::type >::value;
static const int cSelectResult = isTypeListEmpty ? IntegralResult::cTypeListEmpty : IntegralResult::cGetNextType;
public:
typedef typename FindIntegral<_TEnumerator, _TTypes, TFirstType, cSelectResult>::Type Type;
};
```

The boost mpl allows to refer to the first element in the type list event if the list is empty. In this case, it will be a `void`

type. The Loki library will cause a compile error so the Loki's implementation using `void`

type as `_TFirstType`

in the implementation.

The `Integral<>::Type`

contains the integer types in ascending order which will be checked when we are looking for the right type. It is important for the list to be in ascending order because the search is linear and will stop at the first match. `intmax`

and `uintmax`

are typedefs to `intmax_t`

and `uintmax_t`

which are part of the C99 ANSI standard. These types are the largest storable integers by the processor on the current system. For more information, see the section 7.18.1.5 in the standard or on Wiki. `Integral<>::Type`

is a typedef and it uses the `FindIntegral`

template which does the whole job. The `cSelectResult`

determines which template will be specialized. In this case, if the type list is empty, we will get a compile error.

##### Error handling

The error handling in template meta-programming is very important, because the compile time template errors can be very confusing and it will be hard to decipher the root of the problem. If we handle every error, we get the following if the type list is empty in the Loki version:

```
error: variable
'Base::EnumeratorHelper::Loki::CompileTimeError::
```**Integral_Type_List_Is_Empty** i8'
has initializer but incomplete type

It is more readable than this:

```
findintegral.h:71: error: invalid use of incomplete
type 'struct Loki::TL::TypeAt<Loki::NullType, 0u>'
loki/typelist.h:121: error: declaration of 'struct Loki::TL::TypeAt<Loki::NullType, 0u>'
```

#### FindIntegral

`FindIntegral`

does the whole job. The input parameters are the following:

`_TEnumerator`

is the enumerator type (this is the`ResultLevel`

enum in our example above)`_TTypes`

contains the possible types for the enum which is a variadic template in C++14. If you are using boost, then it is a`boost::mpl::vector`

filled with integers, or a Loki TypeList in Loki library.- The
`_TFirstType`

integer type will be checked in the class. If it meets the expectations, the specialized template will be instantiated. `Result`

controls the instantiation of the template. The value can be one in the`IntegralResult`

namespace. It represents a simple`switch case`

statement at compile time. The selection depends on the value of the`Result`

.

The new C++ standard supports variadic templates but there is no type list implementation as you see in the boost or loki library, so I implement a little helper which does this job.

```
namespace TypeListHelper
{
template <typename..._TTypes> struct Iterator;
template <typename _TFirst, typename..._TTypes>
struct Iterator<_TFirst, _TTypes...>
{
typedef _TFirst Type;
typedef Iterator<_TTypes...> Next;
static const bool cHasNext = true;
};
template <typename _TLast>
struct Iterator<_TLast>
{
typedef _TLast Type;
typedef void Next;
static const bool cHasNext = false;
};
}
template <typename..._TTypes>
struct TypeList
{
typedef TypeListHelper::Iterator<_TTypes...> Iterator;
static const size_t cSize = sizeof...(_TTypes);
};
```

The specialization of the `FindIntegral`

template depends on the value of the `Result`

. Here are the possible specializations:

`cTypeListEmpty`

: This template specialization will be instantiated when there is no type in the list. This is an error case and will generate a compile time error.`cTypeNotFound`

: This is an error case too and will be instantiated when there is no matching integral type in the list.`cTypeFound`

: We found the right integral type.`cGetNextType`

: Search is in progress, "calling" while there is type in the list or type isn't found.- neither one: The value of the
`Result`

is invalid and generates a compile time error:`Result_Parameter_Is_Invalid`

. This is an internal error.

The implementation of the `FindIntegral`

are very similar in each version, so I show only one version, but you can see the others in the source code if you download it. This is the C++14 version:

```
namespace IntegralResult
{
static const int cTypeListEmpty = -2;
static const int cTypeNotFound = -1;
static const int cGetNextType = 0;
static const int cTypeFound = 1;
}
namespace CompileTimeError
{
namespace InternalError
{
class Result_Parameter_Is_Invalid;
}
class Integral_Type_Not_Found;
}
/**
* Invalid input. Result is invalid with types.
**/
template <typename _TEnumerator, int Result, typename _TFirstType, typename _TTypes>
struct FindIntegral
{
typedef CompileTimeError::InternalError::Result_Parameter_Is_Invalid Type;
};
/**
* The right integral type doesn't exist in the type list.
**/
template <typename _TEnumerator, typename _TFirstType, typename _TTypes>
struct FindIntegral<_TEnumerator, IntegralResult::cTypeNotFound, _TFirstType, _TTypes>
{
typedef CompileTimeError::Integral_Type_Not_Found Type;
};
/**
* The right integral type has been found.
**/
template <typename _TEnumerator, typename _TFirstType, typename _TTypes>
struct FindIntegral<_TEnumerator, IntegralResult::cTypeFound, _TFirstType, _TTypes>
{
typedef _TFirstType Type;
};
template <typename _TEnumerator, typename _TFirstType, typename _TTypes >
struct FindIntegral<_TEnumerator, IntegralResult::cGetNextType, _TFirstType, _TTypes>
{
typedef typename _TTypes::Type TFirstType;
static const TFirstType cMinValue = ::Base::Type<TFirstType>::cMinValue;
static const TFirstType cMaxValue = ::Base::Type<TFirstType>::cMaxValue;
template <bool /* isTrue */, typename _TNull = void> struct IsInRange;
template <typename _TNull>
struct IsInRange<true, _TNull>
{
enum {
value = (_TEnumerator::_MIN_VALUE >= static_cast<intmax>(cMinValue) &&
_TEnumerator::_MAX_VALUE <= static_cast<intmax>(cMaxValue))
};
};
template <typename _TNull>
struct IsInRange<false, _TNull>
{
enum {
value = (_TEnumerator::_MIN_VALUE >= static_cast<uintmax>(cMinValue) &&
_TEnumerator::_MAX_VALUE <= static_cast<uintmax>(cMaxValue))
};
};
static const bool isSigned = _TEnumerator::_MIN_VALUE < 0;
static const bool isTypeFound = !!IsInRange<isSigned>::value;
static const bool isEndOfList = (!isTypeFound) && (!_TTypes::cHasNext);
static const int cSelectResult =
isEndOfList ?
IntegralResult::cTypeNotFound :
isTypeFound ?
IntegralResult::cTypeFound :
IntegralResult::cGetNextType;
typedef typename FindIntegral<_TEnumerator, cSelectResult, TFirstType, typename _TTypes::Next >::Type Type;
};
```

There is a nested template class `IsInRange<>`

and if it is true, then the right integer type has been found and the specialized `FindIntegral<..., IntegralResult::cTypeFound>`

template will be instantiated and the search will be stopped. I used some static const variables as a helper to select the specialization of the template; `cMinValue`

is the lowest value and `cMaxValue`

is the largest value of `_TFirstType`

. I had to implement a `Type`

template to store the lowest and largest values for the integer types. It was necessary because I have to check these values at compile time for any integer type. The template is very simple:

```
template <typename _TType> struct Type;
template <>
struct Type<uint8>
{
static const uint8 cMinValue = 0;
static const uint8 cMaxValue = UINT8_MAX;
static const bool cIsSigned = false;
};
// and so on ...
```

As you can see, you must declare explicitly for every integer type; if you miss it, you'll get a compile error.

Let's go back to `FindIntegral::isSigned `

is used for `IsInRange`

and helps to check whether the type has been found or not. The `isSigned`

static const boolean tells that the enumerator is signed or unsigned. The sign checking is necessary because we have to cast `cMinValue`

and `cMaxValue`

to the largest integer type to avoid compile warnings or any other problems, and it's now safe because we know the sign of `_TEnumerator::_MIN_VALUE`

. The value of `cSelectResult`

determines which `FindIntegral`

template will be instantiated. The possible values of the `cSelectResult`

mentioned earlier. There is one more restriction: the range of the integer type must be incremental, starting from the smallest type to the largest.

I implemented and attached the boost, Loki and C++14 versions too to the source. Check it out!

If you have any comment or idea, don't hesitate, let me know!

## History

- 28 Dec 2011: Added boost support and error handling.
- 11 Mar 2012: Added C++11 support with error handling.
- 15 Mar 2013: Added Google test files.
- 19 Mar 2015: Refresh article, fix some mistakes and make the C++14 implementation as a default implementation.